Skip to content

Commit

Permalink
Move method resetSequence() from QueryBuilder::class to `Schema::…
Browse files Browse the repository at this point in the history
…class`. (#101)
  • Loading branch information
terabytesoftw authored Sep 16, 2024
1 parent 7336a27 commit ab7fc7d
Show file tree
Hide file tree
Showing 35 changed files with 886 additions and 424 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Yii Core version 2 Change Log
- Enh #99: Add the logic to check for table existence and auto-incremental column in `getNextAutoIncrementValue()` (@terabytesoftw)
- Enh #98: Add support for identity columns, fix `sequenceName` and `autoIncrement` in `ColumnSchema::class` for `Oci` (@terabytesoftw)
- Bug #99: Fix `sequenceName` and `autoIncrement` in `TableSchema::class` in `PgSQL` (@terabytesoftw)
- Enh #100: Move method `resetSequence()` from `QueryBuilder::class` to `Schema::class` (@terabytesoftw)

Yii Framework 2 Change Log
==========================
Expand Down
33 changes: 0 additions & 33 deletions src/db/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -978,39 +978,6 @@ public function dropDefaultValue($name, $table)
return $this->setSql($sql)->requireTableSchemaRefresh($table);
}

/**
* 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 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 the maximum existing value +1.
* @return $this the command object itself
* @throws NotSupportedException if this is not supported by the underlying DBMS
*/
public function resetSequence($table, $value = null)
{
$sql = $this->db->getQueryBuilder()->resetSequence($table, $value);

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.
Expand Down
31 changes: 0 additions & 31 deletions src/db/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -1098,37 +1098,6 @@ public function dropDefaultValue($name, $table)
throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
}

/**
* 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 the maximum existing value +1.
* @param string $table the name of the table whose primary key sequence will be reset
* @param array|string|null $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.
* @return string the SQL statement for resetting sequence
* @throws NotSupportedException if this is not supported by the underlying DBMS
*/
public function resetSequence($table, $value = null)
{
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|null $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.
Expand Down
16 changes: 6 additions & 10 deletions src/db/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,18 @@
*
* Schema represents the database schema information that is DBMS specific.
*
* @property-read string $lastInsertID The row ID of the last row inserted, or the last value retrieved from
* the sequence object.
* @property-read string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
* sequence object.
* @property-read QueryBuilder $queryBuilder The query builder for this connection.
* @property-read string[] $schemaNames All schema names in the database, except system schemas.
* @property-read string $serverVersion Server version as a string.
* @property-read string[] $tableNames All table names in the database.
* @property-read TableSchema[] $tableSchemas The metadata for all tables in the database. Each array element
* is an instance of [[TableSchema]] or its child class.
* @property-write string $transactionIsolationLevel The transaction isolation level to use for this
* transaction. This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]],
* [[Transaction::REPEATABLE_READ]] and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific
* syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0
* @property-write string $transactionIsolationLevel The transaction isolation level to use for this transaction.
* This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]],
* [[Transaction::REPEATABLE_READ]] and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax
* to be used after `SET TRANSACTION ISOLATION LEVEL`.
*/
abstract class Schema extends BaseObject
{
Expand Down
2 changes: 1 addition & 1 deletion src/db/TableSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class TableSchema extends BaseObject
/**
* @var array|string|null sequence name for the primary keys. Null if no sequence.
*
* In `PgSQL`, an associative array is used, with the column name as key and the sequence name as value.
* In `PostgreSQL`, an associative array is used, with the column name as key and the sequence name as value.
*/
public array|string|null $sequenceName = null;
/**
Expand Down
30 changes: 0 additions & 30 deletions src/db/mssql/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,36 +187,6 @@ public function dropDefaultValue($name, $table)
. ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
}

/**
* 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.
* @param string $tableName 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.
* @return string the SQL statement for resetting sequence
* @throws InvalidArgumentException if the table does not exist or there is no sequence associated with the table.
*/
public function resetSequence($tableName, $value = null)
{
$table = $this->db->getTableSchema($tableName);
if ($table !== null && $table->sequenceName !== null) {
$tableName = $this->db->quoteTableName($tableName);
if ($value === null) {
$key = $this->db->quoteColumnName(reset($table->primaryKey));
$value = "(SELECT COALESCE(MAX({$key}),0) FROM {$tableName})+1";
} else {
$value = (int) $value;
}

return "DBCC CHECKIDENT ('{$tableName}', RESEED, {$value})";
} elseif ($table === null) {
throw new InvalidArgumentException("Table not found: $tableName");
}

throw new InvalidArgumentException("There is not sequence associated with table '$tableName'.");
}

/**
* Builds a SQL statement for enabling or disabling integrity check.
* @param bool $check whether to turn on or off the integrity check.
Expand Down
41 changes: 39 additions & 2 deletions src/db/mssql/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace yii\db\mssql;

use Yii;
use yii\base\InvalidArgumentException;
use yii\db\CheckConstraint;
use yii\db\Constraint;
use yii\db\ConstraintFinderInterface;
Expand Down Expand Up @@ -441,8 +442,8 @@ protected function findColumns(TableSchema $table): bool
}
}

if ($column->isPrimaryKey && $column->autoIncrement) {
$table->sequenceName = '';
if ($column->autoIncrement) {
$table->sequenceName = $column->name;
}

$table->columns[$column->name] = $column;
Expand Down Expand Up @@ -723,4 +724,40 @@ public function createColumnSchemaBuilder($type, $length = null)
{
return Yii::createObject(ColumnSchemaBuilder::className(), [$type, $length, $this->db]);
}

/**
* {@inheritdoc}
*/
public function resetSequence(string $tableName, int|null $value = null): int
{
$tableSchema = $this->db->getTableSchema($tableName);

if ($tableSchema === null) {
throw new InvalidArgumentException("Table not found: '$tableName'.");
}

if (empty($tableSchema->primaryKey) || empty($tableSchema->sequenceName)) {
throw new InvalidArgumentException(
"There is no primary key or sequence associated with table '$tableSchema->fullName'."
);
}

if (count($tableSchema->primaryKey) > 1) {
throw new InvalidArgumentException('This method does not support tables with composite primary keys.');
}

$columnPK = reset($tableSchema->primaryKey);

if ($value === null) {
$value = $this->getNextAutoIncrementValue($tableSchema->fullName, $columnPK);
}

$sql = <<<SQL
DBCC CHECKIDENT ({$this->quoteTableName($tableSchema->fullName)}, RESEED, {$value})
SQL;

$this->db->createCommand($sql)->execute();

return $value;
}
}
30 changes: 0 additions & 30 deletions src/db/mysql/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,36 +145,6 @@ public function dropUnique($name, $table)
return $this->dropIndex($name, $table);
}

