From e44b52ff5ba2304743c7d54b28add7d780212710 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Sat, 10 Feb 2024 17:55:51 +0700 Subject: [PATCH] Move and deprecate `Schema` methods (#801) * Move and deprecate `Schema` methods * Add tests * Add lines to CHANGELOG.md [skip ci] * Apply review suggestions * Fix bug with `Quoter` * Move `quoter->setTablePrefix()` to `AbstractPdoConnection` * Fix setTablePrefix() * Remove `QuoterInterface::getRawTableName()` due to BC issue * Update doc [skip ci] * Rearrange tests * Apply fixes from StyleCI * Fix psalm issues * Update CHANGELOG.md [skip ci] --------- Co-authored-by: StyleCI Bot Co-authored-by: Sergei Predvoditelev --- CHANGELOG.md | 5 +++++ src/Driver/Pdo/AbstractPdoConnection.php | 9 ++++++++ src/Helper/DbStringHelper.php | 17 +++++++++++++++- src/Schema/AbstractSchema.php | 12 ++++++++++- src/Schema/Quoter.php | 26 ++++++++++++++++++++++++ src/Schema/SchemaInterface.php | 4 ++++ tests/AbstractQuoterTest.php | 12 +++++++++++ tests/AbstractSchemaTest.php | 12 ----------- tests/Db/Helper/DbStringHelperTest.php | 8 ++++++++ tests/Provider/QuoterProvider.php | 16 +++++++++++++++ 10 files changed, 107 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd63bfa1..6a395afc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ - Enh #795: Allow to use `DMLQueryBuilderInterface::batchInsert()` method with empty columns (@Tigrov) - Enh #794: Add message type to log context (@darkdef) - Enh #802: Minor refactoring of `SchemaCache`, `AbstractPdoCommand` and `AbstractDDLQueryBuilder` (@Tigrov) +- Enh #801: Deprecate `AbstractSchema::normalizeRowKeyCase()` method (@Tigrov) +- Enh #801: Deprecate `SchemaInterface::getRawTableName()` and add `Quoter::getRawTableName()` method (@Tigrov) +- Enh #801: Deprecate `SchemaInterface::isReadQuery()` and add `DbStringHelper::isReadQuery()` method (@Tigrov) +- Enh #801: Remove unnecessary symbol `\\` from `rtrim()` function inside `DbStringHelper::baseName()` method (@Tigrov) +- Bug #801: Fix bug with `Quoter::$tablePrefix` when change `AbstractConnection::$tablePrefix` property (@Tigrov) ## 1.2.0 November 12, 2023 diff --git a/src/Driver/Pdo/AbstractPdoConnection.php b/src/Driver/Pdo/AbstractPdoConnection.php index 477c4b5dc..f6895545d 100644 --- a/src/Driver/Pdo/AbstractPdoConnection.php +++ b/src/Driver/Pdo/AbstractPdoConnection.php @@ -25,6 +25,7 @@ use function array_keys; use function is_string; +use function method_exists; /** * Represents a connection to a database using the PDO (PHP Data Objects) extension. @@ -198,6 +199,14 @@ public function setEmulatePrepare(bool $value): void $this->emulatePrepare = $value; } + public function setTablePrefix(string $value): void + { + parent::setTablePrefix($value); + if ($this->quoter !== null && method_exists($this->quoter, 'setTablePrefix')) { + $this->quoter->setTablePrefix($value); + } + } + /** * Initializes the DB connection. * diff --git a/src/Helper/DbStringHelper.php b/src/Helper/DbStringHelper.php index 2fee97efc..58da98279 100644 --- a/src/Helper/DbStringHelper.php +++ b/src/Helper/DbStringHelper.php @@ -9,6 +9,7 @@ use function mb_strrpos; use function mb_strtolower; use function mb_substr; +use function preg_match; use function preg_replace; use function rtrim; use function str_replace; @@ -38,7 +39,7 @@ final class DbStringHelper */ public static function baseName(string $path): string { - $path = rtrim(str_replace('\\', '/', $path), '/\\'); + $path = rtrim(str_replace('\\', '/', $path), '/'); $position = mb_strrpos($path, '/'); if ($position !== false) { @@ -48,6 +49,20 @@ public static function baseName(string $path): string return $path; } + /** + * Returns a value indicating whether an SQL statement is for read purpose. + * + * @param string $sql The SQL statement. + * + * @return bool Whether an SQL statement is for read purpose. + */ + public static function isReadQuery(string $sql): bool + { + $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i'; + + return preg_match($pattern, $sql) === 1; + } + /** * Returns string representation of a number value without a thousand separators and with dot as decimal separator. * diff --git a/src/Schema/AbstractSchema.php b/src/Schema/AbstractSchema.php index f9f39e400..7e13c88f0 100644 --- a/src/Schema/AbstractSchema.php +++ b/src/Schema/AbstractSchema.php @@ -144,6 +144,7 @@ public function getDataType(mixed $data): int }; } + /** @deprecated Use {@see Quoter::getRawTableName()}. Will be removed in version 2.0.0. */ public function getRawTableName(string $name): string { if (str_contains($name, '{{')) { @@ -315,6 +316,7 @@ public function getTableUniques(string $name, bool $refresh = false): array return is_array($tableUniques) ? $tableUniques : []; } + /** @deprecated Use {@see DbStringHelper::isReadQuery()}. Will be removed in version 2.0.0. */ public function isReadQuery(string $sql): bool { $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i'; @@ -340,6 +342,7 @@ public function refresh(): void */ public function refreshTableSchema(string $name): void { + /** @psalm-suppress DeprecatedMethod */ $rawName = $this->getRawTableName($name); unset($this->tableMetadata[$rawName]); @@ -470,6 +473,7 @@ protected function getSchemaMetadata(string $schema, string $type, bool $refresh */ protected function getTableMetadata(string $name, string $type, bool $refresh = false): mixed { + /** @psalm-suppress DeprecatedMethod */ $rawName = $this->getRawTableName($name); if (!isset($this->tableMetadata[$rawName])) { @@ -532,6 +536,9 @@ protected function getTableTypeMetadata( * @param bool $multiple Whether many rows or a single row passed. * * @return array The normalized row or rows. + * + * @deprecated Use `array_change_key_case($row)` or `array_map('array_change_key_case', $row)`. + * Will be removed in version 2.0.0. */ protected function normalizeRowKeyCase(array $row, bool $multiple): array { @@ -567,7 +574,10 @@ protected function resolveTableName(string $name): TableSchemaInterface */ protected function setTableMetadata(string $name, string $type, mixed $data): void { - /** @psalm-suppress MixedArrayAssignment */ + /** + * @psalm-suppress MixedArrayAssignment + * @psalm-suppress DeprecatedMethod + */ $this->tableMetadata[$this->getRawTableName($name)][$type] = $data; } diff --git a/src/Schema/Quoter.php b/src/Schema/Quoter.php index 1f8aef970..8314c1499 100644 --- a/src/Schema/Quoter.php +++ b/src/Schema/Quoter.php @@ -83,6 +83,27 @@ public function cleanUpTableNames(array $tableNames): array return $cleanedUpTableNames; } + /** + * Returns the actual name of a given table name. + * + * This method will strip off curly brackets from the given table name and replace the percentage character '%' with + * {@see ConnectionInterface::tablePrefix}. + * + * @param string $name The table name to convert. + * + * @return string The real name of the given table name. + */ + public function getRawTableName(string $name): string + { + if (str_contains($name, '{{')) { + $name = preg_replace('/{{(.*?)}}/', '\1', $name); + + return str_replace('%', $this->tablePrefix, $name); + } + + return $name; + } + public function getTableNameParts(string $name, bool $withColumn = false): array { $parts = array_slice(explode('.', $name), -2, 2); @@ -204,6 +225,11 @@ public function quoteValue(mixed $value): mixed return "'" . str_replace("'", "''", addcslashes($value, "\000\032")) . "'"; } + public function setTablePrefix(string $value): void + { + $this->tablePrefix = $value; + } + public function unquoteSimpleColumnName(string $name): string { if (is_string($this->columnQuoteCharacter)) { diff --git a/src/Schema/SchemaInterface.php b/src/Schema/SchemaInterface.php index 5bfa909a0..744ebc555 100644 --- a/src/Schema/SchemaInterface.php +++ b/src/Schema/SchemaInterface.php @@ -284,6 +284,8 @@ public function getDataType(mixed $data): int; * @param string $name The table name to convert. * * @return string The real name of the given table name. + * + * @deprecated Use {@see Quoter::getRawTableName()}. Will be removed in version 2.0.0. */ public function getRawTableName(string $name): string; @@ -367,6 +369,8 @@ public function getTableSchemas(string $schema = '', bool $refresh = false): arr * @param string $sql The SQL statement. * * @return bool Whether an SQL statement is for read purpose. + * + * @deprecated Use {@see DbStringHelper::isReadQuery()}. Will be removed in version 2.0.0. */ public function isReadQuery(string $sql): bool; diff --git a/tests/AbstractQuoterTest.php b/tests/AbstractQuoterTest.php index a5bd9c921..e98a3ab07 100644 --- a/tests/AbstractQuoterTest.php +++ b/tests/AbstractQuoterTest.php @@ -31,6 +31,18 @@ public function testEnsureNameQuoted(string $name, string $expected): void $this->assertSame($expected, $db->getQuoter()->ensureNameQuoted($name)); } + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::rawTableNames + */ + public function testGetRawTableName(string $tableName, string $expected, string $tablePrefix = ''): void + { + $db = $this->getConnection(); + + $db->setTablePrefix($tablePrefix); + + $this->assertSame($expected, $db->getQuoter()->getRawTableName($tableName)); + } + /** * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::tableNameParts */ diff --git a/tests/AbstractSchemaTest.php b/tests/AbstractSchemaTest.php index e868a1a60..3059e606f 100644 --- a/tests/AbstractSchemaTest.php +++ b/tests/AbstractSchemaTest.php @@ -74,18 +74,6 @@ public function testGetDataType(): void fclose($fp); } - public function testIsReadQuery(): void - { - $db = $this->getConnection(); - - $schema = $db->getSchema(); - - $this->assertTrue($schema->isReadQuery('SELECT * FROM tbl')); - $this->assertTrue($schema->isReadQuery('SELECT * FROM tbl WHERE id=1')); - $this->assertTrue($schema->isReadQuery('SELECT * FROM tbl WHERE id=1 LIMIT 1')); - $this->assertTrue($schema->isReadQuery('SELECT * FROM tbl WHERE id=1 LIMIT 1 OFFSET 1')); - } - public function testRefresh(): void { $db = $this->getConnection(); diff --git a/tests/Db/Helper/DbStringHelperTest.php b/tests/Db/Helper/DbStringHelperTest.php index bdb69235b..fb522edcb 100644 --- a/tests/Db/Helper/DbStringHelperTest.php +++ b/tests/Db/Helper/DbStringHelperTest.php @@ -18,6 +18,14 @@ public function testBaseName(): void $this->assertSame('TestCase', DbStringHelper::baseName('TestCase')); } + public function testIsReadQuery(): void + { + $this->assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl')); + $this->assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl WHERE id=1')); + $this->assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl WHERE id=1 LIMIT 1')); + $this->assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl WHERE id=1 LIMIT 1 OFFSET 1')); + } + public static function pascalCaseToIdProvider(): array { return [ diff --git a/tests/Provider/QuoterProvider.php b/tests/Provider/QuoterProvider.php index 74808f2fa..db6181d74 100644 --- a/tests/Provider/QuoterProvider.php +++ b/tests/Provider/QuoterProvider.php @@ -75,6 +75,22 @@ public static function simpleTableNames(): array ]; } + /** + * @return string[][] + */ + public static function rawTableNames(): array + { + return [ + ['table', 'table'], + ['"table"', '"table"'], + ['public.table', 'public.table'], + ['{{table}}', 'table'], + ['{{public}}.{{table}}', 'public.table'], + ['{{%table}}', 'yii_table', 'yii_'], + ['{{public}}.{{%table}}', 'public.yii_table', 'yii_'], + ]; + } + /** * @return string[][] */