diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index bece4b3ae1..6055f546cf 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 Change Log ----------------------- - Bug #7305: Logging of Exception objects resulted in failure of the logger i.e. no logs being written (cebe) +- Bug #7374: Use proper INSERT syntax with default values when no values are specified (nineinchnick) - Bug #7707: client-side `trim` validator now passes the trimmed value to subsequent validators (nkovacs) - Bug #8322: `yii\behaviors\TimestampBehavior::touch()` now throws an exception if owner is new record (klimov-paul) - Bug #8451: `yii\i18n\Formatter` did not allow negative unix timestamps as input for date formatting (cebe) diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index d40ea47fd8..9f9535979b 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -449,11 +449,6 @@ class ActiveRecord extends BaseActiveRecord return false; } $values = $this->getDirtyAttributes($attributes); - if (empty($values)) { - foreach ($this->getPrimaryKey(true) as $key => $value) { - $values[$key] = $value; - } - } if (($primaryKeys = static::getDb()->schema->insert($this->tableName(), $values)) === false) { return false; } diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index fb6c1f99dc..d5da6326fe 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -153,8 +153,8 @@ class QueryBuilder extends \yii\base\Object } return 'INSERT INTO ' . $schema->quoteTableName($table) - . ' (' . implode(', ', $names) . ') VALUES (' - . implode(', ', $placeholders) . ')'; + . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' DEFAULT VALUES'); } /** diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php index f69c5a4a2b..14f946d7a2 100644 --- a/framework/db/mysql/QueryBuilder.php +++ b/framework/db/mysql/QueryBuilder.php @@ -164,4 +164,43 @@ class QueryBuilder extends \yii\db\QueryBuilder return $sql; } + + /** + * @inheritdoc + */ + public function insert($table, $columns, &$params) + { + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + $names = []; + $placeholders = []; + foreach ($columns as $name => $value) { + $names[] = $schema->quoteColumnName($name); + if ($value instanceof Expression) { + $placeholders[] = $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $placeholders[] = $phName; + $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; + } + } + if (empty($names) && $tableSchema !== null) { + $columns = !empty($tableSchema->primaryKey) ? $tableSchema->primaryKey : reset($tableSchema->columns)->name; + foreach ($columns as $name) { + $names[] = $schema->quoteColumnName($name); + $placeholders[] = 'DEFAULT'; + } + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' DEFAULT VALUES'); + } } diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index cddd86fa43..ff1f1afdbd 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -161,6 +161,45 @@ EOD; return $sql; } + /** + * @inheritdoc + */ + public function insert($table, $columns, &$params) + { + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + $names = []; + $placeholders = []; + foreach ($columns as $name => $value) { + $names[] = $schema->quoteColumnName($name); + if ($value instanceof Expression) { + $placeholders[] = $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $placeholders[] = $phName; + $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; + } + } + if (empty($names) && $tableSchema !== null) { + $columns = !empty($tableSchema->primaryKey) ? $tableSchema->primaryKey : reset($tableSchema->columns)->name; + foreach ($columns as $name) { + $names[] = $schema->quoteColumnName($name); + $placeholders[] = 'DEFAULT'; + } + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '') + . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' DEFAULT VALUES'); + } + /** * Generates a batch INSERT SQL statement. * For example, diff --git a/tests/data/oci.sql b/tests/data/oci.sql index dfd14b578e..c3d571fb59 100644 --- a/tests/data/oci.sql +++ b/tests/data/oci.sql @@ -29,6 +29,7 @@ BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "category_SEQ"'; EXCEPTION WHEN OTHERS TH BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "item_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;-- BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "order_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;-- BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "order_with_null_fk_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;-- +BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "null_values_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;-- BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "bool_values_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;-- BEGIN EXECUTE IMMEDIATE 'DROP SEQUENCE "animal_SEQ"'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2289 THEN RAISE; END IF; END;-- @@ -127,6 +128,7 @@ CREATE TABLE "null_values" ( "stringcol" varchar2(32) DEFAULT NULL, CONSTRAINT "null_values_PK" PRIMARY KEY ("id") ENABLE ); +CREATE SEQUENCE "null_values_SEQ"; CREATE TABLE "type" ( "int_col" integer NOT NULL, @@ -220,6 +222,11 @@ CREATE TRIGGER "order_with_null_fk_TRG" BEFORE INSERT ON "order_with_null_fk" FO END COLUMN_SEQUENCES; END; / +CREATE TRIGGER "null_values_TRG" BEFORE INSERT ON "null_values" FOR EACH ROW BEGIN <> BEGIN + IF INSERTING AND :NEW."id" IS NULL THEN SELECT "null_values_SEQ".NEXTVAL INTO :NEW."id" FROM SYS.DUAL; END IF; +END COLUMN_SEQUENCES; +END; +/ CREATE TRIGGER "bool_values_TRG" BEFORE INSERT ON "bool_values" FOR EACH ROW BEGIN <> BEGIN IF INSERTING AND :NEW."id" IS NULL THEN SELECT "bool_values_SEQ".NEXTVAL INTO :NEW."id" FROM SYS.DUAL; END IF; END COLUMN_SEQUENCES; diff --git a/tests/data/postgres.sql b/tests/data/postgres.sql index e5b201f51c..feceea4cc6 100644 --- a/tests/data/postgres.sql +++ b/tests/data/postgres.sql @@ -103,7 +103,7 @@ CREATE TABLE "composite_fk" ( ); CREATE TABLE "null_values" ( - id INT NOT NULL, + id serial NOT NULL, var1 INT NULL, var2 INT NULL, var3 INT DEFAULT NULL, diff --git a/tests/data/sqlite.sql b/tests/data/sqlite.sql index 68fb4a81c4..a0349a4b69 100644 --- a/tests/data/sqlite.sql +++ b/tests/data/sqlite.sql @@ -87,7 +87,7 @@ CREATE TABLE "composite_fk" ( ); CREATE TABLE "null_values" ( - id INTEGER UNSIGNED PRIMARY KEY NOT NULL, + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, var1 INTEGER UNSIGNED, var2 INTEGER, var3 INTEGER DEFAULT NULL, diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index a6b3f937a5..bb0a61eace 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -687,7 +687,7 @@ class ActiveRecordTest extends DatabaseTestCase $model->updateCounters(['status' => 1]); $this->assertEquals(1, $model->status); } - + public function testPopulateRecordCallWhenQueryingOnParentClass() { (new Cat())->save(false); @@ -699,4 +699,11 @@ class ActiveRecordTest extends DatabaseTestCase $animal = Animal::find()->where(['type' => Cat::className()])->one(); $this->assertEquals('meow', $animal->getDoes()); } + + public function testSaveEmpty() + { + $record = new NullValues; + $this->assertTrue($record->save(false)); + $this->assertEquals(1, $record->id); + } }