diff --git a/CHANGELOG-4.0.md b/CHANGELOG-4.0.md index 953677422c9..18f1a4d71cc 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -3,10 +3,10 @@ ## Changed - Changed `Volt::convertEncoding` to no longer using `iconv` for a fallback since it causes issues with macOS [#14912](https://github.com/phalcon/cphalcon/issues/14912) +- Changed schema manipulation in `Phalcon\Db\Dialect\Mysql` - unquote numerical defaults [#14888](https://github.com/phalcon/cphalcon/pull/14888) [#14974](https://github.com/phalcon/cphalcon/pull/14974) ## Fixed -- Fixed `Phalcon\Mvc\Model\Query\Builder::getPhql` to add single quote between string value on a simple condition. [#14874](https://github.com/phalcon/cphalcon/issues/14874) [@jenovateurs](https://github.com/jenovateurs) -- Fixed schema manipulation in `Phalcon\Db\Dialect\Mysql`, remove unnecessary `NULL` in column definition, unquote numerical defaults. [#14888](https://github.com/phalcon/cphalcon/pull/14888) [@pfz](https://github.com/pfz) +- Fixed `Phalcon\Mvc\Model\Query\Builder::getPhql` to add single quote between string value on a simple condition [#14874](https://github.com/phalcon/cphalcon/issues/14874) - Fixed recognizing language operators inside Volt's echo mode (`{{ ... }}`) [#14476](https://github.com/phalcon/cphalcon/issues/14476) - Fixed `Tag::friendlyTitle` to correctly convert titles under MacOS and Windows [#14866](https://github.com/phalcon/cphalcon/issues/14866) - Fixed the Volt compiler to no longer parse `cache` fragments and thus searching for the `viewCache` service (deprecated for v4) [#14907](https://github.com/phalcon/cphalcon/issues/14907) @@ -19,7 +19,7 @@ ## Changed ## Fixed -- Fixed `Phalcon\Db::fetchAll` to correctly return data when `Enum::FETCH_COLUMN` is supplied. [#13321](https://github.com/phalcon/cphalcon/issues/13321) +- Fixed `Phalcon\Db::fetchAll` to correctly return data when `Enum::FETCH_COLUMN` is supplied [#13321](https://github.com/phalcon/cphalcon/issues/13321) - Fixed Postgres NULL values to not be required during model update. [#14862](https://github.com/phalcon/cphalcon/issues/14862) - Fixed MySQL alter column when default value contains not only CURRENT_TIMESTAMP [#14880](https://github.com/phalcon/cphalcon/issues/14880) - Fixed MySQL default value with ON UPDATE expression [#14887](https://github.com/phalcon/cphalcon/pull/14887) diff --git a/phalcon/Db/Dialect/Mysql.zep b/phalcon/Db/Dialect/Mysql.zep index 77e4a76fba8..1b117ae1c37 100644 --- a/phalcon/Db/Dialect/Mysql.zep +++ b/phalcon/Db/Dialect/Mysql.zep @@ -33,25 +33,31 @@ class Mysql extends Dialect */ public function addColumn(string! tableName, string! schemaName, column) -> string { - var afterPosition, defaultValue; + var afterPosition, defaultValue, upperDefaultValue; string sql; let sql = "ALTER TABLE " . this->prepareTable(tableName, schemaName) . " ADD `" . column->getName() . "` " . this->getColumnDefinition(column); + if column->isNotNull() { + let sql .= " NOT NULL"; + } else { + // This is required for some types like TIMESTAMP + // Query won't be executed if NULL wasn't specified + // Even if DEFAULT NULL was specified + let sql .= " NULL"; + } + if column->hasDefault() { let defaultValue = column->getDefault(); + let upperDefaultValue = strtoupper(defaultValue); - if memstr(strtoupper(defaultValue), "CURRENT_TIMESTAMP") || is_int(defaultValue) || is_float(defaultValue) { + if memstr(upperDefaultValue, "CURRENT_TIMESTAMP") || memstr(upperDefaultValue, "NULL") || is_int(defaultValue) || is_float(defaultValue) { let sql .= " DEFAULT " . defaultValue; } else { let sql .= " DEFAULT \"" . addcslashes(defaultValue, "\"") . "\""; } } - if column->isNotNull() { - let sql .= " NOT NULL"; - } - if column->isAutoIncrement() { let sql .= " AUTO_INCREMENT"; } @@ -135,7 +141,7 @@ class Mysql extends Dialect { var temporary, options, table, columns, column, indexes, index, reference, references, indexName, columnLine, indexType, onDelete, - onUpdate, defaultValue; + onUpdate, defaultValue, upperDefaultValue; array createLines; string indexSql, referenceSql, sql; @@ -166,30 +172,32 @@ class Mysql extends Dialect for column in columns { let columnLine = "`" . column->getName() . "` " . this->getColumnDefinition(column); + /** + * Add a NOT NULL clause + */ + if column->isNotNull() { + let columnLine .= " NOT NULL"; + } else { + // This is required for some types like TIMESTAMP + // Query won't be executed if NULL wasn't specified + // Even if DEFAULT NULL was specified + let columnLine .= " NULL"; + } + /** * Add a Default clause */ if column->hasDefault() { let defaultValue = column->getDefault(); + let upperDefaultValue = strtoupper(defaultValue); - if (defaultValue === null) { - let columnLine .= " DEFAULT NULL"; + if memstr(upperDefaultValue, "CURRENT_TIMESTAMP") || memstr(upperDefaultValue, "NULL") || is_int(defaultValue) || is_float(defaultValue) { + let columnLine .= " DEFAULT " . defaultValue; } else { - if memstr(strtoupper(defaultValue), "CURRENT_TIMESTAMP") || is_int(defaultValue) || is_float(defaultValue) { - let columnLine .= " DEFAULT " . defaultValue; - } else { - let columnLine .= " DEFAULT \"" . addcslashes(defaultValue, "\"") . "\""; - } + let columnLine .= " DEFAULT \"" . addcslashes(defaultValue, "\"") . "\""; } } - /** - * Add a NOT NULL clause - */ - if column->isNotNull() { - let columnLine .= " NOT NULL"; - } - /** * Add an AUTO_INCREMENT clause */ @@ -672,7 +680,7 @@ class Mysql extends Dialect */ public function modifyColumn(string! tableName, string! schemaName, column, currentColumn = null) -> string { - var afterPosition, defaultValue, columnDefinition; + var afterPosition, defaultValue, upperDefaultValue, columnDefinition; string sql; let columnDefinition = this->getColumnDefinition(column), @@ -688,20 +696,26 @@ class Mysql extends Dialect let sql .= " MODIFY `" . column->getName() . "` " . columnDefinition; } + if column->isNotNull() { + let sql .= " NOT NULL"; + } else { + // This is required for some types like TIMESTAMP + // Query won't be executed if NULL wasn't specified + // Even if DEFAULT NULL was specified + let sql .= " NULL"; + } + if column->hasDefault() { let defaultValue = column->getDefault(); + let upperDefaultValue = strtoupper(defaultValue); - if memstr(strtoupper(defaultValue), "CURRENT_TIMESTAMP") || is_int(defaultValue) || is_float(defaultValue) { + if memstr(upperDefaultValue, "CURRENT_TIMESTAMP") || memstr(upperDefaultValue, "NULL") || is_int(defaultValue) || is_float(defaultValue) { let sql .= " DEFAULT " . defaultValue; } else { let sql .= " DEFAULT \"" . addcslashes(defaultValue, "\"") . "\""; } } - if column->isNotNull() { - let sql .= " NOT NULL"; - } - if column->isAutoIncrement() { let sql .= " AUTO_INCREMENT"; } diff --git a/tests/integration/Db/Dialect/Mysql/AddColumnCest.php b/tests/integration/Db/Dialect/Mysql/AddColumnCest.php index f025e35913f..e4e7ecbf75e 100644 --- a/tests/integration/Db/Dialect/Mysql/AddColumnCest.php +++ b/tests/integration/Db/Dialect/Mysql/AddColumnCest.php @@ -24,36 +24,72 @@ class AddColumnCest * * @author Phalcon Team * @since 2020-02-27 + * @since 2020-05-02 Changed default null and nullable column definition */ public function dbAdapterPdoMysqlAddColumn(IntegrationTester $I) { $I->wantToTest('Db\Adapter\Pdo\Mysql - addColumn()'); $additions = [ + [ + new Column( + 'numeric_val', + [ + 'type' => Column::TYPE_FLOAT, + 'default' => 21.42, + 'notNull' => true, + ] + ), + 'ALTER TABLE `test` ADD `numeric_val` FLOAT NOT NULL DEFAULT 21.42', + ], + [ + new Column( + 'null_int', + [ + 'type' => Column::TYPE_INTEGER, + 'size' => 11, + 'notNull' => false, + 'after' => 'numeric_val', + ] + ), + 'ALTER TABLE `test` ADD `null_int` INT(11) NULL AFTER `numeric_val`', + ], + [ + new Column( + 'created_at', + [ + 'type' => Column::TYPE_TIMESTAMP, + 'default' => "CURRENT_TIMESTAMP", + 'notNull' => true, + 'after' => 'null_int', + ] + ), + 'ALTER TABLE `test` ADD `created_at` TIMESTAMP NOT NULL ' . + 'DEFAULT CURRENT_TIMESTAMP AFTER `null_int`', + ], [ new Column( 'updated_at', [ 'type' => Column::TYPE_TIMESTAMP, 'default' => "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", - 'notNull' => false, + 'notNull' => true, 'after' => 'created_at', ] ), - 'ALTER TABLE `test` ADD `updated_at` TIMESTAMP ' . + 'ALTER TABLE `test` ADD `updated_at` TIMESTAMP NOT NULL ' . 'DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `created_at`', ], [ new Column( - 'numeric_val', + 'deleted_at', [ - 'type' => Column::TYPE_FLOAT, - 'default' => 21.42, - 'notNull' => true, + 'type' => Column::TYPE_TIMESTAMP, + 'notNull' => false, 'after' => 'updated_at', ] ), - 'ALTER TABLE `test` ADD `numeric_val` FLOAT DEFAULT 21.42 NOT NULL AFTER `updated_at`', + 'ALTER TABLE `test` ADD `deleted_at` TIMESTAMP NULL AFTER `updated_at`', ], ]; diff --git a/tests/integration/Db/Dialect/Mysql/CreateTableCest.php b/tests/integration/Db/Dialect/Mysql/CreateTableCest.php index 05060d9014c..e907c2fcd94 100644 --- a/tests/integration/Db/Dialect/Mysql/CreateTableCest.php +++ b/tests/integration/Db/Dialect/Mysql/CreateTableCest.php @@ -25,6 +25,7 @@ class CreateTableCest * * @author Phalcon Team * @since 2020-02-27 + * @since 2020-05-02 Changed default null and nullable column definition */ public function dbAdapterPdoMysqlCreateTable(IntegrationTester $I) { @@ -42,21 +43,47 @@ public function dbAdapterPdoMysqlCreateTable(IntegrationTester $I) 'first' => true, ] ), + new Column( + 'numeric_val', + [ + 'type' => Column::TYPE_FLOAT, + 'default' => 21.42, + 'notNull' => true, + ] + ), + new Column( + 'null_int', + [ + 'type' => Column::TYPE_INTEGER, + 'size' => 11, + 'notNull' => false, + 'after' => 'numeric_val', + ] + ), + new Column( + 'created_at', + [ + 'type' => Column::TYPE_TIMESTAMP, + 'default' => "CURRENT_TIMESTAMP", + 'notNull' => true, + 'after' => 'created_at', + ] + ), new Column( 'updated_at', [ 'type' => Column::TYPE_TIMESTAMP, 'default' => "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", - 'notNull' => false, + 'notNull' => true, 'after' => 'created_at', ] ), new Column( - 'numeric_val', + 'deleted_at', [ - 'type' => Column::TYPE_FLOAT, + 'type' => Column::TYPE_TIMESTAMP, 'notNull' => false, - 'after' => 'updated_at', + 'after' => 'created_at', ] ), ], @@ -70,8 +97,11 @@ public function dbAdapterPdoMysqlCreateTable(IntegrationTester $I) $expected = << * @since 2020-02-27 + * @since 2020-05-02 Changed default null and nullable column definition */ public function dbAdapterPdoMysqlModifyColumn(IntegrationTester $I) { @@ -36,21 +37,43 @@ public function dbAdapterPdoMysqlModifyColumn(IntegrationTester $I) [ 'type' => Column::TYPE_TIMESTAMP, 'default' => "CURRENT_TIMESTAMP", - 'notNull' => false, + 'notNull' => true, 'after' => 'created_at', ] - ), new Column( + ), + new Column( 'updated_at', [ 'type' => Column::TYPE_TIMESTAMP, 'default' => "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", - 'notNull' => false, + 'notNull' => true, 'after' => 'created_at', ] ), - 'ALTER TABLE `test` MODIFY `updated_at` TIMESTAMP ' . + 'ALTER TABLE `test` MODIFY `updated_at` TIMESTAMP NOT NULL ' . 'DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `created_at`', ], + [ + new Column( + 'deleted_at', + [ + 'type' => Column::TYPE_TIMESTAMP, + 'default' => "CURRENT_TIMESTAMP", + 'notNull' => false, + 'after' => 'updated_at', + ] + ), + new Column( + 'deleted_at', + [ + 'type' => Column::TYPE_TIMESTAMP, + 'default' => "NULL", + 'notNull' => false, + 'after' => 'updated_at', + ] + ), + 'ALTER TABLE `test` MODIFY `deleted_at` TIMESTAMP NULL DEFAULT NULL AFTER `updated_at`', + ], [ new Column( 'numeric_val', @@ -59,7 +82,8 @@ public function dbAdapterPdoMysqlModifyColumn(IntegrationTester $I) 'notNull' => true, 'after' => 'updated_at', ] - ), new Column( + ), + new Column( 'numeric_val', [ 'type' => Column::TYPE_FLOAT, @@ -68,7 +92,7 @@ public function dbAdapterPdoMysqlModifyColumn(IntegrationTester $I) 'after' => 'updated_at', ] ), - 'ALTER TABLE `test` MODIFY `numeric_val` FLOAT DEFAULT 21.42 AFTER `updated_at`', + 'ALTER TABLE `test` MODIFY `numeric_val` FLOAT NULL DEFAULT 21.42 AFTER `updated_at`', ], ];