diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 9aa75c0b204b..48585d6fd969 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1451,11 +1451,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/Database/Postgre/Forge.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Return type \\(array\\|bool\\|string\\) of method CodeIgniter\\\\Database\\\\Postgre\\\\Forge\\:\\:_alterTable\\(\\) should be covariant with return type \\(array\\\\|string\\|false\\) of method CodeIgniter\\\\Database\\\\Forge\\:\\:_alterTable\\(\\)$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/Postgre/Forge.php', -]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', 'count' => 1, @@ -1656,11 +1651,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/Database/SQLite3/Forge.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Return type \\(array\\|string\\|null\\) of method CodeIgniter\\\\Database\\\\SQLite3\\\\Forge\\:\\:_alterTable\\(\\) should be covariant with return type \\(array\\\\|string\\|false\\) of method CodeIgniter\\\\Database\\\\Forge\\:\\:_alterTable\\(\\)$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLite3/Forge.php', -]; $ignoreErrors[] = [ 'message' => '#^Return type \\(SQLite3Result\\|false\\) of method CodeIgniter\\\\Database\\\\SQLite3\\\\PreparedQuery\\:\\:_getResult\\(\\) should be covariant with return type \\(object\\|resource\\|null\\) of method CodeIgniter\\\\Database\\\\BasePreparedQuery\\\\:\\:_getResult\\(\\)$#', 'count' => 1, diff --git a/system/Database/Forge.php b/system/Database/Forge.php index 57e43bf199fe..adbdc208fc47 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -32,7 +32,7 @@ class Forge /** * List of fields. * - * @var array + * @var array [name => attributes] */ protected $fields = []; @@ -351,14 +351,14 @@ public function addUniqueKey($key, string $keyName = '') /** * Add Field * - * @param array|string $field + * @param array|string $fields Field array or Field string * * @return Forge */ - public function addField($field) + public function addField($fields) { - if (is_string($field)) { - if ($field === 'id') { + if (is_string($fields)) { + if ($fields === 'id') { $this->addField([ 'id' => [ 'type' => 'INT', @@ -368,27 +368,27 @@ public function addField($field) ]); $this->addKey('id', true); } else { - if (strpos($field, ' ') === false) { + if (strpos($fields, ' ') === false) { throw new InvalidArgumentException('Field information is required for that operation.'); } - $fieldName = explode(' ', $field, 2)[0]; + $fieldName = explode(' ', $fields, 2)[0]; $fieldName = trim($fieldName, '`\'"'); - $this->fields[$fieldName] = $field; + $this->fields[$fieldName] = $fields; } } - if (is_array($field)) { - foreach ($field as $idx => $f) { - if (is_string($f)) { - $this->addField($f); + if (is_array($fields)) { + foreach ($fields as $name => $attributes) { + if (is_string($attributes)) { + $this->addField($attributes); continue; } - if (is_array($f)) { - $this->fields = array_merge($this->fields, [$idx => $f]); + if (is_array($attributes)) { + $this->fields = array_merge($this->fields, [$name => $attributes]); } } } @@ -404,8 +404,14 @@ public function addField($field) * * @throws DatabaseException */ - public function addForeignKey($fieldName = '', string $tableName = '', $tableField = '', string $onUpdate = '', string $onDelete = '', string $fkName = ''): Forge - { + public function addForeignKey( + $fieldName = '', + string $tableName = '', + $tableField = '', + string $onUpdate = '', + string $onDelete = '', + string $fkName = '' + ): Forge { $fieldName = (array) $fieldName; $tableField = (array) $tableField; @@ -428,8 +434,9 @@ public function addForeignKey($fieldName = '', string $tableName = '', $tableFie */ public function dropKey(string $table, string $keyName, bool $prefixKeyName = true): bool { - $keyName = $this->db->escapeIdentifiers(($prefixKeyName === true ? $this->db->DBPrefix : '') . $keyName); - $table = $this->db->escapeIdentifiers($this->db->DBPrefix . $table); + $keyName = $this->db->escapeIdentifiers(($prefixKeyName === true ? $this->db->DBPrefix : '') . $keyName); + $table = $this->db->escapeIdentifiers($this->db->DBPrefix . $table); + $dropKeyAsConstraint = $this->dropKeyAsConstraint($table, $keyName); if ($dropKeyAsConstraint === true) { @@ -458,7 +465,7 @@ public function dropKey(string $table, string $keyName, bool $prefixKeyName = tr } /** - * Checks if if key needs to be dropped as a constraint. + * Checks if key needs to be dropped as a constraint. */ protected function dropKeyAsConstraint(string $table, string $constraintName): bool { @@ -494,7 +501,7 @@ public function dropPrimaryKey(string $table, string $keyName = ''): bool } /** - * @return BaseResult|bool|false|mixed|Query + * @return bool * * @throws DatabaseException */ @@ -518,7 +525,9 @@ public function dropForeignKey(string $table, string $foreignName) } /** - * @return mixed + * @param array $attributes Table attributes + * + * @return bool * * @throws DatabaseException */ @@ -562,28 +571,30 @@ public function createTable(string $table, bool $ifNotExists = false, array $att } /** + * @param array $attributes Table attributes + * * @return string SQL string * * @deprecated $ifNotExists is no longer used, and will be removed. */ protected function _createTable(string $table, bool $ifNotExists, array $attributes) { - $columns = $this->_processFields(true); + $processedFields = $this->_processFields(true); - for ($i = 0, $c = count($columns); $i < $c; $i++) { - $columns[$i] = ($columns[$i]['_literal'] !== false) ? "\n\t" . $columns[$i]['_literal'] - : "\n\t" . $this->_processColumn($columns[$i]); + for ($i = 0, $c = count($processedFields); $i < $c; $i++) { + $processedFields[$i] = ($processedFields[$i]['_literal'] !== false) ? "\n\t" . $processedFields[$i]['_literal'] + : "\n\t" . $this->_processColumn($processedFields[$i]); } - $columns = implode(',', $columns); + $processedFields = implode(',', $processedFields); - $columns .= $this->_processPrimaryKeys($table); - $columns .= current($this->_processForeignKeys($table)); + $processedFields .= $this->_processPrimaryKeys($table); + $processedFields .= current($this->_processForeignKeys($table)); if ($this->createTableKeys === true) { $indexes = current($this->_processIndexes($table)); if (is_string($indexes)) { - $columns .= $indexes; + $processedFields .= $indexes; } } @@ -591,7 +602,7 @@ protected function _createTable(string $table, bool $ifNotExists, array $attribu $this->createTableStr . '%s', 'CREATE TABLE', $this->db->escapeIdentifiers($table), - $columns, + $processedFields, $this->_createTableAttributes($attributes) ); } @@ -610,7 +621,7 @@ protected function _createTableAttributes(array $attributes): string } /** - * @return mixed + * @return bool * * @throws DatabaseException */ @@ -676,7 +687,7 @@ protected function _dropTable(string $table, bool $ifExists, bool $cascade) } /** - * @return mixed + * @return bool * * @throws DatabaseException */ @@ -716,23 +727,24 @@ public function renameTable(string $tableName, string $newTableName) } /** - * @param array|string $field + * @param array|string $fields Field array or Field string * * @throws DatabaseException */ - public function addColumn(string $table, $field): bool + public function addColumn(string $table, $fields): bool { // Work-around for literal column definitions - if (! is_array($field)) { - $field = [$field]; + if (is_string($fields)) { + $fields = [$fields]; } - foreach (array_keys($field) as $k) { - $this->addField([$k => $field[$k]]); + foreach (array_keys($fields) as $name) { + $this->addField([$name => $fields[$name]]); } $sqls = $this->_alterTable('ADD', $this->db->DBPrefix . $table, $this->_processFields()); $this->reset(); + if ($sqls === false) { if ($this->db->DBDebug) { throw new DatabaseException('This feature is not available for the database you are using.'); @@ -751,15 +763,16 @@ public function addColumn(string $table, $field): bool } /** - * @param array|string $columnName + * @param array|string $columnNames column names to DROP * - * @return mixed + * @return bool * * @throws DatabaseException */ - public function dropColumn(string $table, $columnName) + public function dropColumn(string $table, $columnNames) { - $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $columnName); + $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $columnNames); + if ($sql === false) { if ($this->db->DBDebug) { throw new DatabaseException('This feature is not available for the database you are using.'); @@ -772,19 +785,19 @@ public function dropColumn(string $table, $columnName) } /** - * @param array|string $field + * @param array|string $fields Field array or Field string * * @throws DatabaseException */ - public function modifyColumn(string $table, $field): bool + public function modifyColumn(string $table, $fields): bool { // Work-around for literal column definitions - if (! is_array($field)) { - $field = [$field]; + if (is_string($fields)) { + $fields = [$fields]; } - foreach (array_keys($field) as $k) { - $this->addField([$k => $field[$k]]); + foreach (array_keys($fields) as $name) { + $this->addField([$name => $fields[$name]]); } if ($this->fields === []) { @@ -793,6 +806,7 @@ public function modifyColumn(string $table, $field): bool $sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields()); $this->reset(); + if ($sqls === false) { if ($this->db->DBDebug) { throw new DatabaseException('This feature is not available for the database you are using.'); @@ -813,48 +827,53 @@ public function modifyColumn(string $table, $field): bool } /** - * @param array|string $fields + * @param 'ADD'|'CHANGE'|'DROP' $alterType + * @param array|string $processedFields Processed column definitions + * or column names to DROP * - * @return false|string|string[] + * @return false|list|string|null SQL string + * @phpstan-return ($alterType is 'DROP' ? string : list|false|null) */ - protected function _alterTable(string $alterType, string $table, $fields) + protected function _alterTable(string $alterType, string $table, $processedFields) { $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' '; // DROP has everything it needs now. if ($alterType === 'DROP') { - if (is_string($fields)) { - $fields = explode(',', $fields); + $columnNamesToDrop = $processedFields; + + if (is_string($columnNamesToDrop)) { + $columnNamesToDrop = explode(',', $columnNamesToDrop); } - $fields = array_map(fn ($field) => 'DROP COLUMN ' . $this->db->escapeIdentifiers(trim($field)), $fields); + $columnNamesToDrop = array_map(fn ($field) => 'DROP COLUMN ' . $this->db->escapeIdentifiers(trim($field)), $columnNamesToDrop); - return $sql . implode(', ', $fields); + return $sql . implode(', ', $columnNamesToDrop); } $sql .= ($alterType === 'ADD') ? 'ADD ' : $alterType . ' COLUMN '; $sqls = []; - foreach ($fields as $data) { - $sqls[] = $sql . ($data['_literal'] !== false - ? $data['_literal'] - : $this->_processColumn($data)); + foreach ($processedFields as $field) { + $sqls[] = $sql . ($field['_literal'] !== false + ? $field['_literal'] + : $this->_processColumn($field)); } return $sqls; } /** - * Process fields + * Returns $processedFields array from $this->fields data. */ protected function _processFields(bool $createTable = false): array { - $fields = []; + $processedFields = []; - foreach ($this->fields as $key => $attributes) { + foreach ($this->fields as $name => $attributes) { if (! is_array($attributes)) { - $fields[] = ['_literal' => $attributes]; + $processedFields[] = ['_literal' => $attributes]; continue; } @@ -870,7 +889,7 @@ protected function _processFields(bool $createTable = false): array } $field = [ - 'name' => $key, + 'name' => $name, 'new_name' => $attributes['NAME'] ?? null, 'type' => $attributes['TYPE'] ?? null, 'length' => '', @@ -928,24 +947,24 @@ protected function _processFields(bool $createTable = false): array $field['length'] = '(' . $attributes['CONSTRAINT'] . ')'; } - $fields[] = $field; + $processedFields[] = $field; } - return $fields; + return $processedFields; } /** - * Process column + * Converts $processedField array to field definition string. */ - protected function _processColumn(array $field): string + protected function _processColumn(array $processedField): string { - return $this->db->escapeIdentifiers($field['name']) - . ' ' . $field['type'] . $field['length'] - . $field['unsigned'] - . $field['default'] - . $field['null'] - . $field['auto_increment'] - . $field['unique']; + return $this->db->escapeIdentifiers($processedField['name']) + . ' ' . $processedField['type'] . $processedField['length'] + . $processedField['unsigned'] + . $processedField['default'] + . $processedField['null'] + . $processedField['auto_increment'] + . $processedField['unique']; } /** @@ -1163,10 +1182,10 @@ protected function _processForeignKeys(string $table, bool $asQuery = false): ar { $errorNames = []; - foreach ($this->foreignKeys as $name) { - foreach ($name['field'] as $f) { - if (! isset($this->fields[$f])) { - $errorNames[] = $f; + foreach ($this->foreignKeys as $fkeyInfo) { + foreach ($fkeyInfo['field'] as $fieldName) { + if (! isset($this->fields[$fieldName])) { + $errorNames[] = $fieldName; } } } diff --git a/system/Database/MySQLi/Forge.php b/system/Database/MySQLi/Forge.php index 0d294c2d09a2..b1beba2a9dd2 100644 --- a/system/Database/MySQLi/Forge.php +++ b/system/Database/MySQLi/Forge.php @@ -128,57 +128,59 @@ protected function _createTableAttributes(array $attributes): string /** * ALTER TABLE * - * @param string $alterType ALTER type - * @param string $table Table name - * @param array|string $field Column definition + * @param string $alterType ALTER type + * @param string $table Table name + * @param array|string $processedFields Processed column definitions + * or column names to DROP * - * @return string|string[] + * @return list|string SQL string + * @phpstan-return ($alterType is 'DROP' ? string : list) */ - protected function _alterTable(string $alterType, string $table, $field) + protected function _alterTable(string $alterType, string $table, $processedFields) { if ($alterType === 'DROP') { - return parent::_alterTable($alterType, $table, $field); + return parent::_alterTable($alterType, $table, $processedFields); } $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); - foreach ($field as $i => $data) { - if ($data['_literal'] !== false) { - $field[$i] = ($alterType === 'ADD') ? "\n\tADD " . $data['_literal'] : "\n\tMODIFY " . $data['_literal']; + foreach ($processedFields as $i => $field) { + if ($field['_literal'] !== false) { + $processedFields[$i] = ($alterType === 'ADD') ? "\n\tADD " . $field['_literal'] : "\n\tMODIFY " . $field['_literal']; } else { if ($alterType === 'ADD') { - $field[$i]['_literal'] = "\n\tADD "; + $processedFields[$i]['_literal'] = "\n\tADD "; } else { - $field[$i]['_literal'] = empty($data['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; + $processedFields[$i]['_literal'] = empty($field['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; } - $field[$i] = $field[$i]['_literal'] . $this->_processColumn($field[$i]); + $processedFields[$i] = $processedFields[$i]['_literal'] . $this->_processColumn($processedFields[$i]); } } - return [$sql . implode(',', $field)]; + return [$sql . implode(',', $processedFields)]; } /** * Process column */ - protected function _processColumn(array $field): string + protected function _processColumn(array $processedField): string { - $extraClause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; + $extraClause = isset($processedField['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($processedField['after']) : ''; - if (empty($extraClause) && isset($field['first']) && $field['first'] === true) { + if (empty($extraClause) && isset($processedField['first']) && $processedField['first'] === true) { $extraClause = ' FIRST'; } - return $this->db->escapeIdentifiers($field['name']) - . (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name'])) - . ' ' . $field['type'] . $field['length'] - . $field['unsigned'] - . $field['null'] - . $field['default'] - . $field['auto_increment'] - . $field['unique'] - . (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment']) + return $this->db->escapeIdentifiers($processedField['name']) + . (empty($processedField['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($processedField['new_name'])) + . ' ' . $processedField['type'] . $processedField['length'] + . $processedField['unsigned'] + . $processedField['null'] + . $processedField['default'] + . $processedField['auto_increment'] + . $processedField['unique'] + . (empty($processedField['comment']) ? '' : ' COMMENT ' . $processedField['comment']) . $extraClause; } diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 6e1e85f666a1..232bdcf2721e 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -93,21 +93,29 @@ class Forge extends BaseForge /** * ALTER TABLE * - * @param string $alterType ALTER type - * @param string $table Table name - * @param array|string $field Column definition + * @param string $alterType ALTER type + * @param string $table Table name + * @param array|string $processedFields Processed column definitions + * or column names to DROP * - * @return string|string[] + * @return list|string SQL string + * @phpstan-return ($alterType is 'DROP' ? string : list) */ - protected function _alterTable(string $alterType, string $table, $field) + protected function _alterTable(string $alterType, string $table, $processedFields) { $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); if ($alterType === 'DROP') { - $fields = array_map(fn ($field) => $this->db->escapeIdentifiers(trim($field)), is_string($field) ? explode(',', $field) : $field); + $columnNamesToDrop = $processedFields; + + $fields = array_map( + fn ($field) => $this->db->escapeIdentifiers(trim($field)), + is_string($columnNamesToDrop) ? explode(',', $columnNamesToDrop) : $columnNamesToDrop + ); return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; } + if ($alterType === 'CHANGE') { $alterType = 'MODIFY'; } @@ -115,50 +123,50 @@ protected function _alterTable(string $alterType, string $table, $field) $nullableMap = array_column($this->db->getFieldData($table), 'nullable', 'name'); $sqls = []; - for ($i = 0, $c = count($field); $i < $c; $i++) { + for ($i = 0, $c = count($processedFields); $i < $c; $i++) { if ($alterType === 'MODIFY') { // If a null constraint is added to a column with a null constraint, // ORA-01451 will occur, // so add null constraint is used only when it is different from the current null constraint. // If a not null constraint is added to a column with a not null constraint, // ORA-01442 will occur. - $wantToAddNull = strpos($field[$i]['null'], ' NOT') === false; - $currentNullable = $nullableMap[$field[$i]['name']]; + $wantToAddNull = strpos($processedFields[$i]['null'], ' NOT') === false; + $currentNullable = $nullableMap[$processedFields[$i]['name']]; if ($wantToAddNull === true && $currentNullable === true) { - $field[$i]['null'] = ''; - } elseif ($field[$i]['null'] === '' && $currentNullable === false) { + $processedFields[$i]['null'] = ''; + } elseif ($processedFields[$i]['null'] === '' && $currentNullable === false) { // Nullable by default - $field[$i]['null'] = ' NULL'; + $processedFields[$i]['null'] = ' NULL'; } elseif ($wantToAddNull === false && $currentNullable === false) { - $field[$i]['null'] = ''; + $processedFields[$i]['null'] = ''; } } - if ($field[$i]['_literal'] !== false) { - $field[$i] = "\n\t" . $field[$i]['_literal']; + if ($processedFields[$i]['_literal'] !== false) { + $processedFields[$i] = "\n\t" . $processedFields[$i]['_literal']; } else { - $field[$i]['_literal'] = "\n\t" . $this->_processColumn($field[$i]); + $processedFields[$i]['_literal'] = "\n\t" . $this->_processColumn($processedFields[$i]); - if (! empty($field[$i]['comment'])) { + if (! empty($processedFields[$i]['comment'])) { $sqls[] = 'COMMENT ON COLUMN ' - . $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($field[$i]['name']) - . ' IS ' . $field[$i]['comment']; + . $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($processedFields[$i]['name']) + . ' IS ' . $processedFields[$i]['comment']; } - if ($alterType === 'MODIFY' && ! empty($field[$i]['new_name'])) { - $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) - . ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']); + if ($alterType === 'MODIFY' && ! empty($processedFields[$i]['new_name'])) { + $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($processedFields[$i]['name']) + . ' TO ' . $this->db->escapeIdentifiers($processedFields[$i]['new_name']); } - $field[$i] = "\n\t" . $field[$i]['_literal']; + $processedFields[$i] = "\n\t" . $processedFields[$i]['_literal']; } } $sql .= ' ' . $alterType . ' '; - $sql .= count($field) === 1 - ? $field[0] - : '(' . implode(',', $field) . ')'; + $sql .= count($processedFields) === 1 + ? $processedFields[0] + : '(' . implode(',', $processedFields) . ')'; // RENAME COLUMN must be executed after MODIFY array_unshift($sqls, $sql); @@ -184,26 +192,26 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field) /** * Process column */ - protected function _processColumn(array $field): string + protected function _processColumn(array $processedField): string { $constraint = ''; // @todo: can't cover multi pattern when set type. - if ($field['type'] === 'VARCHAR2' && strpos($field['length'], "('") === 0) { - $constraint = ' CHECK(' . $this->db->escapeIdentifiers($field['name']) - . ' IN ' . $field['length'] . ')'; + if ($processedField['type'] === 'VARCHAR2' && strpos($processedField['length'], "('") === 0) { + $constraint = ' CHECK(' . $this->db->escapeIdentifiers($processedField['name']) + . ' IN ' . $processedField['length'] . ')'; - $field['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($field['length'], 2, -2)))) . ')' . $constraint; - } elseif (isset($this->primaryKeys['fields']) && count($this->primaryKeys['fields']) === 1 && $field['name'] === $this->primaryKeys['fields'][0]) { - $field['unique'] = ''; + $processedField['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($processedField['length'], 2, -2)))) . ')' . $constraint; + } elseif (isset($this->primaryKeys['fields']) && count($this->primaryKeys['fields']) === 1 && $processedField['name'] === $this->primaryKeys['fields'][0]) { + $processedField['unique'] = ''; } - return $this->db->escapeIdentifiers($field['name']) - . ' ' . $field['type'] . $field['length'] - . $field['unsigned'] - . $field['default'] - . $field['auto_increment'] - . $field['null'] - . $field['unique']; + return $this->db->escapeIdentifiers($processedField['name']) + . ' ' . $processedField['type'] . $processedField['length'] + . $processedField['unsigned'] + . $processedField['default'] + . $processedField['auto_increment'] + . $processedField['null'] + . $processedField['unique']; } /** diff --git a/system/Database/Postgre/Forge.php b/system/Database/Postgre/Forge.php index 47be1b063c8d..ee7583642684 100644 --- a/system/Database/Postgre/Forge.php +++ b/system/Database/Postgre/Forge.php @@ -81,50 +81,52 @@ protected function _createTableAttributes(array $attributes): string } /** - * @param array|string $field + * @param array|string $processedFields Processed column definitions + * or column names to DROP * - * @return array|bool|string + * @return false|list|string SQL string or false + * @phpstan-return ($alterType is 'DROP' ? string : list|false) */ - protected function _alterTable(string $alterType, string $table, $field) + protected function _alterTable(string $alterType, string $table, $processedFields) { if (in_array($alterType, ['DROP', 'ADD'], true)) { - return parent::_alterTable($alterType, $table, $field); + return parent::_alterTable($alterType, $table, $processedFields); } $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); $sqls = []; - foreach ($field as $data) { - if ($data['_literal'] !== false) { + foreach ($processedFields as $field) { + if ($field['_literal'] !== false) { return false; } - if (version_compare($this->db->getVersion(), '8', '>=') && isset($data['type'])) { - $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) - . " TYPE {$data['type']}{$data['length']}"; + if (version_compare($this->db->getVersion(), '8', '>=') && isset($field['type'])) { + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name']) + . " TYPE {$field['type']}{$field['length']}"; } - if (! empty($data['default'])) { - $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) - . " SET DEFAULT {$data['default']}"; + if (! empty($field['default'])) { + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name']) + . " SET DEFAULT {$field['default']}"; } $nullable = true; // Nullable by default. - if (isset($data['null']) && ($data['null'] === false || $data['null'] === ' NOT ' . $this->null)) { + if (isset($field['null']) && ($field['null'] === false || $field['null'] === ' NOT ' . $this->null)) { $nullable = false; } - $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name']) . ($nullable === true ? ' DROP' : ' SET') . ' NOT NULL'; - if (! empty($data['new_name'])) { - $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($data['name']) - . ' TO ' . $this->db->escapeIdentifiers($data['new_name']); + if (! empty($field['new_name'])) { + $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field['name']) + . ' TO ' . $this->db->escapeIdentifiers($field['new_name']); } - if (! empty($data['comment'])) { + if (! empty($field['comment'])) { $sqls[] = 'COMMENT ON COLUMN' . $this->db->escapeIdentifiers($table) - . '.' . $this->db->escapeIdentifiers($data['name']) - . " IS {$data['comment']}"; + . '.' . $this->db->escapeIdentifiers($field['name']) + . " IS {$field['comment']}"; } } @@ -134,14 +136,14 @@ protected function _alterTable(string $alterType, string $table, $field) /** * Process column */ - protected function _processColumn(array $field): string + protected function _processColumn(array $processedField): string { - return $this->db->escapeIdentifiers($field['name']) - . ' ' . $field['type'] . ($field['type'] === 'text' ? '' : $field['length']) - . $field['default'] - . $field['null'] - . $field['auto_increment'] - . $field['unique']; + return $this->db->escapeIdentifiers($processedField['name']) + . ' ' . $processedField['type'] . ($processedField['type'] === 'text' ? '' : $processedField['length']) + . $processedField['default'] + . $processedField['null'] + . $processedField['auto_increment'] + . $processedField['unique']; } /** diff --git a/system/Database/SQLSRV/Forge.php b/system/Database/SQLSRV/Forge.php index ff1ce2e59bb9..522d5fa02f54 100755 --- a/system/Database/SQLSRV/Forge.php +++ b/system/Database/SQLSRV/Forge.php @@ -126,23 +126,27 @@ protected function _createTableAttributes(array $attributes): string } /** - * @param array|string $field + * @param array|string $processedFields Processed column definitions + * or column names to DROP * - * @return false|string|string[] + * @return false|list|string SQL string or false + * @phpstan-return ($alterType is 'DROP' ? string : list|false) */ - protected function _alterTable(string $alterType, string $table, $field) + protected function _alterTable(string $alterType, string $table, $processedFields) { // Handle DROP here if ($alterType === 'DROP') { + $columnNamesToDrop = $processedFields; + // check if fields are part of any indexes $indexData = $this->db->getIndexData($table); foreach ($indexData as $index) { - if (is_string($field)) { - $field = explode(',', $field); + if (is_string($columnNamesToDrop)) { + $columnNamesToDrop = explode(',', $columnNamesToDrop); } - $fld = array_intersect($field, $index->fields); + $fld = array_intersect($columnNamesToDrop, $index->fields); // Drop index if field is part of an index if ($fld !== []) { @@ -153,7 +157,7 @@ protected function _alterTable(string $alterType, string $table, $field) $fullTable = $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table); // Drop default constraints - $fields = implode(',', $this->db->escape((array) $field)); + $fields = implode(',', $this->db->escape((array) $columnNamesToDrop)); $sql = << 'COLUMN [' . trim($item) . ']', (array) $field); + $fields = array_map(static fn ($item) => 'COLUMN [' . trim($item) . ']', (array) $columnNamesToDrop); return $sql . implode(',', $fields); } @@ -181,45 +185,45 @@ protected function _alterTable(string $alterType, string $table, $field) $sqls = []; if ($alterType === 'ADD') { - foreach ($field as $data) { - $sqls[] = $sql . ($data['_literal'] !== false ? $data['_literal'] : $this->_processColumn($data)); + foreach ($processedFields as $field) { + $sqls[] = $sql . ($field['_literal'] !== false ? $field['_literal'] : $this->_processColumn($field)); } return $sqls; } - foreach ($field as $data) { - if ($data['_literal'] !== false) { + foreach ($processedFields as $field) { + if ($field['_literal'] !== false) { return false; } - if (isset($data['type'])) { - $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) - . " {$data['type']}{$data['length']}"; + if (isset($field['type'])) { + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name']) + . " {$field['type']}{$field['length']}"; } - if (! empty($data['default'])) { - $sqls[] = $sql . ' ALTER COLUMN ADD CONSTRAINT ' . $this->db->escapeIdentifiers($data['name']) . '_def' - . " DEFAULT {$data['default']} FOR " . $this->db->escapeIdentifiers($data['name']); + if (! empty($field['default'])) { + $sqls[] = $sql . ' ALTER COLUMN ADD CONSTRAINT ' . $this->db->escapeIdentifiers($field['name']) . '_def' + . " DEFAULT {$field['default']} FOR " . $this->db->escapeIdentifiers($field['name']); } $nullable = true; // Nullable by default. - if (isset($data['null']) && ($data['null'] === false || $data['null'] === ' NOT ' . $this->null)) { + if (isset($field['null']) && ($field['null'] === false || $field['null'] === ' NOT ' . $this->null)) { $nullable = false; } - $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) - . " {$data['type']}{$data['length']} " . ($nullable === true ? '' : 'NOT') . ' NULL'; + $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name']) + . " {$field['type']}{$field['length']} " . ($nullable === true ? '' : 'NOT') . ' NULL'; - if (! empty($data['comment'])) { + if (! empty($field['comment'])) { $sqls[] = 'EXEC sys.sp_addextendedproperty ' - . "@name=N'Caption', @value=N'" . $data['comment'] . "' , " + . "@name=N'Caption', @value=N'" . $field['comment'] . "' , " . "@level0type=N'SCHEMA',@level0name=N'" . $this->db->schema . "', " . "@level1type=N'TABLE',@level1name=N'" . $this->db->escapeIdentifiers($table) . "', " - . "@level2type=N'COLUMN',@level2name=N'" . $this->db->escapeIdentifiers($data['name']) . "'"; + . "@level2type=N'COLUMN',@level2name=N'" . $this->db->escapeIdentifiers($field['name']) . "'"; } - if (! empty($data['new_name'])) { - $sqls[] = "EXEC sp_rename '[" . $this->db->schema . '].[' . $table . '].[' . $data['name'] . "]' , '" . $data['new_name'] . "', 'COLUMN';"; + if (! empty($field['new_name'])) { + $sqls[] = "EXEC sp_rename '[" . $this->db->schema . '].[' . $table . '].[' . $field['name'] . "]' , '" . $field['new_name'] . "', 'COLUMN';"; } } @@ -287,16 +291,16 @@ protected function _processIndexes(string $table, bool $asQuery = false): array /** * Process column */ - protected function _processColumn(array $field): string + protected function _processColumn(array $processedField): string { - return $this->db->escapeIdentifiers($field['name']) - . (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name'])) - . ' ' . $field['type'] . ($field['type'] === 'text' ? '' : $field['length']) - . $field['default'] - . $field['null'] - . $field['auto_increment'] + return $this->db->escapeIdentifiers($processedField['name']) + . (empty($processedField['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($processedField['new_name'])) + . ' ' . $processedField['type'] . ($processedField['type'] === 'text' ? '' : $processedField['length']) + . $processedField['default'] + . $processedField['null'] + . $processedField['auto_increment'] . '' - . $field['unique']; + . $processedField['unique']; } /** diff --git a/system/Database/SQLite3/Forge.php b/system/Database/SQLite3/Forge.php index b1dcb1dd599b..d7112c61389f 100644 --- a/system/Database/SQLite3/Forge.php +++ b/system/Database/SQLite3/Forge.php @@ -109,51 +109,56 @@ public function dropDatabase(string $dbName): bool } /** - * @param array|string $field + * @param array|string $processedFields Processed column definitions + * or column names to DROP * * @return array|string|null + * @return list|string|null SQL string or null + * @phpstan-return ($alterType is 'DROP' ? string : list|null) */ - protected function _alterTable(string $alterType, string $table, $field) + protected function _alterTable(string $alterType, string $table, $processedFields) { switch ($alterType) { case 'DROP': + $columnNamesToDrop = $processedFields; + $sqlTable = new Table($this->db, $this); $sqlTable->fromTable($table) - ->dropColumn($field) + ->dropColumn($columnNamesToDrop) ->run(); - return ''; + return ''; // Why empty string? case 'CHANGE': (new Table($this->db, $this)) ->fromTable($table) - ->modifyColumn($field) + ->modifyColumn($processedFields) // @TODO Bug: should be NOT processed fields ->run(); - return null; + return null; // Why null? default: - return parent::_alterTable($alterType, $table, $field); + return parent::_alterTable($alterType, $table, $processedFields); } } /** * Process column */ - protected function _processColumn(array $field): string + protected function _processColumn(array $processedField): string { - if ($field['type'] === 'TEXT' && strpos($field['length'], "('") === 0) { - $field['type'] .= ' CHECK(' . $this->db->escapeIdentifiers($field['name']) - . ' IN ' . $field['length'] . ')'; + if ($processedField['type'] === 'TEXT' && strpos($processedField['length'], "('") === 0) { + $processedField['type'] .= ' CHECK(' . $this->db->escapeIdentifiers($processedField['name']) + . ' IN ' . $processedField['length'] . ')'; } - return $this->db->escapeIdentifiers($field['name']) - . ' ' . $field['type'] - . $field['auto_increment'] - . $field['null'] - . $field['unique'] - . $field['default']; + return $this->db->escapeIdentifiers($processedField['name']) + . ' ' . $processedField['type'] + . $processedField['auto_increment'] + . $processedField['null'] + . $processedField['unique'] + . $processedField['default']; } /** @@ -183,8 +188,11 @@ protected function _attributeType(array &$attributes) */ protected function _attributeAutoIncrement(array &$attributes, array &$field) { - if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true - && stripos($field['type'], 'int') !== false) { + if ( + ! empty($attributes['AUTO_INCREMENT']) + && $attributes['AUTO_INCREMENT'] === true + && stripos($field['type'], 'int') !== false + ) { $field['type'] = 'INTEGER PRIMARY KEY'; $field['default'] = ''; $field['null'] = ''; diff --git a/system/Database/SQLite3/Table.php b/system/Database/SQLite3/Table.php index 77fd8699459f..620d28464738 100644 --- a/system/Database/SQLite3/Table.php +++ b/system/Database/SQLite3/Table.php @@ -28,7 +28,7 @@ class Table /** * All of the fields this table represents. * - * @var array> + * @var array> [name => attributes] */ protected $fields = []; @@ -156,7 +156,7 @@ public function run(): bool /** * Drops columns from the table. * - * @param array|string $columns + * @param list|string $columns Column names to drop. * * @return Table */ @@ -177,14 +177,15 @@ public function dropColumn($columns) } /** - * Modifies a field, including changing data type, - * renaming, etc. + * Modifies a field, including changing data type, renaming, etc. + * + * @param list> $fieldsToModify * * @return Table */ - public function modifyColumn(array $fields) + public function modifyColumn(array $fieldsToModify) { - foreach ($fields as $field) { + foreach ($fieldsToModify as $field) { $oldName = $field['name']; unset($field['name']);