This commit is contained in:
2024-11-05 12:10:06 +08:00
commit 6b687feee2
3995 changed files with 418583 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
use function Cake\Core\deprecationWarning;
deprecationWarning(
'Since 4.1.0: Cake\Database\Schema\BaseSchema is deprecated. ' .
'Use Cake\Database\Schema\SchemaDialect instead.'
);
class_exists('Cake\Database\Schema\SchemaDialect');
+131
View File
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Psr\SimpleCache\CacheInterface;
/**
* Decorates a schema collection and adds caching
*/
class CachedCollection implements CollectionInterface
{
/**
* Cacher instance.
*
* @var \Psr\SimpleCache\CacheInterface
*/
protected $cacher;
/**
* The decorated schema collection
*
* @var \Cake\Database\Schema\CollectionInterface
*/
protected $collection;
/**
* The cache key prefix
*
* @var string
*/
protected $prefix;
/**
* Constructor.
*
* @param \Cake\Database\Schema\CollectionInterface $collection The collection to wrap.
* @param string $prefix The cache key prefix to use. Typically the connection name.
* @param \Psr\SimpleCache\CacheInterface $cacher Cacher instance.
*/
public function __construct(CollectionInterface $collection, string $prefix, CacheInterface $cacher)
{
$this->collection = $collection;
$this->prefix = $prefix;
$this->cacher = $cacher;
}
/**
* @inheritDoc
*/
public function listTablesWithoutViews(): array
{
return $this->collection->listTablesWithoutViews();
}
/**
* @inheritDoc
*/
public function listTables(): array
{
return $this->collection->listTables();
}
/**
* @inheritDoc
*/
public function describe(string $name, array $options = []): TableSchemaInterface
{
$options += ['forceRefresh' => false];
$cacheKey = $this->cacheKey($name);
if (!$options['forceRefresh']) {
$cached = $this->cacher->get($cacheKey);
if ($cached !== null) {
return $cached;
}
}
$table = $this->collection->describe($name, $options);
$this->cacher->set($cacheKey, $table);
return $table;
}
/**
* Get the cache key for a given name.
*
* @param string $name The name to get a cache key for.
* @return string The cache key.
*/
public function cacheKey(string $name): string
{
return $this->prefix . '_' . $name;
}
/**
* Set a cacher.
*
* @param \Psr\SimpleCache\CacheInterface $cacher Cacher object
* @return $this
*/
public function setCacher(CacheInterface $cacher)
{
$this->cacher = $cacher;
return $this;
}
/**
* Get a cacher.
*
* @return \Psr\SimpleCache\CacheInterface $cacher Cacher object
*/
public function getCacher(): CacheInterface
{
return $this->cacher;
}
}
+168
View File
@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Database\Connection;
use Cake\Database\Exception\DatabaseException;
use PDOException;
/**
* Represents a database schema collection
*
* Used to access information about the tables,
* and other data in a database.
*/
class Collection implements CollectionInterface
{
/**
* Connection object
*
* @var \Cake\Database\Connection
*/
protected $_connection;
/**
* Schema dialect instance.
*
* @var \Cake\Database\Schema\SchemaDialect
*/
protected $_dialect;
/**
* Constructor.
*
* @param \Cake\Database\Connection $connection The connection instance.
*/
public function __construct(Connection $connection)
{
$this->_connection = $connection;
$this->_dialect = $connection->getDriver()->schemaDialect();
}
/**
* Get the list of tables, excluding any views, available in the current connection.
*
* @return array<string> The list of tables in the connected database/schema.
*/
public function listTablesWithoutViews(): array
{
[$sql, $params] = $this->_dialect->listTablesWithoutViewsSql($this->_connection->getDriver()->config());
$result = [];
$statement = $this->_connection->execute($sql, $params);
while ($row = $statement->fetch()) {
$result[] = $row[0];
}
$statement->closeCursor();
return $result;
}
/**
* Get the list of tables and views available in the current connection.
*
* @return array<string> The list of tables and views in the connected database/schema.
*/
public function listTables(): array
{
[$sql, $params] = $this->_dialect->listTablesSql($this->_connection->getDriver()->config());
$result = [];
$statement = $this->_connection->execute($sql, $params);
while ($row = $statement->fetch()) {
$result[] = $row[0];
}
$statement->closeCursor();
return $result;
}
/**
* Get the column metadata for a table.
*
* The name can include a database schema name in the form 'schema.table'.
*
* Caching will be applied if `cacheMetadata` key is present in the Connection
* configuration options. Defaults to _cake_model_ when true.
*
* ### Options
*
* - `forceRefresh` - Set to true to force rebuilding the cached metadata.
* Defaults to false.
*
* @param string $name The name of the table to describe.
* @param array<string, mixed> $options The options to use, see above.
* @return \Cake\Database\Schema\TableSchema Object with column metadata.
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
*/
public function describe(string $name, array $options = []): TableSchemaInterface
{
$config = $this->_connection->getDriver()->config();
if (strpos($name, '.')) {
[$config['schema'], $name] = explode('.', $name);
}
$table = $this->_connection->getDriver()->newTableSchema($name);
$this->_reflect('Column', $name, $config, $table);
if (count($table->columns()) === 0) {
throw new DatabaseException(sprintf('Cannot describe %s. It has 0 columns.', $name));
}
$this->_reflect('Index', $name, $config, $table);
$this->_reflect('ForeignKey', $name, $config, $table);
$this->_reflect('Options', $name, $config, $table);
return $table;
}
/**
* Helper method for running each step of the reflection process.
*
* @param string $stage The stage name.
* @param string $name The table name.
* @param array<string, mixed> $config The config data.
* @param \Cake\Database\Schema\TableSchema $schema The table schema instance.
* @return void
* @throws \Cake\Database\Exception\DatabaseException on query failure.
* @uses \Cake\Database\Schema\SchemaDialect::describeColumnSql
* @uses \Cake\Database\Schema\SchemaDialect::describeIndexSql
* @uses \Cake\Database\Schema\SchemaDialect::describeForeignKeySql
* @uses \Cake\Database\Schema\SchemaDialect::describeOptionsSql
* @uses \Cake\Database\Schema\SchemaDialect::convertColumnDescription
* @uses \Cake\Database\Schema\SchemaDialect::convertIndexDescription
* @uses \Cake\Database\Schema\SchemaDialect::convertForeignKeyDescription
* @uses \Cake\Database\Schema\SchemaDialect::convertOptionsDescription
*/
protected function _reflect(string $stage, string $name, array $config, TableSchema $schema): void
{
$describeMethod = "describe{$stage}Sql";
$convertMethod = "convert{$stage}Description";
[$sql, $params] = $this->_dialect->{$describeMethod}($name, $config);
if (empty($sql)) {
return;
}
try {
$statement = $this->_connection->execute($sql, $params);
} catch (PDOException $e) {
throw new DatabaseException($e->getMessage(), 500, $e);
}
/** @psalm-suppress PossiblyFalseIterator */
foreach ($statement->fetchAll('assoc') as $row) {
$this->_dialect->{$convertMethod}($schema, $row);
}
$statement->closeCursor();
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 4.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
/**
* Represents a database schema collection
*
* Used to access information about the tables,
* and other data in a database.
*
* @method array<string> listTablesWithoutViews() Get the list of tables available in the current connection.
* This will exclude any views in the schema.
*/
interface CollectionInterface
{
/**
* Get the list of tables available in the current connection.
*
* @return array<string> The list of tables in the connected database/schema.
*/
public function listTables(): array;
/**
* Get the column metadata for a table.
*
* Caching will be applied if `cacheMetadata` key is present in the Connection
* configuration options. Defaults to _cake_model_ when true.
*
* ### Options
*
* - `forceRefresh` - Set to true to force rebuilding the cached metadata.
* Defaults to false.
*
* @param string $name The name of the table to describe.
* @param array<string, mixed> $options The options to use, see above.
* @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata.
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
*/
public function describe(string $name, array $options = []): TableSchemaInterface;
}
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
use function Cake\Core\deprecationWarning;
deprecationWarning(
'Since 4.1.0: Cake\Database\Schema\MysqlSchema is deprecated. ' .
'Use Cake\Database\Schema\MysqlSchemaDialect instead.'
);
class_exists('Cake\Database\Schema\MysqlSchemaDialect');
+669
View File
@@ -0,0 +1,669 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Database\DriverInterface;
use Cake\Database\Exception\DatabaseException;
/**
* Schema generation/reflection features for MySQL
*
* @internal
*/
class MysqlSchemaDialect extends SchemaDialect
{
/**
* The driver instance being used.
*
* @var \Cake\Database\Driver\Mysql
*/
protected $_driver;
/**
* Generate the SQL to list the tables and views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array<mixed> An array of (sql, params) to execute.
*/
public function listTablesSql(array $config): array
{
return ['SHOW FULL TABLES FROM ' . $this->_driver->quoteIdentifier($config['database']), []];
}
/**
* Generate the SQL to list the tables, excluding all views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array<mixed> An array of (sql, params) to execute.
*/
public function listTablesWithoutViewsSql(array $config): array
{
return [
'SHOW FULL TABLES FROM ' . $this->_driver->quoteIdentifier($config['database'])
. ' WHERE TABLE_TYPE = "BASE TABLE"'
, []];
}
/**
* @inheritDoc
*/
public function describeColumnSql(string $tableName, array $config): array
{
return ['SHOW FULL COLUMNS FROM ' . $this->_driver->quoteIdentifier($tableName), []];
}
/**
* @inheritDoc
*/
public function describeIndexSql(string $tableName, array $config): array
{
return ['SHOW INDEXES FROM ' . $this->_driver->quoteIdentifier($tableName), []];
}
/**
* @inheritDoc
*/
public function describeOptionsSql(string $tableName, array $config): array
{
return ['SHOW TABLE STATUS WHERE Name = ?', [$tableName]];
}
/**
* @inheritDoc
*/
public function convertOptionsDescription(TableSchema $schema, array $row): void
{
$schema->setOptions([
'engine' => $row['Engine'],
'collation' => $row['Collation'],
]);
}
/**
* Convert a MySQL column type into an abstract type.
*
* The returned type will be a type that Cake\Database\TypeFactory can handle.
*
* @param string $column The column type + length
* @return array<string, mixed> Array of column information.
* @throws \Cake\Database\Exception\DatabaseException When column type cannot be parsed.
*/
protected function _convertColumn(string $column): array
{
preg_match('/([a-z]+)(?:\(([0-9,]+)\))?\s*([a-z]+)?/i', $column, $matches);
if (empty($matches)) {
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
}
$col = strtolower($matches[1]);
$length = $precision = $scale = null;
if (isset($matches[2]) && strlen($matches[2])) {
$length = $matches[2];
if (strpos($matches[2], ',') !== false) {
[$length, $precision] = explode(',', $length);
}
$length = (int)$length;
$precision = (int)$precision;
}
$type = $this->_applyTypeSpecificColumnConversion(
$col,
compact('length', 'precision', 'scale')
);
if ($type !== null) {
return $type;
}
if (in_array($col, ['date', 'time'])) {
return ['type' => $col, 'length' => null];
}
if (in_array($col, ['datetime', 'timestamp'])) {
$typeName = $col;
if ($length > 0) {
$typeName = $col . 'fractional';
}
return ['type' => $typeName, 'length' => null, 'precision' => $length];
}
if (($col === 'tinyint' && $length === 1) || $col === 'boolean') {
return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null];
}
$unsigned = (isset($matches[3]) && strtolower($matches[3]) === 'unsigned');
if (strpos($col, 'bigint') !== false || $col === 'bigint') {
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => null, 'unsigned' => $unsigned];
}
if ($col === 'tinyint') {
return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => null, 'unsigned' => $unsigned];
}
if ($col === 'smallint') {
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => null, 'unsigned' => $unsigned];
}
if (in_array($col, ['int', 'integer', 'mediumint'])) {
return ['type' => TableSchema::TYPE_INTEGER, 'length' => null, 'unsigned' => $unsigned];
}
if ($col === 'char' && $length === 36) {
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
}
if ($col === 'char') {
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
}
if (strpos($col, 'char') !== false) {
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
}
if (strpos($col, 'text') !== false) {
$lengthName = substr($col, 0, -4);
$length = TableSchema::$columnLengths[$lengthName] ?? null;
return ['type' => TableSchema::TYPE_TEXT, 'length' => $length];
}
if ($col === 'binary' && $length === 16) {
return ['type' => TableSchema::TYPE_BINARY_UUID, 'length' => null];
}
if (strpos($col, 'blob') !== false || in_array($col, ['binary', 'varbinary'])) {
$lengthName = substr($col, 0, -4);
$length = TableSchema::$columnLengths[$lengthName] ?? $length;
return ['type' => TableSchema::TYPE_BINARY, 'length' => $length];
}
if (strpos($col, 'float') !== false || strpos($col, 'double') !== false) {
return [
'type' => TableSchema::TYPE_FLOAT,
'length' => $length,
'precision' => $precision,
'unsigned' => $unsigned,
];
}
if (strpos($col, 'decimal') !== false) {
return [
'type' => TableSchema::TYPE_DECIMAL,
'length' => $length,
'precision' => $precision,
'unsigned' => $unsigned,
];
}
if (strpos($col, 'json') !== false) {
return ['type' => TableSchema::TYPE_JSON, 'length' => null];
}
return ['type' => TableSchema::TYPE_STRING, 'length' => null];
}
/**
* @inheritDoc
*/
public function convertColumnDescription(TableSchema $schema, array $row): void
{
$field = $this->_convertColumn($row['Type']);
$field += [
'null' => $row['Null'] === 'YES',
'default' => $row['Default'],
'collate' => $row['Collation'],
'comment' => $row['Comment'],
];
if (isset($row['Extra']) && $row['Extra'] === 'auto_increment') {
$field['autoIncrement'] = true;
}
$schema->addColumn($row['Field'], $field);
}
/**
* @inheritDoc
*/
public function convertIndexDescription(TableSchema $schema, array $row): void
{
$type = null;
$columns = $length = [];
$name = $row['Key_name'];
if ($name === 'PRIMARY') {
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
}
if (!empty($row['Column_name'])) {
$columns[] = $row['Column_name'];
}
if ($row['Index_type'] === 'FULLTEXT') {
$type = TableSchema::INDEX_FULLTEXT;
} elseif ((int)$row['Non_unique'] === 0 && $type !== 'primary') {
$type = TableSchema::CONSTRAINT_UNIQUE;
} elseif ($type !== 'primary') {
$type = TableSchema::INDEX_INDEX;
}
if (!empty($row['Sub_part'])) {
$length[$row['Column_name']] = $row['Sub_part'];
}
$isIndex = (
$type === TableSchema::INDEX_INDEX ||
$type === TableSchema::INDEX_FULLTEXT
);
if ($isIndex) {
$existing = $schema->getIndex($name);
} else {
$existing = $schema->getConstraint($name);
}
// MySQL multi column indexes come back as multiple rows.
if (!empty($existing)) {
$columns = array_merge($existing['columns'], $columns);
$length = array_merge($existing['length'], $length);
}
if ($isIndex) {
$schema->addIndex($name, [
'type' => $type,
'columns' => $columns,
'length' => $length,
]);
} else {
$schema->addConstraint($name, [
'type' => $type,
'columns' => $columns,
'length' => $length,
]);
}
}
/**
* @inheritDoc
*/
public function describeForeignKeySql(string $tableName, array $config): array
{
$sql = 'SELECT * FROM information_schema.key_column_usage AS kcu
INNER JOIN information_schema.referential_constraints AS rc
ON (
kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
)
WHERE kcu.TABLE_SCHEMA = ? AND kcu.TABLE_NAME = ? AND rc.TABLE_NAME = ?
ORDER BY kcu.ORDINAL_POSITION ASC';
return [$sql, [$config['database'], $tableName, $tableName]];
}
/**
* @inheritDoc
*/
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
{
$data = [
'type' => TableSchema::CONSTRAINT_FOREIGN,
'columns' => [$row['COLUMN_NAME']],
'references' => [$row['REFERENCED_TABLE_NAME'], $row['REFERENCED_COLUMN_NAME']],
'update' => $this->_convertOnClause($row['UPDATE_RULE']),
'delete' => $this->_convertOnClause($row['DELETE_RULE']),
];
$name = $row['CONSTRAINT_NAME'];
$schema->addConstraint($name, $data);
}
/**
* @inheritDoc
*/
public function truncateTableSql(TableSchema $schema): array
{
return [sprintf('TRUNCATE TABLE `%s`', $schema->name())];
}
/**
* @inheritDoc
*/
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
{
$content = implode(",\n", array_merge($columns, $constraints, $indexes));
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
$content = sprintf("CREATE%sTABLE `%s` (\n%s\n)", $temporary, $schema->name(), $content);
$options = $schema->getOptions();
if (isset($options['engine'])) {
$content .= sprintf(' ENGINE=%s', $options['engine']);
}
if (isset($options['charset'])) {
$content .= sprintf(' DEFAULT CHARSET=%s', $options['charset']);
}
if (isset($options['collate'])) {
$content .= sprintf(' COLLATE=%s', $options['collate']);
}
return [$content];
}
/**
* @inheritDoc
*/
public function columnSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getColumn($name);
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
if ($sql !== null) {
return $sql;
}
$out = $this->_driver->quoteIdentifier($name);
$nativeJson = $this->_driver->supports(DriverInterface::FEATURE_JSON);
$typeMap = [
TableSchema::TYPE_TINYINTEGER => ' TINYINT',
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
TableSchema::TYPE_INTEGER => ' INTEGER',
TableSchema::TYPE_BIGINTEGER => ' BIGINT',
TableSchema::TYPE_BINARY_UUID => ' BINARY(16)',
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
TableSchema::TYPE_FLOAT => ' FLOAT',
TableSchema::TYPE_DECIMAL => ' DECIMAL',
TableSchema::TYPE_DATE => ' DATE',
TableSchema::TYPE_TIME => ' TIME',
TableSchema::TYPE_DATETIME => ' DATETIME',
TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIME',
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP',
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMP',
TableSchema::TYPE_CHAR => ' CHAR',
TableSchema::TYPE_UUID => ' CHAR(36)',
TableSchema::TYPE_JSON => $nativeJson ? ' JSON' : ' LONGTEXT',
];
$specialMap = [
'string' => true,
'text' => true,
'char' => true,
'binary' => true,
];
if (isset($typeMap[$data['type']])) {
$out .= $typeMap[$data['type']];
}
if (isset($specialMap[$data['type']])) {
switch ($data['type']) {
case TableSchema::TYPE_STRING:
$out .= ' VARCHAR';
if (!isset($data['length'])) {
$data['length'] = 255;
}
break;
case TableSchema::TYPE_TEXT:
$isKnownLength = in_array($data['length'], TableSchema::$columnLengths);
if (empty($data['length']) || !$isKnownLength) {
$out .= ' TEXT';
break;
}
/** @var string $length */
$length = array_search($data['length'], TableSchema::$columnLengths);
$out .= ' ' . strtoupper($length) . 'TEXT';
break;
case TableSchema::TYPE_BINARY:
$isKnownLength = in_array($data['length'], TableSchema::$columnLengths);
if ($isKnownLength) {
/** @var string $length */
$length = array_search($data['length'], TableSchema::$columnLengths);
$out .= ' ' . strtoupper($length) . 'BLOB';
break;
}
if (empty($data['length'])) {
$out .= ' BLOB';
break;
}
if ($data['length'] > 2) {
$out .= ' VARBINARY(' . $data['length'] . ')';
} else {
$out .= ' BINARY(' . $data['length'] . ')';
}
break;
}
}
$hasLength = [
TableSchema::TYPE_INTEGER,
TableSchema::TYPE_CHAR,
TableSchema::TYPE_SMALLINTEGER,
TableSchema::TYPE_TINYINTEGER,
TableSchema::TYPE_STRING,
];
if (in_array($data['type'], $hasLength, true) && isset($data['length'])) {
$out .= '(' . $data['length'] . ')';
}
$lengthAndPrecisionTypes = [TableSchema::TYPE_FLOAT, TableSchema::TYPE_DECIMAL];
if (in_array($data['type'], $lengthAndPrecisionTypes, true) && isset($data['length'])) {
if (isset($data['precision'])) {
$out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
} else {
$out .= '(' . (int)$data['length'] . ')';
}
}
$precisionTypes = [TableSchema::TYPE_DATETIME_FRACTIONAL, TableSchema::TYPE_TIMESTAMP_FRACTIONAL];
if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) {
$out .= '(' . (int)$data['precision'] . ')';
}
$hasUnsigned = [
TableSchema::TYPE_TINYINTEGER,
TableSchema::TYPE_SMALLINTEGER,
TableSchema::TYPE_INTEGER,
TableSchema::TYPE_BIGINTEGER,
TableSchema::TYPE_FLOAT,
TableSchema::TYPE_DECIMAL,
];
if (
in_array($data['type'], $hasUnsigned, true) &&
isset($data['unsigned']) &&
$data['unsigned'] === true
) {
$out .= ' UNSIGNED';
}
$hasCollate = [
TableSchema::TYPE_TEXT,
TableSchema::TYPE_CHAR,
TableSchema::TYPE_STRING,
];
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
$out .= ' COLLATE ' . $data['collate'];
}
if (isset($data['null']) && $data['null'] === false) {
$out .= ' NOT NULL';
}
$addAutoIncrement = (
$schema->getPrimaryKey() === [$name] &&
!$schema->hasAutoincrement() &&
!isset($data['autoIncrement'])
);
if (
in_array($data['type'], [TableSchema::TYPE_INTEGER, TableSchema::TYPE_BIGINTEGER]) &&
(
$data['autoIncrement'] === true ||
$addAutoIncrement
)
) {
$out .= ' AUTO_INCREMENT';
}
$timestampTypes = [
TableSchema::TYPE_TIMESTAMP,
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
];
if (isset($data['null']) && $data['null'] === true && in_array($data['type'], $timestampTypes, true)) {
$out .= ' NULL';
unset($data['default']);
}
$dateTimeTypes = [
TableSchema::TYPE_DATETIME,
TableSchema::TYPE_DATETIME_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP,
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
];
if (
isset($data['default']) &&
in_array($data['type'], $dateTimeTypes) &&
strpos(strtolower($data['default']), 'current_timestamp') !== false
) {
$out .= ' DEFAULT CURRENT_TIMESTAMP';
if (isset($data['precision'])) {
$out .= '(' . $data['precision'] . ')';
}
unset($data['default']);
}
if (isset($data['default'])) {
$out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']);
unset($data['default']);
}
if (isset($data['comment']) && $data['comment'] !== '') {
$out .= ' COMMENT ' . $this->_driver->schemaValue($data['comment']);
}
return $out;
}
/**
* @inheritDoc
*/
public function constraintSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getConstraint($name);
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
return sprintf('PRIMARY KEY (%s)', implode(', ', $columns));
}
$out = '';
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
$out = 'UNIQUE KEY ';
}
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$out = 'CONSTRAINT ';
}
$out .= $this->_driver->quoteIdentifier($name);
return $this->_keySql($out, $data);
}
/**
* @inheritDoc
*/
public function addConstraintSql(TableSchema $schema): array
{
$sqlPattern = 'ALTER TABLE %s ADD %s;';
$sql = [];
foreach ($schema->constraints() as $name) {
/** @var array $constraint */
$constraint = $schema->getConstraint($name);
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$tableName = $this->_driver->quoteIdentifier($schema->name());
$sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name));
}
}
return $sql;
}
/**
* @inheritDoc
*/
public function dropConstraintSql(TableSchema $schema): array
{
$sqlPattern = 'ALTER TABLE %s DROP FOREIGN KEY %s;';
$sql = [];
foreach ($schema->constraints() as $name) {
/** @var array $constraint */
$constraint = $schema->getConstraint($name);
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$tableName = $this->_driver->quoteIdentifier($schema->name());
$constraintName = $this->_driver->quoteIdentifier($name);
$sql[] = sprintf($sqlPattern, $tableName, $constraintName);
}
}
return $sql;
}
/**
* @inheritDoc
*/
public function indexSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getIndex($name);
$out = '';
if ($data['type'] === TableSchema::INDEX_INDEX) {
$out = 'KEY ';
}
if ($data['type'] === TableSchema::INDEX_FULLTEXT) {
$out = 'FULLTEXT KEY ';
}
$out .= $this->_driver->quoteIdentifier($name);
return $this->_keySql($out, $data);
}
/**
* Helper method for generating key SQL snippets.
*
* @param string $prefix The key prefix
* @param array $data Key data.
* @return string
*/
protected function _keySql(string $prefix, array $data): string
{
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
foreach ($data['columns'] as $i => $column) {
if (isset($data['length'][$column])) {
$columns[$i] .= sprintf('(%d)', $data['length'][$column]);
}
}
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
return $prefix . sprintf(
' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
implode(', ', $columns),
$this->_driver->quoteIdentifier($data['references'][0]),
$this->_convertConstraintColumns($data['references'][1]),
$this->_foreignOnClause($data['update']),
$this->_foreignOnClause($data['delete'])
);
}
return $prefix . ' (' . implode(', ', $columns) . ')';
}
}
// phpcs:disable
class_alias(
'Cake\Database\Schema\MysqlSchemaDialect',
'Cake\Database\Schema\MysqlSchema'
);
// phpcs:enable
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
use function Cake\Core\deprecationWarning;
deprecationWarning(
'Since 4.1.0: Cake\Database\Schema\PostgresSchema is deprecated. ' .
'Use Cake\Database\Schema\PostgresSchemaDialect instead.'
);
class_exists('Cake\Database\Schema\PostgresSchemaDialect');
+704
View File
@@ -0,0 +1,704 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Database\Exception\DatabaseException;
/**
* Schema management/reflection features for Postgres.
*
* @internal
*/
class PostgresSchemaDialect extends SchemaDialect
{
/**
* Generate the SQL to list the tables and views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array An array of (sql, params) to execute.
*/
public function listTablesSql(array $config): array
{
$sql = 'SELECT table_name as name FROM information_schema.tables
WHERE table_schema = ? ORDER BY name';
$schema = empty($config['schema']) ? 'public' : $config['schema'];
return [$sql, [$schema]];
}
/**
* Generate the SQL to list the tables, excluding all views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array<mixed> An array of (sql, params) to execute.
*/
public function listTablesWithoutViewsSql(array $config): array
{
$sql = 'SELECT table_name as name FROM information_schema.tables
WHERE table_schema = ? AND table_type = \'BASE TABLE\' ORDER BY name';
$schema = empty($config['schema']) ? 'public' : $config['schema'];
return [$sql, [$schema]];
}
/**
* @inheritDoc
*/
public function describeColumnSql(string $tableName, array $config): array
{
$sql = 'SELECT DISTINCT table_schema AS schema,
column_name AS name,
data_type AS type,
is_nullable AS null, column_default AS default,
character_maximum_length AS char_length,
c.collation_name,
d.description as comment,
ordinal_position,
c.datetime_precision,
c.numeric_precision as column_precision,
c.numeric_scale as column_scale,
pg_get_serial_sequence(attr.attrelid::regclass::text, attr.attname) IS NOT NULL AS has_serial
FROM information_schema.columns c
INNER JOIN pg_catalog.pg_namespace ns ON (ns.nspname = table_schema)
INNER JOIN pg_catalog.pg_class cl ON (cl.relnamespace = ns.oid AND cl.relname = table_name)
LEFT JOIN pg_catalog.pg_index i ON (i.indrelid = cl.oid AND i.indkey[0] = c.ordinal_position)
LEFT JOIN pg_catalog.pg_description d on (cl.oid = d.objoid AND d.objsubid = c.ordinal_position)
LEFT JOIN pg_catalog.pg_attribute attr ON (cl.oid = attr.attrelid AND column_name = attr.attname)
WHERE table_name = ? AND table_schema = ? AND table_catalog = ?
ORDER BY ordinal_position';
$schema = empty($config['schema']) ? 'public' : $config['schema'];
return [$sql, [$tableName, $schema, $config['database']]];
}
/**
* Convert a column definition to the abstract types.
*
* The returned type will be a type that
* Cake\Database\TypeFactory can handle.
*
* @param string $column The column type + length
* @throws \Cake\Database\Exception\DatabaseException when column cannot be parsed.
* @return array<string, mixed> Array of column information.
*/
protected function _convertColumn(string $column): array
{
preg_match('/([a-z\s]+)(?:\(([0-9,]+)\))?/i', $column, $matches);
if (empty($matches)) {
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
}
$col = strtolower($matches[1]);
$length = $precision = $scale = null;
if (isset($matches[2])) {
$length = (int)$matches[2];
}
$type = $this->_applyTypeSpecificColumnConversion(
$col,
compact('length', 'precision', 'scale')
);
if ($type !== null) {
return $type;
}
if (in_array($col, ['date', 'time', 'boolean'], true)) {
return ['type' => $col, 'length' => null];
}
if (in_array($col, ['timestamptz', 'timestamp with time zone'], true)) {
return ['type' => TableSchema::TYPE_TIMESTAMP_TIMEZONE, 'length' => null];
}
if (strpos($col, 'timestamp') !== false) {
return ['type' => TableSchema::TYPE_TIMESTAMP_FRACTIONAL, 'length' => null];
}
if (strpos($col, 'time') !== false) {
return ['type' => TableSchema::TYPE_TIME, 'length' => null];
}
if ($col === 'serial' || $col === 'integer') {
return ['type' => TableSchema::TYPE_INTEGER, 'length' => 10];
}
if ($col === 'bigserial' || $col === 'bigint') {
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => 20];
}
if ($col === 'smallint') {
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => 5];
}
if ($col === 'inet') {
return ['type' => TableSchema::TYPE_STRING, 'length' => 39];
}
if ($col === 'uuid') {
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
}
if ($col === 'char') {
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
}
if (strpos($col, 'character') !== false) {
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
}
// money is 'string' as it includes arbitrary text content
// before the number value.
if (strpos($col, 'money') !== false || $col === 'string') {
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
}
if (strpos($col, 'text') !== false) {
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
}
if ($col === 'bytea') {
return ['type' => TableSchema::TYPE_BINARY, 'length' => null];
}
if ($col === 'real' || strpos($col, 'double') !== false) {
return ['type' => TableSchema::TYPE_FLOAT, 'length' => null];
}
if (
strpos($col, 'numeric') !== false ||
strpos($col, 'decimal') !== false
) {
return ['type' => TableSchema::TYPE_DECIMAL, 'length' => null];
}
if (strpos($col, 'json') !== false) {
return ['type' => TableSchema::TYPE_JSON, 'length' => null];
}
$length = is_numeric($length) ? $length : null;
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
}
/**
* @inheritDoc
*/
public function convertColumnDescription(TableSchema $schema, array $row): void
{
$field = $this->_convertColumn($row['type']);
if ($field['type'] === TableSchema::TYPE_BOOLEAN) {
if ($row['default'] === 'true') {
$row['default'] = 1;
}
if ($row['default'] === 'false') {
$row['default'] = 0;
}
}
if (!empty($row['has_serial'])) {
$field['autoIncrement'] = true;
}
$field += [
'default' => $this->_defaultValue($row['default']),
'null' => $row['null'] === 'YES',
'collate' => $row['collation_name'],
'comment' => $row['comment'],
];
$field['length'] = $row['char_length'] ?: $field['length'];
if ($field['type'] === 'numeric' || $field['type'] === 'decimal') {
$field['length'] = $row['column_precision'];
$field['precision'] = $row['column_scale'] ?: null;
}
if ($field['type'] === TableSchema::TYPE_TIMESTAMP_FRACTIONAL) {
$field['precision'] = $row['datetime_precision'];
if ($field['precision'] === 0) {
$field['type'] = TableSchema::TYPE_TIMESTAMP;
}
}
if ($field['type'] === TableSchema::TYPE_TIMESTAMP_TIMEZONE) {
$field['precision'] = $row['datetime_precision'];
}
$schema->addColumn($row['name'], $field);
}
/**
* Manipulate the default value.
*
* Postgres includes sequence data and casting information in default values.
* We need to remove those.
*
* @param string|int|null $default The default value.
* @return string|int|null
*/
protected function _defaultValue($default)
{
if (is_numeric($default) || $default === null) {
return $default;
}
// Sequences
if (strpos($default, 'nextval') === 0) {
return null;
}
if (strpos($default, 'NULL::') === 0) {
return null;
}
// Remove quotes and postgres casts
return preg_replace(
"/^'(.*)'(?:::.*)$/",
'$1',
$default
);
}
/**
* @inheritDoc
*/
public function describeIndexSql(string $tableName, array $config): array
{
$sql = 'SELECT
c2.relname,
a.attname,
i.indisprimary,
i.indisunique
FROM pg_catalog.pg_namespace n
INNER JOIN pg_catalog.pg_class c ON (n.oid = c.relnamespace)
INNER JOIN pg_catalog.pg_index i ON (c.oid = i.indrelid)
INNER JOIN pg_catalog.pg_class c2 ON (c2.oid = i.indexrelid)
INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND i.indrelid::regclass = a.attrelid::regclass)
WHERE n.nspname = ?
AND a.attnum = ANY(i.indkey)
AND c.relname = ?
ORDER BY i.indisprimary DESC, i.indisunique DESC, c.relname, a.attnum';
$schema = 'public';
if (!empty($config['schema'])) {
$schema = $config['schema'];
}
return [$sql, [$schema, $tableName]];
}
/**
* @inheritDoc
*/
public function convertIndexDescription(TableSchema $schema, array $row): void
{
$type = TableSchema::INDEX_INDEX;
$name = $row['relname'];
if ($row['indisprimary']) {
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
}
if ($row['indisunique'] && $type === TableSchema::INDEX_INDEX) {
$type = TableSchema::CONSTRAINT_UNIQUE;
}
if ($type === TableSchema::CONSTRAINT_PRIMARY || $type === TableSchema::CONSTRAINT_UNIQUE) {
$this->_convertConstraint($schema, $name, $type, $row);
return;
}
$index = $schema->getIndex($name);
if (!$index) {
$index = [
'type' => $type,
'columns' => [],
];
}
$index['columns'][] = $row['attname'];
$schema->addIndex($name, $index);
}
/**
* Add/update a constraint into the schema object.
*
* @param \Cake\Database\Schema\TableSchema $schema The table to update.
* @param string $name The index name.
* @param string $type The index type.
* @param array $row The metadata record to update with.
* @return void
*/
protected function _convertConstraint(TableSchema $schema, string $name, string $type, array $row): void
{
$constraint = $schema->getConstraint($name);
if (!$constraint) {
$constraint = [
'type' => $type,
'columns' => [],
];
}
$constraint['columns'][] = $row['attname'];
$schema->addConstraint($name, $constraint);
}
/**
* @inheritDoc
*/
public function describeForeignKeySql(string $tableName, array $config): array
{
// phpcs:disable Generic.Files.LineLength
$sql = 'SELECT
c.conname AS name,
c.contype AS type,
a.attname AS column_name,
c.confmatchtype AS match_type,
c.confupdtype AS on_update,
c.confdeltype AS on_delete,
c.confrelid::regclass AS references_table,
ab.attname AS references_field
FROM pg_catalog.pg_namespace n
INNER JOIN pg_catalog.pg_class cl ON (n.oid = cl.relnamespace)
INNER JOIN pg_catalog.pg_constraint c ON (n.oid = c.connamespace)
INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid = cl.oid AND c.conrelid = a.attrelid AND a.attnum = ANY(c.conkey))
INNER JOIN pg_catalog.pg_attribute ab ON (a.attrelid = cl.oid AND c.confrelid = ab.attrelid AND ab.attnum = ANY(c.confkey))
WHERE n.nspname = ?
AND cl.relname = ?
ORDER BY name, a.attnum, ab.attnum DESC';
// phpcs:enable Generic.Files.LineLength
$schema = empty($config['schema']) ? 'public' : $config['schema'];
return [$sql, [$schema, $tableName]];
}
/**
* @inheritDoc
*/
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
{
$data = [
'type' => TableSchema::CONSTRAINT_FOREIGN,
'columns' => $row['column_name'],
'references' => [$row['references_table'], $row['references_field']],
'update' => $this->_convertOnClause($row['on_update']),
'delete' => $this->_convertOnClause($row['on_delete']),
];
$schema->addConstraint($row['name'], $data);
}
/**
* @inheritDoc
*/
protected function _convertOnClause(string $clause): string
{
if ($clause === 'r') {
return TableSchema::ACTION_RESTRICT;
}
if ($clause === 'a') {
return TableSchema::ACTION_NO_ACTION;
}
if ($clause === 'c') {
return TableSchema::ACTION_CASCADE;
}
return TableSchema::ACTION_SET_NULL;
}
/**
* @inheritDoc
*/
public function columnSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getColumn($name);
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
if ($sql !== null) {
return $sql;
}
$out = $this->_driver->quoteIdentifier($name);
$typeMap = [
TableSchema::TYPE_TINYINTEGER => ' SMALLINT',
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
TableSchema::TYPE_BINARY_UUID => ' UUID',
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
TableSchema::TYPE_FLOAT => ' FLOAT',
TableSchema::TYPE_DECIMAL => ' DECIMAL',
TableSchema::TYPE_DATE => ' DATE',
TableSchema::TYPE_TIME => ' TIME',
TableSchema::TYPE_DATETIME => ' TIMESTAMP',
TableSchema::TYPE_DATETIME_FRACTIONAL => ' TIMESTAMP',
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP',
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTZ',
TableSchema::TYPE_UUID => ' UUID',
TableSchema::TYPE_CHAR => ' CHAR',
TableSchema::TYPE_JSON => ' JSONB',
];
if (isset($typeMap[$data['type']])) {
$out .= $typeMap[$data['type']];
}
if ($data['type'] === TableSchema::TYPE_INTEGER || $data['type'] === TableSchema::TYPE_BIGINTEGER) {
$type = $data['type'] === TableSchema::TYPE_INTEGER ? ' INTEGER' : ' BIGINT';
if ($schema->getPrimaryKey() === [$name] || $data['autoIncrement'] === true) {
$type = $data['type'] === TableSchema::TYPE_INTEGER ? ' SERIAL' : ' BIGSERIAL';
unset($data['null'], $data['default']);
}
$out .= $type;
}
if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
$out .= ' TEXT';
}
if ($data['type'] === TableSchema::TYPE_BINARY) {
$out .= ' BYTEA';
}
if ($data['type'] === TableSchema::TYPE_CHAR) {
$out .= '(' . $data['length'] . ')';
}
if (
$data['type'] === TableSchema::TYPE_STRING ||
(
$data['type'] === TableSchema::TYPE_TEXT &&
$data['length'] === TableSchema::LENGTH_TINY
)
) {
$out .= ' VARCHAR';
if (isset($data['length']) && $data['length'] !== '') {
$out .= '(' . $data['length'] . ')';
}
}
$hasCollate = [TableSchema::TYPE_TEXT, TableSchema::TYPE_STRING, TableSchema::TYPE_CHAR];
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
$out .= ' COLLATE "' . $data['collate'] . '"';
}
$hasPrecision = [
TableSchema::TYPE_FLOAT,
TableSchema::TYPE_DATETIME,
TableSchema::TYPE_DATETIME_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP,
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
];
if (in_array($data['type'], $hasPrecision) && isset($data['precision'])) {
$out .= '(' . $data['precision'] . ')';
}
if (
$data['type'] === TableSchema::TYPE_DECIMAL &&
(
isset($data['length']) ||
isset($data['precision'])
)
) {
$out .= '(' . $data['length'] . ',' . (int)$data['precision'] . ')';
}
if (isset($data['null']) && $data['null'] === false) {
$out .= ' NOT NULL';
}
$datetimeTypes = [
TableSchema::TYPE_DATETIME,
TableSchema::TYPE_DATETIME_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP,
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
];
if (
isset($data['default']) &&
in_array($data['type'], $datetimeTypes) &&
strtolower($data['default']) === 'current_timestamp'
) {
$out .= ' DEFAULT CURRENT_TIMESTAMP';
} elseif (isset($data['default'])) {
$defaultValue = $data['default'];
if ($data['type'] === 'boolean') {
$defaultValue = (bool)$defaultValue;
}
$out .= ' DEFAULT ' . $this->_driver->schemaValue($defaultValue);
} elseif (isset($data['null']) && $data['null'] !== false) {
$out .= ' DEFAULT NULL';
}
return $out;
}
/**
* @inheritDoc
*/
public function addConstraintSql(TableSchema $schema): array
{
$sqlPattern = 'ALTER TABLE %s ADD %s;';
$sql = [];
foreach ($schema->constraints() as $name) {
/** @var array $constraint */
$constraint = $schema->getConstraint($name);
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$tableName = $this->_driver->quoteIdentifier($schema->name());
$sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name));
}
}
return $sql;
}
/**
* @inheritDoc
*/
public function dropConstraintSql(TableSchema $schema): array
{
$sqlPattern = 'ALTER TABLE %s DROP CONSTRAINT %s;';
$sql = [];
foreach ($schema->constraints() as $name) {
/** @var array $constraint */
$constraint = $schema->getConstraint($name);
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$tableName = $this->_driver->quoteIdentifier($schema->name());
$constraintName = $this->_driver->quoteIdentifier($name);
$sql[] = sprintf($sqlPattern, $tableName, $constraintName);
}
}
return $sql;
}
/**
* @inheritDoc
*/
public function indexSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getIndex($name);
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
return sprintf(
'CREATE INDEX %s ON %s (%s)',
$this->_driver->quoteIdentifier($name),
$this->_driver->quoteIdentifier($schema->name()),
implode(', ', $columns)
);
}
/**
* @inheritDoc
*/
public function constraintSql(TableSchema $schema, string $name): string
{
/** @var array<string, mixed> $data */
$data = $schema->getConstraint($name);
$out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name);
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
$out = 'PRIMARY KEY';
}
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
$out .= ' UNIQUE';
}
return $this->_keySql($out, $data);
}
/**
* Helper method for generating key SQL snippets.
*
* @param string $prefix The key prefix
* @param array<string, mixed> $data Key data.
* @return string
*/
protected function _keySql(string $prefix, array $data): string
{
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
return $prefix . sprintf(
' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s DEFERRABLE INITIALLY IMMEDIATE',
implode(', ', $columns),
$this->_driver->quoteIdentifier($data['references'][0]),
$this->_convertConstraintColumns($data['references'][1]),
$this->_foreignOnClause($data['update']),
$this->_foreignOnClause($data['delete'])
);
}
return $prefix . ' (' . implode(', ', $columns) . ')';
}
/**
* @inheritDoc
*/
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
{
$content = array_merge($columns, $constraints);
$content = implode(",\n", array_filter($content));
$tableName = $this->_driver->quoteIdentifier($schema->name());
$dbSchema = $this->_driver->schema();
if ($dbSchema != 'public') {
$tableName = $this->_driver->quoteIdentifier($dbSchema) . '.' . $tableName;
}
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
$out = [];
$out[] = sprintf("CREATE%sTABLE %s (\n%s\n)", $temporary, $tableName, $content);
foreach ($indexes as $index) {
$out[] = $index;
}
foreach ($schema->columns() as $column) {
$columnData = $schema->getColumn($column);
if (isset($columnData['comment'])) {
$out[] = sprintf(
'COMMENT ON COLUMN %s.%s IS %s',
$tableName,
$this->_driver->quoteIdentifier($column),
$this->_driver->schemaValue($columnData['comment'])
);
}
}
return $out;
}
/**
* @inheritDoc
*/
public function truncateTableSql(TableSchema $schema): array
{
$name = $this->_driver->quoteIdentifier($schema->name());
return [
sprintf('TRUNCATE %s RESTART IDENTITY CASCADE', $name),
];
}
/**
* Generate the SQL to drop a table.
*
* @param \Cake\Database\Schema\TableSchema $schema Table instance
* @return array SQL statements to drop a table.
*/
public function dropTableSql(TableSchema $schema): array
{
$sql = sprintf(
'DROP TABLE %s CASCADE',
$this->_driver->quoteIdentifier($schema->name())
);
return [$sql];
}
}
// phpcs:disable
class_alias(
'Cake\Database\Schema\PostgresSchemaDialect',
'Cake\Database\Schema\PostgresSchema'
);
// phpcs:enable
+346
View File
@@ -0,0 +1,346 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Database\DriverInterface;
use Cake\Database\Type\ColumnSchemaAwareInterface;
use Cake\Database\TypeFactory;
use InvalidArgumentException;
/**
* Base class for schema implementations.
*
* This class contains methods that are common across
* the various SQL dialects.
*
* @method array<mixed> listTablesWithoutViewsSql(array $config) Generate the SQL to list the tables, excluding all views.
*/
abstract class SchemaDialect
{
/**
* The driver instance being used.
*
* @var \Cake\Database\DriverInterface
*/
protected $_driver;
/**
* Constructor
*
* This constructor will connect the driver so that methods like columnSql() and others
* will fail when the driver has not been connected.
*
* @param \Cake\Database\DriverInterface $driver The driver to use.
*/
public function __construct(DriverInterface $driver)
{
$driver->connect();
$this->_driver = $driver;
}
/**
* Generate an ON clause for a foreign key.
*
* @param string $on The on clause
* @return string
*/
protected function _foreignOnClause(string $on): string
{
if ($on === TableSchema::ACTION_SET_NULL) {
return 'SET NULL';
}
if ($on === TableSchema::ACTION_SET_DEFAULT) {
return 'SET DEFAULT';
}
if ($on === TableSchema::ACTION_CASCADE) {
return 'CASCADE';
}
if ($on === TableSchema::ACTION_RESTRICT) {
return 'RESTRICT';
}
if ($on === TableSchema::ACTION_NO_ACTION) {
return 'NO ACTION';
}
throw new InvalidArgumentException('Invalid value for "on": ' . $on);
}
/**
* Convert string on clauses to the abstract ones.
*
* @param string $clause The on clause to convert.
* @return string
*/
protected function _convertOnClause(string $clause): string
{
if ($clause === 'CASCADE' || $clause === 'RESTRICT') {
return strtolower($clause);
}
if ($clause === 'NO ACTION') {
return TableSchema::ACTION_NO_ACTION;
}
return TableSchema::ACTION_SET_NULL;
}
/**
* Convert foreign key constraints references to a valid
* stringified list
*
* @param array<string>|string $references The referenced columns of a foreign key constraint statement
* @return string
*/
protected function _convertConstraintColumns($references): string
{
if (is_string($references)) {
return $this->_driver->quoteIdentifier($references);
}
return implode(', ', array_map(
[$this->_driver, 'quoteIdentifier'],
$references
));
}
/**
* Tries to use a matching database type to generate the SQL
* fragment for a single column in a table.
*
* @param string $columnType The column type.
* @param \Cake\Database\Schema\TableSchemaInterface $schema The table schema instance the column is in.
* @param string $column The name of the column.
* @return string|null An SQL fragment, or `null` in case no corresponding type was found or the type didn't provide
* custom column SQL.
*/
protected function _getTypeSpecificColumnSql(
string $columnType,
TableSchemaInterface $schema,
string $column
): ?string {
if (!TypeFactory::getMap($columnType)) {
return null;
}
$type = TypeFactory::build($columnType);
if (!($type instanceof ColumnSchemaAwareInterface)) {
return null;
}
return $type->getColumnSql($schema, $column, $this->_driver);
}
/**
* Tries to use a matching database type to convert a SQL column
* definition to an abstract type definition.
*
* @param string $columnType The column type.
* @param array $definition The column definition.
* @return array|null Array of column information, or `null` in case no corresponding type was found or the type
* didn't provide custom column information.
*/
protected function _applyTypeSpecificColumnConversion(string $columnType, array $definition): ?array
{
if (!TypeFactory::getMap($columnType)) {
return null;
}
$type = TypeFactory::build($columnType);
if (!($type instanceof ColumnSchemaAwareInterface)) {
return null;
}
return $type->convertColumnDefinition($definition, $this->_driver);
}
/**
* Generate the SQL to drop a table.
*
* @param \Cake\Database\Schema\TableSchema $schema Schema instance
* @return array SQL statements to drop a table.
*/
public function dropTableSql(TableSchema $schema): array
{
$sql = sprintf(
'DROP TABLE %s',
$this->_driver->quoteIdentifier($schema->name())
);
return [$sql];
}
/**
* Generate the SQL to list the tables.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array An array of (sql, params) to execute.
*/
abstract public function listTablesSql(array $config): array;
/**
* Generate the SQL to describe a table.
*
* @param string $tableName The table name to get information on.
* @param array<string, mixed> $config The connection configuration.
* @return array An array of (sql, params) to execute.
*/
abstract public function describeColumnSql(string $tableName, array $config): array;
/**
* Generate the SQL to describe the indexes in a table.
*
* @param string $tableName The table name to get information on.
* @param array<string, mixed> $config The connection configuration.
* @return array An array of (sql, params) to execute.
*/
abstract public function describeIndexSql(string $tableName, array $config): array;
/**
* Generate the SQL to describe the foreign keys in a table.
*
* @param string $tableName The table name to get information on.
* @param array<string, mixed> $config The connection configuration.
* @return array An array of (sql, params) to execute.
*/
abstract public function describeForeignKeySql(string $tableName, array $config): array;
/**
* Generate the SQL to describe table options
*
* @param string $tableName Table name.
* @param array<string, mixed> $config The connection configuration.
* @return array SQL statements to get options for a table.
*/
public function describeOptionsSql(string $tableName, array $config): array
{
return ['', ''];
}
/**
* Convert field description results into abstract schema fields.
*
* @param \Cake\Database\Schema\TableSchema $schema The table object to append fields to.
* @param array $row The row data from `describeColumnSql`.
* @return void
*/
abstract public function convertColumnDescription(TableSchema $schema, array $row): void;
/**
* Convert an index description results into abstract schema indexes or constraints.
*
* @param \Cake\Database\Schema\TableSchema $schema The table object to append
* an index or constraint to.
* @param array $row The row data from `describeIndexSql`.
* @return void
*/
abstract public function convertIndexDescription(TableSchema $schema, array $row): void;
/**
* Convert a foreign key description into constraints on the Table object.
*
* @param \Cake\Database\Schema\TableSchema $schema The table object to append
* a constraint to.
* @param array $row The row data from `describeForeignKeySql`.
* @return void
*/
abstract public function convertForeignKeyDescription(TableSchema $schema, array $row): void;
/**
* Convert options data into table options.
*
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
* @param array $row The row of data.
* @return void
*/
public function convertOptionsDescription(TableSchema $schema, array $row): void
{
}
/**
* Generate the SQL to create a table.
*
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
* @param array<string> $columns The columns to go inside the table.
* @param array<string> $constraints The constraints for the table.
* @param array<string> $indexes The indexes for the table.
* @return array<string> SQL statements to create a table.
*/
abstract public function createTableSql(
TableSchema $schema,
array $columns,
array $constraints,
array $indexes
): array;
/**
* Generate the SQL fragment for a single column in a table.
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
* @param string $name The name of the column.
* @return string SQL fragment.
*/
abstract public function columnSql(TableSchema $schema, string $name): string;
/**
* Generate the SQL queries needed to add foreign key constraints to the table
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
* @return array SQL fragment.
*/
abstract public function addConstraintSql(TableSchema $schema): array;
/**
* Generate the SQL queries needed to drop foreign key constraints from the table
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
* @return array SQL fragment.
*/
abstract public function dropConstraintSql(TableSchema $schema): array;
/**
* Generate the SQL fragments for defining table constraints.
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
* @param string $name The name of the column.
* @return string SQL fragment.
*/
abstract public function constraintSql(TableSchema $schema, string $name): string;
/**
* Generate the SQL fragment for a single index in a table.
*
* @param \Cake\Database\Schema\TableSchema $schema The table object the column is in.
* @param string $name The name of the column.
* @return string SQL fragment.
*/
abstract public function indexSql(TableSchema $schema, string $name): string;
/**
* Generate the SQL to truncate a table.
*
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
* @return array SQL statements to truncate a table.
*/
abstract public function truncateTableSql(TableSchema $schema): array;
}
// phpcs:disable
class_alias(
'Cake\Database\Schema\SchemaDialect',
'Cake\Database\Schema\BaseSchema'
);
// phpcs:enable
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Database\Connection;
/**
* An interface used by TableSchema objects.
*/
interface SqlGeneratorInterface
{
/**
* Generate the SQL to create the Table.
*
* Uses the connection to access the schema dialect
* to generate platform specific SQL.
*
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
* @return array List of SQL statements to create the table and the
* required indexes.
*/
public function createSql(Connection $connection): array;
/**
* Generate the SQL to drop a table.
*
* Uses the connection to access the schema dialect to generate platform
* specific SQL.
*
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
* @return array SQL to drop a table.
*/
public function dropSql(Connection $connection): array;
/**
* Generate the SQL statements to truncate a table
*
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
* @return array SQL to truncate a table.
*/
public function truncateSql(Connection $connection): array;
/**
* Generate the SQL statements to add the constraints to the table
*
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
* @return array SQL to add the constraints.
*/
public function addConstraintSql(Connection $connection): array;
/**
* Generate the SQL statements to drop the constraints to the table
*
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
* @return array SQL to drop a table.
*/
public function dropConstraintSql(Connection $connection): array;
}
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
use function Cake\Core\deprecationWarning;
deprecationWarning(
'Since 4.1.0: Cake\Database\Schema\SqliteSchema is deprecated. ' .
'Use Cake\Database\Schema\SqliteSchemaDialect instead.'
);
class_exists('Cake\Database\Schema\SqliteSchemaDialect');
+649
View File
@@ -0,0 +1,649 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Database\Exception\DatabaseException;
/**
* Schema management/reflection features for Sqlite
*
* @internal
*/
class SqliteSchemaDialect extends SchemaDialect
{
/**
* Array containing the foreign keys constraints names
* Necessary for composite foreign keys to be handled
*
* @var array<string, mixed>
*/
protected $_constraintsIdMap = [];
/**
* Whether there is any table in this connection to SQLite containing sequences.
*
* @var bool
*/
protected $_hasSequences;
/**
* Convert a column definition to the abstract types.
*
* The returned type will be a type that
* Cake\Database\TypeFactory can handle.
*
* @param string $column The column type + length
* @throws \Cake\Database\Exception\DatabaseException when unable to parse column type
* @return array<string, mixed> Array of column information.
*/
protected function _convertColumn(string $column): array
{
if ($column === '') {
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
}
preg_match('/(unsigned)?\s*([a-z]+)(?:\(([0-9,]+)\))?/i', $column, $matches);
if (empty($matches)) {
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
}
$unsigned = false;
if (strtolower($matches[1]) === 'unsigned') {
$unsigned = true;
}
$col = strtolower($matches[2]);
$length = $precision = $scale = null;
if (isset($matches[3])) {
$length = $matches[3];
if (strpos($length, ',') !== false) {
[$length, $precision] = explode(',', $length);
}
$length = (int)$length;
$precision = (int)$precision;
}
$type = $this->_applyTypeSpecificColumnConversion(
$col,
compact('length', 'precision', 'scale')
);
if ($type !== null) {
return $type;
}
if ($col === 'bigint') {
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => $length, 'unsigned' => $unsigned];
}
if ($col === 'smallint') {
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => $length, 'unsigned' => $unsigned];
}
if ($col === 'tinyint') {
return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => $length, 'unsigned' => $unsigned];
}
if (strpos($col, 'int') !== false) {
return ['type' => TableSchema::TYPE_INTEGER, 'length' => $length, 'unsigned' => $unsigned];
}
if (strpos($col, 'decimal') !== false) {
return [
'type' => TableSchema::TYPE_DECIMAL,
'length' => $length,
'precision' => $precision,
'unsigned' => $unsigned,
];
}
if (in_array($col, ['float', 'real', 'double'])) {
return [
'type' => TableSchema::TYPE_FLOAT,
'length' => $length,
'precision' => $precision,
'unsigned' => $unsigned,
];
}
if (strpos($col, 'boolean') !== false) {
return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null];
}
if (($col === 'char' && $length === 36) || $col === 'uuid') {
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
}
if ($col === 'char') {
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
}
if (strpos($col, 'char') !== false) {
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
}
if ($col === 'binary' && $length === 16) {
return ['type' => TableSchema::TYPE_BINARY_UUID, 'length' => null];
}
if (in_array($col, ['blob', 'clob', 'binary', 'varbinary'])) {
return ['type' => TableSchema::TYPE_BINARY, 'length' => $length];
}
$datetimeTypes = [
'date',
'time',
'timestamp',
'timestampfractional',
'timestamptimezone',
'datetime',
'datetimefractional',
];
if (in_array($col, $datetimeTypes)) {
return ['type' => $col, 'length' => null];
}
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
}
/**
* Generate the SQL to list the tables and views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array An array of (sql, params) to execute.
*/
public function listTablesSql(array $config): array
{
return [
'SELECT name FROM sqlite_master ' .
'WHERE (type="table" OR type="view") ' .
'AND name != "sqlite_sequence" ORDER BY name',
[],
];
}
/**
* Generate the SQL to list the tables, excluding all views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array<mixed> An array of (sql, params) to execute.
*/
public function listTablesWithoutViewsSql(array $config): array
{
return [
'SELECT name FROM sqlite_master WHERE type="table" ' .
'AND name != "sqlite_sequence" ORDER BY name',
[],
];
}
/**
* @inheritDoc
*/
public function describeColumnSql(string $tableName, array $config): array
{
$sql = sprintf(
'PRAGMA table_info(%s)',
$this->_driver->quoteIdentifier($tableName)
);
return [$sql, []];
}
/**
* @inheritDoc
*/
public function convertColumnDescription(TableSchema $schema, array $row): void
{
$field = $this->_convertColumn($row['type']);
$field += [
'null' => !$row['notnull'],
'default' => $this->_defaultValue($row['dflt_value']),
];
$primary = $schema->getConstraint('primary');
if ($row['pk'] && empty($primary)) {
$field['null'] = false;
$field['autoIncrement'] = true;
}
// SQLite does not support autoincrement on composite keys.
if ($row['pk'] && !empty($primary)) {
$existingColumn = $primary['columns'][0];
/** @psalm-suppress PossiblyNullOperand */
$schema->addColumn($existingColumn, ['autoIncrement' => null] + $schema->getColumn($existingColumn));
}
$schema->addColumn($row['name'], $field);
if ($row['pk']) {
$constraint = (array)$schema->getConstraint('primary') + [
'type' => TableSchema::CONSTRAINT_PRIMARY,
'columns' => [],
];
$constraint['columns'] = array_merge($constraint['columns'], [$row['name']]);
$schema->addConstraint('primary', $constraint);
}
}
/**
* Manipulate the default value.
*
* Sqlite includes quotes and bared NULLs in default values.
* We need to remove those.
*
* @param string|int|null $default The default value.
* @return string|int|null
*/
protected function _defaultValue($default)
{
if ($default === 'NULL' || $default === null) {
return null;
}
// Remove quotes
if (is_string($default) && preg_match("/^'(.*)'$/", $default, $matches)) {
return str_replace("''", "'", $matches[1]);
}
return $default;
}
/**
* @inheritDoc
*/
public function describeIndexSql(string $tableName, array $config): array
{
$sql = sprintf(
'PRAGMA index_list(%s)',
$this->_driver->quoteIdentifier($tableName)
);
return [$sql, []];
}
/**
* {@inheritDoc}
*
* Since SQLite does not have a way to get metadata about all indexes at once,
* additional queries are done here. Sqlite constraint names are not
* stable, and the names for constraints will not match those used to create
* the table. This is a limitation in Sqlite's metadata features.
*
* @param \Cake\Database\Schema\TableSchema $schema The table object to append
* an index or constraint to.
* @param array $row The row data from `describeIndexSql`.
* @return void
*/
public function convertIndexDescription(TableSchema $schema, array $row): void
{
$sql = sprintf(
'PRAGMA index_info(%s)',
$this->_driver->quoteIdentifier($row['name'])
);
$statement = $this->_driver->prepare($sql);
$statement->execute();
$columns = [];
/** @psalm-suppress PossiblyFalseIterator */
foreach ($statement->fetchAll('assoc') as $column) {
$columns[] = $column['name'];
}
$statement->closeCursor();
if ($row['unique']) {
$schema->addConstraint($row['name'], [
'type' => TableSchema::CONSTRAINT_UNIQUE,
'columns' => $columns,
]);
} else {
$schema->addIndex($row['name'], [
'type' => TableSchema::INDEX_INDEX,
'columns' => $columns,
]);
}
}
/**
* @inheritDoc
*/
public function describeForeignKeySql(string $tableName, array $config): array
{
$sql = sprintf('PRAGMA foreign_key_list(%s)', $this->_driver->quoteIdentifier($tableName));
return [$sql, []];
}
/**
* @inheritDoc
*/
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
{
$name = $row['from'] . '_fk';
$update = $row['on_update'] ?? '';
$delete = $row['on_delete'] ?? '';
$data = [
'type' => TableSchema::CONSTRAINT_FOREIGN,
'columns' => [$row['from']],
'references' => [$row['table'], $row['to']],
'update' => $this->_convertOnClause($update),
'delete' => $this->_convertOnClause($delete),
];
if (isset($this->_constraintsIdMap[$schema->name()][$row['id']])) {
$name = $this->_constraintsIdMap[$schema->name()][$row['id']];
} else {
$this->_constraintsIdMap[$schema->name()][$row['id']] = $name;
}
$schema->addConstraint($name, $data);
}
/**
* {@inheritDoc}
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
* @param string $name The name of the column.
* @return string SQL fragment.
* @throws \Cake\Database\Exception\DatabaseException when the column type is unknown
*/
public function columnSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getColumn($name);
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
if ($sql !== null) {
return $sql;
}
$typeMap = [
TableSchema::TYPE_BINARY_UUID => ' BINARY(16)',
TableSchema::TYPE_UUID => ' CHAR(36)',
TableSchema::TYPE_CHAR => ' CHAR',
TableSchema::TYPE_TINYINTEGER => ' TINYINT',
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
TableSchema::TYPE_INTEGER => ' INTEGER',
TableSchema::TYPE_BIGINTEGER => ' BIGINT',
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
TableSchema::TYPE_FLOAT => ' FLOAT',
TableSchema::TYPE_DECIMAL => ' DECIMAL',
TableSchema::TYPE_DATE => ' DATE',
TableSchema::TYPE_TIME => ' TIME',
TableSchema::TYPE_DATETIME => ' DATETIME',
TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIMEFRACTIONAL',
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMPFRACTIONAL',
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTIMEZONE',
TableSchema::TYPE_JSON => ' TEXT',
];
$out = $this->_driver->quoteIdentifier($name);
$hasUnsigned = [
TableSchema::TYPE_TINYINTEGER,
TableSchema::TYPE_SMALLINTEGER,
TableSchema::TYPE_INTEGER,
TableSchema::TYPE_BIGINTEGER,
TableSchema::TYPE_FLOAT,
TableSchema::TYPE_DECIMAL,
];
if (
in_array($data['type'], $hasUnsigned, true) &&
isset($data['unsigned']) &&
$data['unsigned'] === true
) {
if ($data['type'] !== TableSchema::TYPE_INTEGER || $schema->getPrimaryKey() !== [$name]) {
$out .= ' UNSIGNED';
}
}
if (isset($typeMap[$data['type']])) {
$out .= $typeMap[$data['type']];
}
if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
$out .= ' TEXT';
}
if ($data['type'] === TableSchema::TYPE_CHAR) {
$out .= '(' . $data['length'] . ')';
}
if (
$data['type'] === TableSchema::TYPE_STRING ||
(
$data['type'] === TableSchema::TYPE_TEXT &&
$data['length'] === TableSchema::LENGTH_TINY
)
) {
$out .= ' VARCHAR';
if (isset($data['length'])) {
$out .= '(' . $data['length'] . ')';
}
}
if ($data['type'] === TableSchema::TYPE_BINARY) {
if (isset($data['length'])) {
$out .= ' BLOB(' . $data['length'] . ')';
} else {
$out .= ' BLOB';
}
}
$integerTypes = [
TableSchema::TYPE_TINYINTEGER,
TableSchema::TYPE_SMALLINTEGER,
TableSchema::TYPE_INTEGER,
];
if (
in_array($data['type'], $integerTypes, true) &&
isset($data['length']) &&
$schema->getPrimaryKey() !== [$name]
) {
$out .= '(' . (int)$data['length'] . ')';
}
$hasPrecision = [TableSchema::TYPE_FLOAT, TableSchema::TYPE_DECIMAL];
if (
in_array($data['type'], $hasPrecision, true) &&
(
isset($data['length']) ||
isset($data['precision'])
)
) {
$out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
}
if (isset($data['null']) && $data['null'] === false) {
$out .= ' NOT NULL';
}
if ($data['type'] === TableSchema::TYPE_INTEGER && $schema->getPrimaryKey() === [$name]) {
$out .= ' PRIMARY KEY AUTOINCREMENT';
}
$timestampTypes = [
TableSchema::TYPE_DATETIME,
TableSchema::TYPE_DATETIME_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP,
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
];
if (isset($data['null']) && $data['null'] === true && in_array($data['type'], $timestampTypes, true)) {
$out .= ' DEFAULT NULL';
}
if (isset($data['default'])) {
$out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']);
}
return $out;
}
/**
* {@inheritDoc}
*
* Note integer primary keys will return ''. This is intentional as Sqlite requires
* that integer primary keys be defined in the column definition.
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
* @param string $name The name of the column.
* @return string SQL fragment.
*/
public function constraintSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getConstraint($name);
/** @psalm-suppress PossiblyNullArrayAccess */
if (
$data['type'] === TableSchema::CONSTRAINT_PRIMARY &&
count($data['columns']) === 1 &&
$schema->getColumn($data['columns'][0])['type'] === TableSchema::TYPE_INTEGER
) {
return '';
}
$clause = '';
$type = '';
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
$type = 'PRIMARY KEY';
}
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
$type = 'UNIQUE';
}
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$type = 'FOREIGN KEY';
$clause = sprintf(
' REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
$this->_driver->quoteIdentifier($data['references'][0]),
$this->_convertConstraintColumns($data['references'][1]),
$this->_foreignOnClause($data['update']),
$this->_foreignOnClause($data['delete'])
);
}
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
return sprintf(
'CONSTRAINT %s %s (%s)%s',
$this->_driver->quoteIdentifier($name),
$type,
implode(', ', $columns),
$clause
);
}
/**
* {@inheritDoc}
*
* SQLite can not properly handle adding a constraint to an existing table.
* This method is no-op
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
* @return array SQL fragment.
*/
public function addConstraintSql(TableSchema $schema): array
{
return [];
}
/**
* {@inheritDoc}
*
* SQLite can not properly handle dropping a constraint to an existing table.
* This method is no-op
*
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
* @return array SQL fragment.
*/
public function dropConstraintSql(TableSchema $schema): array
{
return [];
}
/**
* @inheritDoc
*/
public function indexSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getIndex($name);
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
return sprintf(
'CREATE INDEX %s ON %s (%s)',
$this->_driver->quoteIdentifier($name),
$this->_driver->quoteIdentifier($schema->name()),
implode(', ', $columns)
);
}
/**
* @inheritDoc
*/
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
{
$lines = array_merge($columns, $constraints);
$content = implode(",\n", array_filter($lines));
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
$table = sprintf("CREATE%sTABLE \"%s\" (\n%s\n)", $temporary, $schema->name(), $content);
$out = [$table];
foreach ($indexes as $index) {
$out[] = $index;
}
return $out;
}
/**
* @inheritDoc
*/
public function truncateTableSql(TableSchema $schema): array
{
$name = $schema->name();
$sql = [];
if ($this->hasSequences()) {
$sql[] = sprintf('DELETE FROM sqlite_sequence WHERE name="%s"', $name);
}
$sql[] = sprintf('DELETE FROM "%s"', $name);
return $sql;
}
/**
* Returns whether there is any table in this connection to SQLite containing
* sequences
*
* @return bool
*/
public function hasSequences(): bool
{
$result = $this->_driver->prepare(
'SELECT 1 FROM sqlite_master WHERE name = "sqlite_sequence"'
);
$result->execute();
$this->_hasSequences = (bool)$result->rowCount();
$result->closeCursor();
return $this->_hasSequences;
}
}
// phpcs:disable
class_alias(
'Cake\Database\Schema\SqliteSchemaDialect',
'Cake\Database\Schema\SqliteSchema'
);
// phpcs:enable
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
use function Cake\Core\deprecationWarning;
deprecationWarning(
'Since 4.1.0: Cake\Database\Schema\SqlserverSchema is deprecated. ' .
'Use Cake\Database\Schema\SqlServerSchemaDialect instead.'
);
class_exists('Cake\Database\Schema\SqlServerSchemaDialect');
@@ -0,0 +1,707 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
/**
* Schema management/reflection features for SQLServer.
*
* @internal
*/
class SqlserverSchemaDialect extends SchemaDialect
{
/**
* @var string
*/
public const DEFAULT_SCHEMA_NAME = 'dbo';
/**
* Generate the SQL to list the tables and views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array An array of (sql, params) to execute.
*/
public function listTablesSql(array $config): array
{
$sql = "SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = ?
AND (TABLE_TYPE = 'BASE TABLE' OR TABLE_TYPE = 'VIEW')
ORDER BY TABLE_NAME";
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
return [$sql, [$schema]];
}
/**
* Generate the SQL to list the tables, excluding all views.
*
* @param array<string, mixed> $config The connection configuration to use for
* getting tables from.
* @return array<mixed> An array of (sql, params) to execute.
*/
public function listTablesWithoutViewsSql(array $config): array
{
$sql = "SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = ?
AND (TABLE_TYPE = 'BASE TABLE')
ORDER BY TABLE_NAME";
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
return [$sql, [$schema]];
}
/**
* @inheritDoc
*/
public function describeColumnSql(string $tableName, array $config): array
{
$sql = 'SELECT DISTINCT
AC.column_id AS [column_id],
AC.name AS [name],
TY.name AS [type],
AC.max_length AS [char_length],
AC.precision AS [precision],
AC.scale AS [scale],
AC.is_identity AS [autoincrement],
AC.is_nullable AS [null],
OBJECT_DEFINITION(AC.default_object_id) AS [default],
AC.collation_name AS [collation_name]
FROM sys.[objects] T
INNER JOIN sys.[schemas] S ON S.[schema_id] = T.[schema_id]
INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id]
INNER JOIN sys.[types] TY ON TY.[user_type_id] = AC.[user_type_id]
WHERE T.[name] = ? AND S.[name] = ?
ORDER BY column_id';
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
return [$sql, [$tableName, $schema]];
}
/**
* Convert a column definition to the abstract types.
*
* The returned type will be a type that
* Cake\Database\TypeFactory can handle.
*
* @param string $col The column type
* @param int|null $length the column length
* @param int|null $precision The column precision
* @param int|null $scale The column scale
* @return array<string, mixed> Array of column information.
* @link https://technet.microsoft.com/en-us/library/ms187752.aspx
*/
protected function _convertColumn(
string $col,
?int $length = null,
?int $precision = null,
?int $scale = null
): array {
$col = strtolower($col);
$type = $this->_applyTypeSpecificColumnConversion(
$col,
compact('length', 'precision', 'scale')
);
if ($type !== null) {
return $type;
}
if (in_array($col, ['date', 'time'])) {
return ['type' => $col, 'length' => null];
}
if ($col === 'datetime') {
// datetime cannot parse more than 3 digits of precision and isn't accurate
return ['type' => TableSchema::TYPE_DATETIME, 'length' => null];
}
if (strpos($col, 'datetime') !== false) {
$typeName = TableSchema::TYPE_DATETIME;
if ($scale > 0) {
$typeName = TableSchema::TYPE_DATETIME_FRACTIONAL;
}
return ['type' => $typeName, 'length' => null, 'precision' => $scale];
}
if ($col === 'char') {
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
}
if ($col === 'tinyint') {
return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => $precision ?: 3];
}
if ($col === 'smallint') {
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => $precision ?: 5];
}
if ($col === 'int' || $col === 'integer') {
return ['type' => TableSchema::TYPE_INTEGER, 'length' => $precision ?: 10];
}
if ($col === 'bigint') {
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => $precision ?: 20];
}
if ($col === 'bit') {
return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null];
}
if (
strpos($col, 'numeric') !== false ||
strpos($col, 'money') !== false ||
strpos($col, 'decimal') !== false
) {
return ['type' => TableSchema::TYPE_DECIMAL, 'length' => $precision, 'precision' => $scale];
}
if ($col === 'real' || $col === 'float') {
return ['type' => TableSchema::TYPE_FLOAT, 'length' => null];
}
// SqlServer schema reflection returns double length for unicode
// columns because internally it uses UTF16/UCS2
if ($col === 'nvarchar' || $col === 'nchar' || $col === 'ntext') {
$length /= 2;
}
if (strpos($col, 'varchar') !== false && $length < 0) {
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
}
if (strpos($col, 'varchar') !== false) {
return ['type' => TableSchema::TYPE_STRING, 'length' => $length ?: 255];
}
if (strpos($col, 'char') !== false) {
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
}
if (strpos($col, 'text') !== false) {
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
}
if ($col === 'image' || strpos($col, 'binary') !== false) {
// -1 is the value for MAX which we treat as a 'long' binary
if ($length == -1) {
$length = TableSchema::LENGTH_LONG;
}
return ['type' => TableSchema::TYPE_BINARY, 'length' => $length];
}
if ($col === 'uniqueidentifier') {
return ['type' => TableSchema::TYPE_UUID];
}
return ['type' => TableSchema::TYPE_STRING, 'length' => null];
}
/**
* @inheritDoc
*/
public function convertColumnDescription(TableSchema $schema, array $row): void
{
$field = $this->_convertColumn(
$row['type'],
$row['char_length'] !== null ? (int)$row['char_length'] : null,
$row['precision'] !== null ? (int)$row['precision'] : null,
$row['scale'] !== null ? (int)$row['scale'] : null
);
if (!empty($row['autoincrement'])) {
$field['autoIncrement'] = true;
}
$field += [
'null' => $row['null'] === '1',
'default' => $this->_defaultValue($field['type'], $row['default']),
'collate' => $row['collation_name'],
];
$schema->addColumn($row['name'], $field);
}
/**
* Manipulate the default value.
*
* Removes () wrapping default values, extracts strings from
* N'' wrappers and collation text and converts NULL strings.
*
* @param string $type The schema type
* @param string|null $default The default value.
* @return string|int|null
*/
protected function _defaultValue($type, $default)
{
if ($default === null) {
return null;
}
// remove () surrounding value (NULL) but leave () at the end of functions
// integers might have two ((0)) wrapping value
if (preg_match('/^\(+(.*?(\(\))?)\)+$/', $default, $matches)) {
$default = $matches[1];
}
if ($default === 'NULL') {
return null;
}
if ($type === TableSchema::TYPE_BOOLEAN) {
return (int)$default;
}
// Remove quotes
if (preg_match("/^\(?N?'(.*)'\)?/", $default, $matches)) {
return str_replace("''", "'", $matches[1]);
}
return $default;
}
/**
* @inheritDoc
*/
public function describeIndexSql(string $tableName, array $config): array
{
$sql = "SELECT
I.[name] AS [index_name],
IC.[index_column_id] AS [index_order],
AC.[name] AS [column_name],
I.[is_unique], I.[is_primary_key],
I.[is_unique_constraint]
FROM sys.[tables] AS T
INNER JOIN sys.[schemas] S ON S.[schema_id] = T.[schema_id]
INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] AND I.[index_id] = IC.[index_id]
INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = ? AND S.[name] = ?
ORDER BY I.[index_id], IC.[index_column_id]";
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
return [$sql, [$tableName, $schema]];
}
/**
* @inheritDoc
*/
public function convertIndexDescription(TableSchema $schema, array $row): void
{
$type = TableSchema::INDEX_INDEX;
$name = $row['index_name'];
if ($row['is_primary_key']) {
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
}
if ($row['is_unique_constraint'] && $type === TableSchema::INDEX_INDEX) {
$type = TableSchema::CONSTRAINT_UNIQUE;
}
if ($type === TableSchema::INDEX_INDEX) {
$existing = $schema->getIndex($name);
} else {
$existing = $schema->getConstraint($name);
}
$columns = [$row['column_name']];
if (!empty($existing)) {
$columns = array_merge($existing['columns'], $columns);
}
if ($type === TableSchema::CONSTRAINT_PRIMARY || $type === TableSchema::CONSTRAINT_UNIQUE) {
$schema->addConstraint($name, [
'type' => $type,
'columns' => $columns,
]);
return;
}
$schema->addIndex($name, [
'type' => $type,
'columns' => $columns,
]);
}
/**
* @inheritDoc
*/
public function describeForeignKeySql(string $tableName, array $config): array
{
// phpcs:disable Generic.Files.LineLength
$sql = 'SELECT FK.[name] AS [foreign_key_name], FK.[delete_referential_action_desc] AS [delete_type],
FK.[update_referential_action_desc] AS [update_type], C.name AS [column], RT.name AS [reference_table],
RC.name AS [reference_column]
FROM sys.foreign_keys FK
INNER JOIN sys.foreign_key_columns FKC ON FKC.constraint_object_id = FK.object_id
INNER JOIN sys.tables T ON T.object_id = FKC.parent_object_id
INNER JOIN sys.tables RT ON RT.object_id = FKC.referenced_object_id
INNER JOIN sys.schemas S ON S.schema_id = T.schema_id AND S.schema_id = RT.schema_id
INNER JOIN sys.columns C ON C.column_id = FKC.parent_column_id AND C.object_id = FKC.parent_object_id
INNER JOIN sys.columns RC ON RC.column_id = FKC.referenced_column_id AND RC.object_id = FKC.referenced_object_id
WHERE FK.is_ms_shipped = 0 AND T.name = ? AND S.name = ?
ORDER BY FKC.constraint_column_id';
// phpcs:enable Generic.Files.LineLength
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
return [$sql, [$tableName, $schema]];
}
/**
* @inheritDoc
*/
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
{
$data = [
'type' => TableSchema::CONSTRAINT_FOREIGN,
'columns' => [$row['column']],
'references' => [$row['reference_table'], $row['reference_column']],
'update' => $this->_convertOnClause($row['update_type']),
'delete' => $this->_convertOnClause($row['delete_type']),
];
$name = $row['foreign_key_name'];
$schema->addConstraint($name, $data);
}
/**
* @inheritDoc
*/
protected function _foreignOnClause(string $on): string
{
$parent = parent::_foreignOnClause($on);
return $parent === 'RESTRICT' ? parent::_foreignOnClause(TableSchema::ACTION_NO_ACTION) : $parent;
}
/**
* @inheritDoc
*/
protected function _convertOnClause(string $clause): string
{
switch ($clause) {
case 'NO_ACTION':
return TableSchema::ACTION_NO_ACTION;
case 'CASCADE':
return TableSchema::ACTION_CASCADE;
case 'SET_NULL':
return TableSchema::ACTION_SET_NULL;
case 'SET_DEFAULT':
return TableSchema::ACTION_SET_DEFAULT;
}
return TableSchema::ACTION_SET_NULL;
}
/**
* @inheritDoc
*/
public function columnSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getColumn($name);
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
if ($sql !== null) {
return $sql;
}
$out = $this->_driver->quoteIdentifier($name);
$typeMap = [
TableSchema::TYPE_TINYINTEGER => ' TINYINT',
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
TableSchema::TYPE_INTEGER => ' INTEGER',
TableSchema::TYPE_BIGINTEGER => ' BIGINT',
TableSchema::TYPE_BINARY_UUID => ' UNIQUEIDENTIFIER',
TableSchema::TYPE_BOOLEAN => ' BIT',
TableSchema::TYPE_CHAR => ' NCHAR',
TableSchema::TYPE_FLOAT => ' FLOAT',
TableSchema::TYPE_DECIMAL => ' DECIMAL',
TableSchema::TYPE_DATE => ' DATE',
TableSchema::TYPE_TIME => ' TIME',
TableSchema::TYPE_DATETIME => ' DATETIME2',
TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIME2',
TableSchema::TYPE_TIMESTAMP => ' DATETIME2',
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' DATETIME2',
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' DATETIME2',
TableSchema::TYPE_UUID => ' UNIQUEIDENTIFIER',
TableSchema::TYPE_JSON => ' NVARCHAR(MAX)',
];
if (isset($typeMap[$data['type']])) {
$out .= $typeMap[$data['type']];
}
if ($data['type'] === TableSchema::TYPE_INTEGER || $data['type'] === TableSchema::TYPE_BIGINTEGER) {
if ($schema->getPrimaryKey() === [$name] || $data['autoIncrement'] === true) {
unset($data['null'], $data['default']);
$out .= ' IDENTITY(1, 1)';
}
}
if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
$out .= ' NVARCHAR(MAX)';
}
if ($data['type'] === TableSchema::TYPE_CHAR) {
$out .= '(' . $data['length'] . ')';
}
if ($data['type'] === TableSchema::TYPE_BINARY) {
if (
!isset($data['length'])
|| in_array($data['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true)
) {
$data['length'] = 'MAX';
}
if ($data['length'] === 1) {
$out .= ' BINARY(1)';
} else {
$out .= ' VARBINARY';
$out .= sprintf('(%s)', $data['length']);
}
}
if (
$data['type'] === TableSchema::TYPE_STRING ||
(
$data['type'] === TableSchema::TYPE_TEXT &&
$data['length'] === TableSchema::LENGTH_TINY
)
) {
$type = ' NVARCHAR';
$length = $data['length'] ?? TableSchema::LENGTH_TINY;
$out .= sprintf('%s(%d)', $type, $length);
}
$hasCollate = [TableSchema::TYPE_TEXT, TableSchema::TYPE_STRING, TableSchema::TYPE_CHAR];
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
$out .= ' COLLATE ' . $data['collate'];
}
$precisionTypes = [
TableSchema::TYPE_FLOAT,
TableSchema::TYPE_DATETIME,
TableSchema::TYPE_DATETIME_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP,
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
];
if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) {
$out .= '(' . (int)$data['precision'] . ')';
}
if (
$data['type'] === TableSchema::TYPE_DECIMAL &&
(
isset($data['length']) ||
isset($data['precision'])
)
) {
$out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
}
if (isset($data['null']) && $data['null'] === false) {
$out .= ' NOT NULL';
}
$dateTimeTypes = [
TableSchema::TYPE_DATETIME,
TableSchema::TYPE_DATETIME_FRACTIONAL,
TableSchema::TYPE_TIMESTAMP,
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
];
$dateTimeDefaults = [
'current_timestamp',
'getdate()',
'getutcdate()',
'sysdatetime()',
'sysutcdatetime()',
'sysdatetimeoffset()',
];
if (
isset($data['default']) &&
in_array($data['type'], $dateTimeTypes, true) &&
in_array(strtolower($data['default']), $dateTimeDefaults, true)
) {
$out .= ' DEFAULT ' . strtoupper($data['default']);
} elseif (isset($data['default'])) {
$default = is_bool($data['default'])
? (int)$data['default']
: $this->_driver->schemaValue($data['default']);
$out .= ' DEFAULT ' . $default;
} elseif (isset($data['null']) && $data['null'] !== false) {
$out .= ' DEFAULT NULL';
}
return $out;
}
/**
* @inheritDoc
*/
public function addConstraintSql(TableSchema $schema): array
{
$sqlPattern = 'ALTER TABLE %s ADD %s;';
$sql = [];
foreach ($schema->constraints() as $name) {
/** @var array $constraint */
$constraint = $schema->getConstraint($name);
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$tableName = $this->_driver->quoteIdentifier($schema->name());
$sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name));
}
}
return $sql;
}
/**
* @inheritDoc
*/
public function dropConstraintSql(TableSchema $schema): array
{
$sqlPattern = 'ALTER TABLE %s DROP CONSTRAINT %s;';
$sql = [];
foreach ($schema->constraints() as $name) {
/** @var array $constraint */
$constraint = $schema->getConstraint($name);
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
$tableName = $this->_driver->quoteIdentifier($schema->name());
$constraintName = $this->_driver->quoteIdentifier($name);
$sql[] = sprintf($sqlPattern, $tableName, $constraintName);
}
}
return $sql;
}
/**
* @inheritDoc
*/
public function indexSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getIndex($name);
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
return sprintf(
'CREATE INDEX %s ON %s (%s)',
$this->_driver->quoteIdentifier($name),
$this->_driver->quoteIdentifier($schema->name()),
implode(', ', $columns)
);
}
/**
* @inheritDoc
*/
public function constraintSql(TableSchema $schema, string $name): string
{
/** @var array $data */
$data = $schema->getConstraint($name);
$out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name);
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
$out = 'PRIMARY KEY';
}
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
$out .= ' UNIQUE';
}
return $this->_keySql($out, $data);
}
/**
* Helper method for generating key SQL snippets.
*
* @param string $prefix The key prefix
* @param array $data Key data.
* @return string
*/
protected function _keySql(string $prefix, array $data): string
{
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
return $prefix . sprintf(
' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
implode(', ', $columns),
$this->_driver->quoteIdentifier($data['references'][0]),
$this->_convertConstraintColumns($data['references'][1]),
$this->_foreignOnClause($data['update']),
$this->_foreignOnClause($data['delete'])
);
}
return $prefix . ' (' . implode(', ', $columns) . ')';
}
/**
* @inheritDoc
*/
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
{
$content = array_merge($columns, $constraints);
$content = implode(",\n", array_filter($content));
$tableName = $this->_driver->quoteIdentifier($schema->name());
$out = [];
$out[] = sprintf("CREATE TABLE %s (\n%s\n)", $tableName, $content);
foreach ($indexes as $index) {
$out[] = $index;
}
return $out;
}
/**
* @inheritDoc
*/
public function truncateTableSql(TableSchema $schema): array
{
$name = $this->_driver->quoteIdentifier($schema->name());
$queries = [
sprintf('DELETE FROM %s', $name),
];
// Restart identity sequences
$pk = $schema->getPrimaryKey();
if (count($pk) === 1) {
/** @var array $column */
$column = $schema->getColumn($pk[0]);
if (in_array($column['type'], ['integer', 'biginteger'])) {
$queries[] = sprintf(
"IF EXISTS (SELECT * FROM sys.identity_columns WHERE OBJECT_NAME(OBJECT_ID) = '%s' AND " .
"last_value IS NOT NULL) DBCC CHECKIDENT('%s', RESEED, 0)",
$schema->name(),
$schema->name()
);
}
}
return $queries;
}
}
// phpcs:disable
class_alias(
'Cake\Database\Schema\SqlserverSchemaDialect',
'Cake\Database\Schema\SqlserverSchema'
);
// phpcs:enable
+793
View File
@@ -0,0 +1,793 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Database\Connection;
use Cake\Database\Exception\DatabaseException;
use Cake\Database\TypeFactory;
use function Cake\Core\deprecationWarning;
/**
* Represents a single table in a database schema.
*
* Can either be populated using the reflection API's
* or by incrementally building an instance using
* methods.
*
* Once created TableSchema instances can be added to
* Schema\Collection objects. They can also be converted into SQL using the
* createSql(), dropSql() and truncateSql() methods.
*/
class TableSchema implements TableSchemaInterface, SqlGeneratorInterface
{
/**
* The name of the table
*
* @var string
*/
protected $_table;
/**
* Columns in the table.
*
* @var array<string, array>
*/
protected $_columns = [];
/**
* A map with columns to types
*
* @var array<string, string>
*/
protected $_typeMap = [];
/**
* Indexes in the table.
*
* @var array<string, array>
*/
protected $_indexes = [];
/**
* Constraints in the table.
*
* @var array<string, array<string, mixed>>
*/
protected $_constraints = [];
/**
* Options for the table.
*
* @var array<string, mixed>
*/
protected $_options = [];
/**
* Whether the table is temporary
*
* @var bool
*/
protected $_temporary = false;
/**
* Column length when using a `tiny` column type
*
* @var int
*/
public const LENGTH_TINY = 255;
/**
* Column length when using a `medium` column type
*
* @var int
*/
public const LENGTH_MEDIUM = 16777215;
/**
* Column length when using a `long` column type
*
* @var int
*/
public const LENGTH_LONG = 4294967295;
/**
* Valid column length that can be used with text type columns
*
* @var array<string, int>
*/
public static $columnLengths = [
'tiny' => self::LENGTH_TINY,
'medium' => self::LENGTH_MEDIUM,
'long' => self::LENGTH_LONG,
];
/**
* The valid keys that can be used in a column
* definition.
*
* @var array<string, mixed>
*/
protected static $_columnKeys = [
'type' => null,
'baseType' => null,
'length' => null,
'precision' => null,
'null' => null,
'default' => null,
'comment' => null,
];
/**
* Additional type specific properties.
*
* @var array<string, array<string, mixed>>
*/
protected static $_columnExtras = [
'string' => [
'collate' => null,
],
'char' => [
'collate' => null,
],
'text' => [
'collate' => null,
],
'tinyinteger' => [
'unsigned' => null,
],
'smallinteger' => [
'unsigned' => null,
],
'integer' => [
'unsigned' => null,
'autoIncrement' => null,
],
'biginteger' => [
'unsigned' => null,
'autoIncrement' => null,
],
'decimal' => [
'unsigned' => null,
],
'float' => [
'unsigned' => null,
],
];
/**
* The valid keys that can be used in an index
* definition.
*
* @var array<string, mixed>
*/
protected static $_indexKeys = [
'type' => null,
'columns' => [],
'length' => [],
'references' => [],
'update' => 'restrict',
'delete' => 'restrict',
];
/**
* Names of the valid index types.
*
* @var array<string>
*/
protected static $_validIndexTypes = [
self::INDEX_INDEX,
self::INDEX_FULLTEXT,
];
/**
* Names of the valid constraint types.
*
* @var array<string>
*/
protected static $_validConstraintTypes = [
self::CONSTRAINT_PRIMARY,
self::CONSTRAINT_UNIQUE,
self::CONSTRAINT_FOREIGN,
];
/**
* Names of the valid foreign key actions.
*
* @var array<string>
*/
protected static $_validForeignKeyActions = [
self::ACTION_CASCADE,
self::ACTION_SET_NULL,
self::ACTION_SET_DEFAULT,
self::ACTION_NO_ACTION,
self::ACTION_RESTRICT,
];
/**
* Primary constraint type
*
* @var string
*/
public const CONSTRAINT_PRIMARY = 'primary';
/**
* Unique constraint type
*
* @var string
*/
public const CONSTRAINT_UNIQUE = 'unique';
/**
* Foreign constraint type
*
* @var string
*/
public const CONSTRAINT_FOREIGN = 'foreign';
/**
* Index - index type
*
* @var string
*/
public const INDEX_INDEX = 'index';
/**
* Fulltext index type
*
* @var string
*/
public const INDEX_FULLTEXT = 'fulltext';
/**
* Foreign key cascade action
*
* @var string
*/
public const ACTION_CASCADE = 'cascade';
/**
* Foreign key set null action
*
* @var string
*/
public const ACTION_SET_NULL = 'setNull';
/**
* Foreign key no action
*
* @var string
*/
public const ACTION_NO_ACTION = 'noAction';
/**
* Foreign key restrict action
*
* @var string
*/
public const ACTION_RESTRICT = 'restrict';
/**
* Foreign key restrict default
*
* @var string
*/
public const ACTION_SET_DEFAULT = 'setDefault';
/**
* Constructor.
*
* @param string $table The table name.
* @param array<string, array|string> $columns The list of columns for the schema.
*/
public function __construct(string $table, array $columns = [])
{
$this->_table = $table;
foreach ($columns as $field => $definition) {
$this->addColumn($field, $definition);
}
}
/**
* @inheritDoc
*/
public function name(): string
{
return $this->_table;
}
/**
* @inheritDoc
*/
public function addColumn(string $name, $attrs)
{
if (is_string($attrs)) {
$attrs = ['type' => $attrs];
}
$valid = static::$_columnKeys;
if (isset(static::$_columnExtras[$attrs['type']])) {
$valid += static::$_columnExtras[$attrs['type']];
}
$attrs = array_intersect_key($attrs, $valid);
$this->_columns[$name] = $attrs + $valid;
$this->_typeMap[$name] = $this->_columns[$name]['type'];
return $this;
}
/**
* @inheritDoc
*/
public function removeColumn(string $name)
{
unset($this->_columns[$name], $this->_typeMap[$name]);
return $this;
}
/**
* @inheritDoc
*/
public function columns(): array
{
return array_keys($this->_columns);
}
/**
* @inheritDoc
*/
public function getColumn(string $name): ?array
{
if (!isset($this->_columns[$name])) {
return null;
}
$column = $this->_columns[$name];
unset($column['baseType']);
return $column;
}
/**
* @inheritDoc
*/
public function getColumnType(string $name): ?string
{
if (!isset($this->_columns[$name])) {
return null;
}
return $this->_columns[$name]['type'];
}
/**
* @inheritDoc
*/
public function setColumnType(string $name, string $type)
{
if (!isset($this->_columns[$name])) {
return $this;
}
$this->_columns[$name]['type'] = $type;
$this->_typeMap[$name] = $type;
return $this;
}
/**
* @inheritDoc
*/
public function hasColumn(string $name): bool
{
return isset($this->_columns[$name]);
}
/**
* @inheritDoc
*/
public function baseColumnType(string $column): ?string
{
if (isset($this->_columns[$column]['baseType'])) {
return $this->_columns[$column]['baseType'];
}
$type = $this->getColumnType($column);
if ($type === null) {
return null;
}
if (TypeFactory::getMap($type)) {
$type = TypeFactory::build($type)->getBaseType();
}
return $this->_columns[$column]['baseType'] = $type;
}
/**
* @inheritDoc
*/
public function typeMap(): array
{
return $this->_typeMap;
}
/**
* @inheritDoc
*/
public function isNullable(string $name): bool
{
if (!isset($this->_columns[$name])) {
return true;
}
return $this->_columns[$name]['null'] === true;
}
/**
* @inheritDoc
*/
public function defaultValues(): array
{
$defaults = [];
foreach ($this->_columns as $name => $data) {
if (!array_key_exists('default', $data)) {
continue;
}
if ($data['default'] === null && $data['null'] !== true) {
continue;
}
$defaults[$name] = $data['default'];
}
return $defaults;
}
/**
* @inheritDoc
*/
public function addIndex(string $name, $attrs)
{
if (is_string($attrs)) {
$attrs = ['type' => $attrs];
}
$attrs = array_intersect_key($attrs, static::$_indexKeys);
$attrs += static::$_indexKeys;
unset($attrs['references'], $attrs['update'], $attrs['delete']);
if (!in_array($attrs['type'], static::$_validIndexTypes, true)) {
throw new DatabaseException(sprintf(
'Invalid index type "%s" in index "%s" in table "%s".',
$attrs['type'],
$name,
$this->_table
));
}
$attrs['columns'] = (array)$attrs['columns'];
foreach ($attrs['columns'] as $field) {
if (empty($this->_columns[$field])) {
$msg = sprintf(
'Columns used in index "%s" in table "%s" must be added to the Table schema first. ' .
'The column "%s" was not found.',
$name,
$this->_table,
$field
);
throw new DatabaseException($msg);
}
}
$this->_indexes[$name] = $attrs;
return $this;
}
/**
* @inheritDoc
*/
public function indexes(): array
{
return array_keys($this->_indexes);
}
/**
* @inheritDoc
*/
public function getIndex(string $name): ?array
{
if (!isset($this->_indexes[$name])) {
return null;
}
return $this->_indexes[$name];
}
/**
* Get the column(s) used for the primary key.
*
* @return array Column name(s) for the primary key. An
* empty list will be returned when the table has no primary key.
* @deprecated 4.0.0 Renamed to {@link getPrimaryKey()}.
*/
public function primaryKey(): array
{
deprecationWarning('`TableSchema::primaryKey()` is deprecated. Use `TableSchema::getPrimaryKey()`.');
return $this->getPrimarykey();
}
/**
* @inheritDoc
*/
public function getPrimaryKey(): array
{
foreach ($this->_constraints as $data) {
if ($data['type'] === static::CONSTRAINT_PRIMARY) {
return $data['columns'];
}
}
return [];
}
/**
* @inheritDoc
*/
public function addConstraint(string $name, $attrs)
{
if (is_string($attrs)) {
$attrs = ['type' => $attrs];
}
$attrs = array_intersect_key($attrs, static::$_indexKeys);
$attrs += static::$_indexKeys;
if (!in_array($attrs['type'], static::$_validConstraintTypes, true)) {
throw new DatabaseException(sprintf(
'Invalid constraint type "%s" in table "%s".',
$attrs['type'],
$this->_table
));
}
if (empty($attrs['columns'])) {
throw new DatabaseException(sprintf(
'Constraints in table "%s" must have at least one column.',
$this->_table
));
}
$attrs['columns'] = (array)$attrs['columns'];
foreach ($attrs['columns'] as $field) {
if (empty($this->_columns[$field])) {
$msg = sprintf(
'Columns used in constraints must be added to the Table schema first. ' .
'The column "%s" was not found in table "%s".',
$field,
$this->_table
);
throw new DatabaseException($msg);
}
}
if ($attrs['type'] === static::CONSTRAINT_FOREIGN) {
$attrs = $this->_checkForeignKey($attrs);
if (isset($this->_constraints[$name])) {
$this->_constraints[$name]['columns'] = array_unique(array_merge(
$this->_constraints[$name]['columns'],
$attrs['columns']
));
if (isset($this->_constraints[$name]['references'])) {
$this->_constraints[$name]['references'][1] = array_unique(array_merge(
(array)$this->_constraints[$name]['references'][1],
[$attrs['references'][1]]
));
}
return $this;
}
} else {
unset($attrs['references'], $attrs['update'], $attrs['delete']);
}
$this->_constraints[$name] = $attrs;
return $this;
}
/**
* @inheritDoc
*/
public function dropConstraint(string $name)
{
if (isset($this->_constraints[$name])) {
unset($this->_constraints[$name]);
}
return $this;
}
/**
* Check whether a table has an autoIncrement column defined.
*
* @return bool
*/
public function hasAutoincrement(): bool
{
foreach ($this->_columns as $column) {
if (isset($column['autoIncrement']) && $column['autoIncrement']) {
return true;
}
}
return false;
}
/**
* Helper method to check/validate foreign keys.
*
* @param array<string, mixed> $attrs Attributes to set.
* @return array<string, mixed>
* @throws \Cake\Database\Exception\DatabaseException When foreign key definition is not valid.
*/
protected function _checkForeignKey(array $attrs): array
{
if (count($attrs['references']) < 2) {
throw new DatabaseException('References must contain a table and column.');
}
if (!in_array($attrs['update'], static::$_validForeignKeyActions)) {
throw new DatabaseException(sprintf(
'Update action is invalid. Must be one of %s',
implode(',', static::$_validForeignKeyActions)
));
}
if (!in_array($attrs['delete'], static::$_validForeignKeyActions)) {
throw new DatabaseException(sprintf(
'Delete action is invalid. Must be one of %s',
implode(',', static::$_validForeignKeyActions)
));
}
return $attrs;
}
/**
* @inheritDoc
*/
public function constraints(): array
{
return array_keys($this->_constraints);
}
/**
* @inheritDoc
*/
public function getConstraint(string $name): ?array
{
return $this->_constraints[$name] ?? null;
}
/**
* @inheritDoc
*/
public function setOptions(array $options)
{
$this->_options = $options + $this->_options;
return $this;
}
/**
* @inheritDoc
*/
public function getOptions(): array
{
return $this->_options;
}
/**
* @inheritDoc
*/
public function setTemporary(bool $temporary)
{
$this->_temporary = $temporary;
return $this;
}
/**
* @inheritDoc
*/
public function isTemporary(): bool
{
return $this->_temporary;
}
/**
* @inheritDoc
*/
public function createSql(Connection $connection): array
{
$dialect = $connection->getDriver()->schemaDialect();
$columns = $constraints = $indexes = [];
foreach (array_keys($this->_columns) as $name) {
$columns[] = $dialect->columnSql($this, $name);
}
foreach (array_keys($this->_constraints) as $name) {
$constraints[] = $dialect->constraintSql($this, $name);
}
foreach (array_keys($this->_indexes) as $name) {
$indexes[] = $dialect->indexSql($this, $name);
}
return $dialect->createTableSql($this, $columns, $constraints, $indexes);
}
/**
* @inheritDoc
*/
public function dropSql(Connection $connection): array
{
$dialect = $connection->getDriver()->schemaDialect();
return $dialect->dropTableSql($this);
}
/**
* @inheritDoc
*/
public function truncateSql(Connection $connection): array
{
$dialect = $connection->getDriver()->schemaDialect();
return $dialect->truncateTableSql($this);
}
/**
* @inheritDoc
*/
public function addConstraintSql(Connection $connection): array
{
$dialect = $connection->getDriver()->schemaDialect();
return $dialect->addConstraintSql($this);
}
/**
* @inheritDoc
*/
public function dropConstraintSql(Connection $connection): array
{
$dialect = $connection->getDriver()->schemaDialect();
return $dialect->dropConstraintSql($this);
}
/**
* Returns an array of the table schema.
*
* @return array<string, mixed>
*/
public function __debugInfo(): array
{
return [
'table' => $this->_table,
'columns' => $this->_columns,
'indexes' => $this->_indexes,
'constraints' => $this->_constraints,
'options' => $this->_options,
'typeMap' => $this->_typeMap,
'temporary' => $this->_temporary,
];
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
/**
* Defines the interface for getting the schema.
*/
interface TableSchemaAwareInterface
{
/**
* Get and set the schema for this fixture.
*
* @return \Cake\Database\Schema\TableSchemaInterface&\Cake\Database\Schema\SqlGeneratorInterface
*/
public function getTableSchema();
/**
* Get and set the schema for this fixture.
*
* @param \Cake\Database\Schema\TableSchemaInterface&\Cake\Database\Schema\SqlGeneratorInterface $schema The table to set.
* @return $this
*/
public function setTableSchema($schema);
}
+283
View File
@@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
use Cake\Datasource\SchemaInterface;
/**
* An interface used by database TableSchema objects.
*/
interface TableSchemaInterface extends SchemaInterface
{
/**
* Binary column type
*
* @var string
*/
public const TYPE_BINARY = 'binary';
/**
* Binary UUID column type
*
* @var string
*/
public const TYPE_BINARY_UUID = 'binaryuuid';
/**
* Date column type
*
* @var string
*/
public const TYPE_DATE = 'date';
/**
* Datetime column type
*
* @var string
*/
public const TYPE_DATETIME = 'datetime';
/**
* Datetime with fractional seconds column type
*
* @var string
*/
public const TYPE_DATETIME_FRACTIONAL = 'datetimefractional';
/**
* Time column type
*
* @var string
*/
public const TYPE_TIME = 'time';
/**
* Timestamp column type
*
* @var string
*/
public const TYPE_TIMESTAMP = 'timestamp';
/**
* Timestamp with fractional seconds column type
*
* @var string
*/
public const TYPE_TIMESTAMP_FRACTIONAL = 'timestampfractional';
/**
* Timestamp with time zone column type
*
* @var string
*/
public const TYPE_TIMESTAMP_TIMEZONE = 'timestamptimezone';
/**
* JSON column type
*
* @var string
*/
public const TYPE_JSON = 'json';
/**
* String column type
*
* @var string
*/
public const TYPE_STRING = 'string';
/**
* Char column type
*
* @var string
*/
public const TYPE_CHAR = 'char';
/**
* Text column type
*
* @var string
*/
public const TYPE_TEXT = 'text';
/**
* Tiny Integer column type
*
* @var string
*/
public const TYPE_TINYINTEGER = 'tinyinteger';
/**
* Small Integer column type
*
* @var string
*/
public const TYPE_SMALLINTEGER = 'smallinteger';
/**
* Integer column type
*
* @var string
*/
public const TYPE_INTEGER = 'integer';
/**
* Big Integer column type
*
* @var string
*/
public const TYPE_BIGINTEGER = 'biginteger';
/**
* Float column type
*
* @var string
*/
public const TYPE_FLOAT = 'float';
/**
* Decimal column type
*
* @var string
*/
public const TYPE_DECIMAL = 'decimal';
/**
* Boolean column type
*
* @var string
*/
public const TYPE_BOOLEAN = 'boolean';
/**
* UUID column type
*
* @var string
*/
public const TYPE_UUID = 'uuid';
/**
* Check whether a table has an autoIncrement column defined.
*
* @return bool
*/
public function hasAutoincrement(): bool;
/**
* Sets whether the table is temporary in the database.
*
* @param bool $temporary Whether the table is to be temporary.
* @return $this
*/
public function setTemporary(bool $temporary);
/**
* Gets whether the table is temporary in the database.
*
* @return bool The current temporary setting.
*/
public function isTemporary(): bool;
/**
* Get the column(s) used for the primary key.
*
* @return array<string> Column name(s) for the primary key. An
* empty list will be returned when the table has no primary key.
*/
public function getPrimaryKey(): array;
/**
* Add an index.
*
* Used to add indexes, and full text indexes in platforms that support
* them.
*
* ### Attributes
*
* - `type` The type of index being added.
* - `columns` The columns in the index.
*
* @param string $name The name of the index.
* @param array<string, mixed>|string $attrs The attributes for the index.
* If string it will be used as `type`.
* @return $this
* @throws \Cake\Database\Exception\DatabaseException
*/
public function addIndex(string $name, $attrs);
/**
* Read information about an index based on name.
*
* @param string $name The name of the index.
* @return array<string, mixed>|null Array of index data, or null
*/
public function getIndex(string $name): ?array;
/**
* Get the names of all the indexes in the table.
*
* @return array<string>
*/
public function indexes(): array;
/**
* Add a constraint.
*
* Used to add constraints to a table. For example primary keys, unique
* keys and foreign keys.
*
* ### Attributes
*
* - `type` The type of constraint being added.
* - `columns` The columns in the index.
* - `references` The table, column a foreign key references.
* - `update` The behavior on update. Options are 'restrict', 'setNull', 'cascade', 'noAction'.
* - `delete` The behavior on delete. Options are 'restrict', 'setNull', 'cascade', 'noAction'.
*
* The default for 'update' & 'delete' is 'cascade'.
*
* @param string $name The name of the constraint.
* @param array<string, mixed>|string $attrs The attributes for the constraint.
* If string it will be used as `type`.
* @return $this
* @throws \Cake\Database\Exception\DatabaseException
*/
public function addConstraint(string $name, $attrs);
/**
* Read information about a constraint based on name.
*
* @param string $name The name of the constraint.
* @return array<string, mixed>|null Array of constraint data, or null
*/
public function getConstraint(string $name): ?array;
/**
* Remove a constraint.
*
* @param string $name Name of the constraint to remove
* @return $this
*/
public function dropConstraint(string $name);
/**
* Get the names of all the constraints in the table.
*
* @return array<string>
*/
public function constraints(): array;
}