From 35556332234736cd59ac5ec66f4e87bc71b16076 Mon Sep 17 00:00:00 2001 From: CedricYii Date: Wed, 27 Jun 2018 21:29:50 +0200 Subject: [PATCH] Fixes #14289: Added `yii\db\Command::executeResetSequence()` to work with Oracle --- framework/CHANGELOG.md | 1 + framework/db/Command.php | 20 ++++++++++++++++++-- framework/db/QueryBuilder.php | 20 ++++++++++++++++++-- framework/db/oci/QueryBuilder.php | 17 ++++++++++++----- framework/test/ActiveFixture.php | 2 +- tests/framework/db/oci/QueryBuilderTest.php | 18 +++++++++--------- 6 files changed, 59 insertions(+), 19 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fe46100f53..70cb03daaa 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.16 under development ------------------------ +- Enh #14289: Added `yii\db\Command::executeResetSequence()` to work with Oracle (CedricYii) - Enh #9133: Added `yii\behaviors\OptimisticLockBehavior` (tunecino) - Bug #16104: Fixed `yii\db\pgsql\QueryBuilder::dropIndex()` to prepend index name with schema name (wapmorgan) - Bug #16193: Fixed `yii\filters\Cors` to not reflect origin header value when configured to wildcard origins (Jianjun Chen) diff --git a/framework/db/Command.php b/framework/db/Command.php index b2fed61100..984204fecb 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -931,10 +931,10 @@ class Command extends Component /** * Creates a SQL command for resetting the sequence value of a table's primary key. * The sequence will be reset such that the primary key of the next new row inserted - * will have the specified value or 1. + * will have the specified value or the maximum existing value +1. * @param string $table the name of the table whose primary key sequence will be reset * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, - * the next new row's primary key will have a value 1. + * the next new row's primary key will have the maximum existing value +1. * @return $this the command object itself * @throws NotSupportedException if this is not supported by the underlying DBMS */ @@ -945,6 +945,22 @@ class Command extends Component return $this->setSql($sql); } + /** + * Executes a db command resetting the sequence value of a table's primary key. + * Reason for execute is that some databases (Oracle) need several queries to do so. + * The sequence is reset such that the primary key of the next new row inserted + * will have the specified value or the maximum existing value +1. + * @param string $table the name of the table whose primary key sequence is reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have the maximum existing value +1. + * @throws NotSupportedException if this is not supported by the underlying DBMS + * @since 2.0.16 + */ + public function executeResetSequence($table, $value = null) + { + return $this->db->getQueryBuilder()->executeResetSequence($table, $value); + } + /** * Builds a SQL command for enabling or disabling integrity check. * @param bool $check whether to turn on or off the integrity check. diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 815a2b3257..ba6331ea78 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -1028,10 +1028,10 @@ class QueryBuilder extends \yii\base\BaseObject /** * Creates a SQL statement for resetting the sequence value of a table's primary key. * The sequence will be reset such that the primary key of the next new row inserted - * will have the specified value or 1. + * will have the specified value or the maximum existing value +1. * @param string $table the name of the table whose primary key sequence will be reset * @param array|string $value the value for the primary key of the next new row inserted. If this is not set, - * the next new row's primary key will have a value 1. + * the next new row's primary key will have the maximum existing value +1. * @return string the SQL statement for resetting sequence * @throws NotSupportedException if this is not supported by the underlying DBMS */ @@ -1040,6 +1040,22 @@ class QueryBuilder extends \yii\base\BaseObject throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.'); } + /** + * Execute a SQL statement for resetting the sequence value of a table's primary key. + * Reason for execute is that some databases (Oracle) need several queries to do so. + * The sequence is reset such that the primary key of the next new row inserted + * will have the specified value or the maximum existing value +1. + * @param string $table the name of the table whose primary key sequence is reset + * @param array|string $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have the maximum existing value +1. + * @throws NotSupportedException if this is not supported by the underlying DBMS + * @since 2.0.16 + */ + public function executeResetSequence($table, $value = null) + { + $this->db->createCommand()->resetSequence($table, $value)->execute(); + } + /** * Builds a SQL statement for enabling or disabling integrity check. * @param bool $check whether to turn on or off the integrity check. diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index 57004fc11c..5f380b13e0 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -138,27 +138,34 @@ EOD; /** * {@inheritdoc} */ - public function resetSequence($table, $value = null) + public function executeResetSequence($table, $value = null) { $tableSchema = $this->db->getTableSchema($table); if ($tableSchema === null) { throw new InvalidArgumentException("Unknown table: $table"); } if ($tableSchema->sequenceName === null) { - return ''; + throw new InvalidArgumentException("There is no sequence associated with table: $table"); } if ($value !== null) { $value = (int) $value; } else { + if (count($tableSchema->primaryKey)>1) { + throw new InvalidArgumentException("Can't reset sequence for composite primary key in table: $table"); + } // use master connection to get the biggest PK value $value = $this->db->useMaster(function (Connection $db) use ($tableSchema) { - return $db->createCommand("SELECT MAX(\"{$tableSchema->primaryKey}\") FROM \"{$tableSchema->name}\"")->queryScalar(); + return $db->createCommand( + 'SELECT MAX("' . $tableSchema->primaryKey[0] . '") FROM "'. $tableSchema->name . '"' + )->queryScalar(); }) + 1; } - return "DROP SEQUENCE \"{$tableSchema->name}_SEQ\";" - . "CREATE SEQUENCE \"{$tableSchema->name}_SEQ\" START WITH {$value} INCREMENT BY 1 NOMAXVALUE NOCACHE"; + //Oracle needs at least two queries to reset sequence (see adding transactions and/or use alter method to avoid grants' issue?) + $this->db->createCommand('DROP SEQUENCE "' . $tableSchema->sequenceName . '"')->execute(); + $this->db->createCommand('CREATE SEQUENCE "' . $tableSchema->sequenceName . '" START WITH ' . $value + . ' INCREMENT BY 1 NOMAXVALUE NOCACHE')->execute(); } /** diff --git a/framework/test/ActiveFixture.php b/framework/test/ActiveFixture.php index b9dfcc4dca..6d93b79c01 100644 --- a/framework/test/ActiveFixture.php +++ b/framework/test/ActiveFixture.php @@ -125,7 +125,7 @@ class ActiveFixture extends BaseActiveFixture $table = $this->getTableSchema(); $this->db->createCommand()->delete($table->fullName)->execute(); if ($table->sequenceName !== null) { - $this->db->createCommand()->resetSequence($table->fullName, 1)->execute(); + $this->db->createCommand()->executeResetSequence($table->fullName, 1); } } diff --git a/tests/framework/db/oci/QueryBuilderTest.php b/tests/framework/db/oci/QueryBuilderTest.php index 930eff4e2e..a923334ced 100644 --- a/tests/framework/db/oci/QueryBuilderTest.php +++ b/tests/framework/db/oci/QueryBuilderTest.php @@ -106,19 +106,19 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest $this->assertEquals($this->replaceQuotes($expected), $sql); } - public function testResetSequence() + public function testExecuteResetSequence() { + $db = $this->getConnection(); $qb = $this->getQueryBuilder(); + $sqlResult = "SELECT last_number FROM user_sequences WHERE sequence_name = 'item_SEQ'"; - $expected = 'DROP SEQUENCE "item_SEQ";' - . 'CREATE SEQUENCE "item_SEQ" START WITH 6 INCREMENT BY 1 NOMAXVALUE NOCACHE'; - $sql = $qb->resetSequence('item'); - $this->assertEquals($expected, $sql); + $qb->executeResetSequence('item'); + $result = $db->createCommand($sqlResult)->queryScalar(); + $this->assertEquals(6, $result); - $expected = 'DROP SEQUENCE "item_SEQ";' - . 'CREATE SEQUENCE "item_SEQ" START WITH 4 INCREMENT BY 1 NOMAXVALUE NOCACHE'; - $sql = $qb->resetSequence('item', 4); - $this->assertEquals($expected, $sql); + $qb->executeResetSequence('item', 4); + $result = $db->createCommand($sqlResult)->queryScalar(); + $this->assertEquals(4, $result); } public function likeConditionProvider()