From 17a84b140f0d3b1dfb40b32cd10b45bac096b403 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 16 Jan 2024 16:31:37 +0700 Subject: [PATCH 1/6] Support table and column comments --- src/Schema.php | 90 +++++++++++++++++++++++++++++--- tests/SchemaTest.php | 19 +++++++ tests/Support/Fixture/sqlite.sql | 11 ++++ 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 23d46723..750fbf8e 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -31,6 +31,8 @@ use function strncasecmp; use function strtolower; +use const PREG_SET_ORDER; + /** * Implements the SQLite Server specific schema, supporting SQLite 3.3.0 or higher. * @@ -155,8 +157,11 @@ protected function loadTableSchema(string $name): TableSchemaInterface|null $table->name($name); $table->fullName($name); + $table->createSql($this->getCreateTableSql($name)); + $this->findTableComment($table); if ($this->findColumns($table)) { + $this->findComments($table); $this->findConstraints($table); return $table; @@ -294,12 +299,7 @@ protected function loadTableUniques(string $tableName): array */ protected function loadTableChecks(string $tableName): array { - $sql = $this->db->createCommand( - 'SELECT `sql` FROM `sqlite_master` WHERE name = :tableName', - [':tableName' => $tableName], - )->queryScalar(); - - $sql = ($sql === false || $sql === null) ? '' : (string) $sql; + $sql = $this->getCreateTableSql($tableName); $code = (new SqlTokenizer($sql))->tokenize(); $pattern = (new SqlTokenizer('any CREATE any TABLE any()'))->tokenize(); @@ -741,6 +741,84 @@ protected function getCacheTag(): string return md5(serialize(array_merge([self::class], $this->generateCacheKey()))); } + /** + * @throws Exception + * @throws InvalidConfigException + * @throws Throwable + */ + private function findTableComment(TableSchemaInterface $tableSchema): void + { + $sql = $tableSchema->getCreateSql(); + + if (preg_match('#^[^(]+?((?:\s*--[^\n]*|\s*/\*.*?\*/)+)\s*\(#', $sql, $matches) === 1) { + $comment = $this->filterComment($matches[1]); + $tableSchema->comment($comment); + } + } + + /** + * @throws Exception + * @throws InvalidConfigException + * @throws Throwable + */ + private function findComments(TableSchemaInterface $tableSchema): void + { + $sql = $tableSchema->getCreateSql(); + + preg_match('#^(?:[^(]*--[^\n]*|[^(]*/\*.*?\*/)*[^(]*\((.*)\)[^)]*$#s', $sql, $matches); + + $columnsDefinition = $matches[1]; + + $identifierPattern = '(?:([`"])([^`"]+)\1|\[([^\]]+)\]|([A-Za-z_]\w*))'; + $notCommaPattern = '(?:[^,]|\([^()]+\))*?'; + $commentPattern = '(?:\s*--[^\n]*|\s*/\*.*?\*/)'; + + $pattern = "#$identifierPattern\s*$notCommaPattern,?($commentPattern+)#"; + + if (preg_match_all($pattern, $columnsDefinition, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $columnName = $match[2] ?: $match[3] ?: $match[4]; + $comment = $this->filterComment($match[5] ?: $match[6]); + + $tableSchema->getColumn($columnName)?->comment($comment); + } + } + } + + private function filterComment(string $comment): string + { + preg_match_all('#--([^\n]*)|/\*(.*?)\*/#', $comment, $matches, PREG_SET_ORDER); + + $lines = []; + + foreach ($matches as $match) { + $lines[] = trim($match[1] ?: $match[2]); + } + + return implode("\n", $lines); + } + + /** + * Returns the `CREATE TABLE` SQL string. + * + * @param string $name The table name. + * + * @return string The `CREATE TABLE` SQL string. + * + * @throws Exception + * @throws InvalidConfigException + * @throws Throwable + */ + private function getCreateTableSql(string $name): string + { + $sql = $this->db->createCommand( + 'SELECT `sql` FROM `sqlite_master` WHERE `name` = :tableName', + [':tableName' => $name], + )->queryScalar(); + + return (string)$sql; + } + /** * @throws Throwable */ diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index e15c3f1b..ca2f5484 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -359,4 +359,23 @@ public function testNotConnectionPDO(): void $schema->refreshTableSchema('customer'); } + + public function testTableComment(): void + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $table = $schema->getTableSchema('comment'); + + $this->assertSame("Table comment\nsecond line\nthird line", $table->getComment()); + } + + public function testColumnComments(): void + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $table = $schema->getTableSchema('comment'); + + $this->assertSame('primary key', $table->getColumn('id')->getComment()); + $this->assertSame("Column comment\nsecond line\nthird line", $table->getColumn('name')->getComment()); + } } diff --git a/tests/Support/Fixture/sqlite.sql b/tests/Support/Fixture/sqlite.sql index c55b2d4c..d38ed56e 100644 --- a/tests/Support/Fixture/sqlite.sql +++ b/tests/Support/Fixture/sqlite.sql @@ -15,6 +15,7 @@ DROP TABLE IF EXISTS "negative_default_values"; DROP TABLE IF EXISTS "animal"; DROP TABLE IF EXISTS "default_pk"; DROP TABLE IF EXISTS "notauto_pk"; +DROP TABLE IF EXISTS "comment"; DROP VIEW IF EXISTS "animal_view"; DROP TABLE IF EXISTS "T_constraints_4"; DROP TABLE IF EXISTS "T_constraints_3"; @@ -173,6 +174,16 @@ CREATE TABLE "timestamp_default" ( timestamp_text TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- STRICT +CREATE TABLE `comment` -- Table comment +-- second line +/* third line */ +( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, -- primary key + `name` varchar(100) -- Column comment + -- second line + /* third line */ +); + CREATE VIEW "animal_view" AS SELECT * FROM "animal"; INSERT INTO "animal" ("type") VALUES ('yiiunit\data\ar\Cat'); From 243044cc80b47f58de0625be4190bb8068199fdd Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 16 Jan 2024 09:32:14 +0000 Subject: [PATCH 2/6] Apply fixes from StyleCI --- src/Schema.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 750fbf8e..1487fc7c 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -803,11 +803,10 @@ private function filterComment(string $comment): string * * @param string $name The table name. * - * @return string The `CREATE TABLE` SQL string. - * * @throws Exception * @throws InvalidConfigException * @throws Throwable + * @return string The `CREATE TABLE` SQL string. */ private function getCreateTableSql(string $name): string { From 47c6cc7ecd1b9511082bbacb843bf5c6e4ebc5e9 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 16 Jan 2024 09:35:45 +0000 Subject: [PATCH 3/6] Apply Rector changes (CI) --- src/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Schema.php b/src/Schema.php index 1487fc7c..b8b1e6d1 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -777,7 +777,7 @@ private function findComments(TableSchemaInterface $tableSchema): void if (preg_match_all($pattern, $columnsDefinition, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { - $columnName = $match[2] ?: $match[3] ?: $match[4]; + $columnName = ($match[2] ?: $match[3]) ?: $match[4]; $comment = $this->filterComment($match[5] ?: $match[6]); $tableSchema->getColumn($columnName)?->comment($comment); From cb874bc42d8de884284ee1d11af93224b8167c1e Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 16 Jan 2024 16:41:29 +0700 Subject: [PATCH 4/6] Fix test issues --- src/Schema.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Schema.php b/src/Schema.php index b8b1e6d1..931b2f46 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -765,7 +765,9 @@ private function findComments(TableSchemaInterface $tableSchema): void { $sql = $tableSchema->getCreateSql(); - preg_match('#^(?:[^(]*--[^\n]*|[^(]*/\*.*?\*/)*[^(]*\((.*)\)[^)]*$#s', $sql, $matches); + if (!preg_match('#^(?:[^(]*--[^\n]*|[^(]*/\*.*?\*/)*[^(]*\((.*)\)[^)]*$#s', $sql, $matches)) { + return; + } $columnsDefinition = $matches[1]; From ce91d21e93d8a0195c19fd3cf1f2180ef0c1ef77 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 16 Jan 2024 16:44:47 +0700 Subject: [PATCH 5/6] Fix psalm issues --- src/Schema.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 931b2f46..1ddfab06 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -748,7 +748,7 @@ protected function getCacheTag(): string */ private function findTableComment(TableSchemaInterface $tableSchema): void { - $sql = $tableSchema->getCreateSql(); + $sql = $tableSchema->getCreateSql() ?? ''; if (preg_match('#^[^(]+?((?:\s*--[^\n]*|\s*/\*.*?\*/)+)\s*\(#', $sql, $matches) === 1) { $comment = $this->filterComment($matches[1]); @@ -763,7 +763,7 @@ private function findTableComment(TableSchemaInterface $tableSchema): void */ private function findComments(TableSchemaInterface $tableSchema): void { - $sql = $tableSchema->getCreateSql(); + $sql = $tableSchema->getCreateSql() ?? ''; if (!preg_match('#^(?:[^(]*--[^\n]*|[^(]*/\*.*?\*/)*[^(]*\((.*)\)[^)]*$#s', $sql, $matches)) { return; From 18146e239c138788cc899aa8b09cee82219cb727 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 18 Jan 2024 17:11:21 +0700 Subject: [PATCH 6/6] Improve regex, add tests --- src/Schema.php | 4 ++-- tests/SchemaTest.php | 1 + tests/Support/Fixture/sqlite.sql | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 1ddfab06..2532b1b8 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -771,8 +771,8 @@ private function findComments(TableSchemaInterface $tableSchema): void $columnsDefinition = $matches[1]; - $identifierPattern = '(?:([`"])([^`"]+)\1|\[([^\]]+)\]|([A-Za-z_]\w*))'; - $notCommaPattern = '(?:[^,]|\([^()]+\))*?'; + $identifierPattern = '(?:([`"])([^`"]+)\1|\[([^\]]+)\]|([a-zA-Z_]\w*))'; + $notCommaPattern = "(?:[^,]|\([^()]+\)|'[^']+')*?"; $commentPattern = '(?:\s*--[^\n]*|\s*/\*.*?\*/)'; $pattern = "#$identifierPattern\s*$notCommaPattern,?($commentPattern+)#"; diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index ca2f5484..071c2740 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -376,6 +376,7 @@ public function testColumnComments(): void $table = $schema->getTableSchema('comment'); $this->assertSame('primary key', $table->getColumn('id')->getComment()); + $this->assertSame('USD', $table->getColumn('price')->getComment()); $this->assertSame("Column comment\nsecond line\nthird line", $table->getColumn('name')->getComment()); } } diff --git a/tests/Support/Fixture/sqlite.sql b/tests/Support/Fixture/sqlite.sql index d38ed56e..90879882 100644 --- a/tests/Support/Fixture/sqlite.sql +++ b/tests/Support/Fixture/sqlite.sql @@ -179,7 +179,8 @@ CREATE TABLE `comment` -- Table comment /* third line */ ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, -- primary key - `name` varchar(100) -- Column comment + price decimal(10,2), -- USD + `name` varchar(100) DEFAULT 'Pan, Peter' -- Column comment -- second line /* third line */ );