/**
* 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.
* @param string $tableName 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.
* @return string the SQL statement for resetting sequence
* @throws InvalidArgumentException if the table does not exist or there is no sequence associated with the table.
*/
public function resetSequence($tableName, $value = null)
{
$table = $this->db->getTableSchema($tableName);
if ($table !== null && $table->sequenceName !== null) {
$tableName = $this->db->quoteTableName($tableName);
if ($value === null) {
$key = reset($table->primaryKey);
$value = $this->db->createCommand("SELECT MAX(`$key`) FROM $tableName")->queryScalar() + 1;
} else {
$value = (int) $value;
}

return "ALTER TABLE $tableName AUTO_INCREMENT=$value";
} elseif ($table === null) {
throw new InvalidArgumentException("Table not found: $tableName");
}

throw new InvalidArgumentException("There is no sequence associated with table '$tableName'.");
}

/**
* Builds a SQL statement for enabling or disabling integrity check.
* @param bool $check whether to turn on or off the integrity check.
Expand Down
55 changes: 54 additions & 1 deletion src/db/mysql/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace yii\db\mysql;

use Yii;
use yii\base\InvalidArgumentException;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\db\CheckConstraint;
Expand Down Expand Up @@ -386,7 +387,7 @@ protected function findColumns($table)
if ($column->isPrimaryKey) {
$table->primaryKey[] = $column->name;
if ($column->autoIncrement) {
$table->sequenceName = '';
$table->sequenceName = $column->name;
}
}
}
Expand Down Expand Up @@ -517,6 +518,58 @@ public function createColumnSchemaBuilder($type, $length = null)
return Yii::createObject(ColumnSchemaBuilder::className(), [$type, $length, $this->db]);
}

/**
* {@inheritdoc}
*
* Note:
* - `MySQL` does not support sequences as in other databases like `Oracle` or `PostgreSQL`. Instead, it uses
* auto-increment columns to generate unique sequential values for a primary key column automatically.
* Auto-increment columns are often used as a replacement for sequences to handle the generation of unique
* identifiers.
* - `MySQL` not support value negative for auto increment column.
* - `MySQL` auto-increment value must be greater than zero.
*/
public function resetSequence(string $tableName, int|null $value = null): int
{
if ($value < 0) {
throw new InvalidArgumentException("The value must be greater than '0'.");
}

if ($value === 0) {
$value = 1;
}

$tableSchema = $this->db->getTableSchema($tableName);

if ($tableSchema === null) {
throw new InvalidArgumentException("Table not found: '$tableName'.");
}

if (empty($tableSchema->primaryKey)) {
throw new InvalidArgumentException(
"There is no primary key or sequence associated with table '$tableSchema->fullName'."
);
}

if (count($tableSchema->primaryKey) > 1) {
throw new InvalidArgumentException('This method does not support tables with composite primary keys.');
}

$columnPK = reset($tableSchema->primaryKey);

if ($value === null) {
$value = $this->db->getSchema()->getNextAutoIncrementValue($tableSchema->fullName, $columnPK);
}

$sql = <<<SQL
ALTER TABLE {$this->db->quoteTableName($tableSchema->fullName)} AUTO_INCREMENT={$value}
SQL;

$this->db->createCommand($sql)->execute();

return $value;
}

/**
* @return bool whether the version of the MySQL being used is older than 5.1.
* @throws InvalidConfigException
Expand Down
33 changes: 0 additions & 33 deletions src/db/oci/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,39 +138,6 @@ public function dropIndex($name, $table)
return 'DROP INDEX ' . $this->db->quoteTableName($name);
}

/**
* {@inheritdoc}
*/
public function executeResetSequence($table, $value = null)
{
$tableSchema = $this->db->getTableSchema($table);
if ($tableSchema === null) {
throw new InvalidArgumentException("Unknown table: $table");
}
if ($tableSchema->sequenceName === null) {
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[0] . '") FROM "' . $tableSchema->name . '"'
)->queryScalar();
}) + 1;
}

//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();
}

/**
* {@inheritdoc}
*/
Expand Down
Loading

0 comments on commit ab7fc7d

Please sign in to comment.