diff --git a/src/Schema.php b/src/Schema.php index 23d46723..2532b1b8 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,85 @@ 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() ?? ''; + + if (!preg_match('#^(?:[^(]*--[^\n]*|[^(]*/\*.*?\*/)*[^(]*\((.*)\)[^)]*$#s', $sql, $matches)) { + return; + } + + $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. + * + * @throws Exception + * @throws InvalidConfigException + * @throws Throwable + * @return string The `CREATE TABLE` SQL string. + */ + 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..071c2740 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -359,4 +359,24 @@ 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('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 c55b2d4c..90879882 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,17 @@ 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 + price decimal(10,2), -- USD + `name` varchar(100) DEFAULT 'Pan, Peter' -- Column comment + -- second line + /* third line */ +); + CREATE VIEW "animal_view" AS SELECT * FROM "animal"; INSERT INTO "animal" ("type") VALUES ('yiiunit\data\ar\Cat');