diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml new file mode 100644 index 0000000..3dc0cd1 --- /dev/null +++ b/.github/workflows/cs.yml @@ -0,0 +1,68 @@ +name: CS + +on: + # Run on all relevant pushes (except to main) and on all relevant pull requests. + push: + paths: + - '**.php' + - 'composer.json' + - 'composer.lock' + - '.phpcs.xml.dist' + - 'phpcs.xml.dist' + - '.github/workflows/cs.yml' + pull_request: + paths: + - '**.php' + - 'composer.json' + - 'composer.lock' + - '.phpcs.xml.dist' + - 'phpcs.xml.dist' + - '.github/workflows/cs.yml' + # Allow manually triggering the workflow. + workflow_dispatch: + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + # The concurrency group contains the workflow name and the branch name. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checkcs: + name: 'Check code style' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@main + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: cs2pr + + # Validate the composer.json file. + # @link https://getcomposer.org/doc/03-cli.md#validate + - name: Validate Composer installation + run: composer validate --no-check-all + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies + uses: ramsey/composer-install@v2 + with: + # Bust the cache at least once a month - output format: YYYY-MM. + custom-cache-suffix: $(date -u "+%Y-%m") + + # Check the codestyle of the files. + # The results of the CS check will be shown inline in the PR via the CS2PR tool. + # @link https://github.com/staabm/annotate-pull-request-from-checkstyle/ + - name: Check PHP code style + id: phpcs + run: composer check-cs -- --no-cache --report-full --report-checkstyle=./phpcs-report.xml + + - name: Show PHPCS results in PR + if: ${{ always() && steps.phpcs.outcome == 'failure' }} + run: cs2pr ./phpcs-report.xml diff --git a/composer.json b/composer.json index 7b25a2d..b64928b 100644 --- a/composer.json +++ b/composer.json @@ -13,13 +13,26 @@ "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", "squizlabs/php_codesniffer": "^3.7", - "wp-coding-standards/wpcs": "^3.0", - "phpcompatibility/phpcompatibility-wp": "^2.1", + "wp-coding-standards/wpcs": "^3.1", + "phpcompatibility/phpcompatibility-wp": "*", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.10", + "szepeviktor/phpstan-wordpress": "^1.3", + "phpstan/extension-installer": "^1.3", "yoast/phpunit-polyfills": "2.0.0" }, "config": { "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true } + }, + "scripts": { + "check-cs": [ + "@php ./vendor/bin/phpcs" + ], + "fix-cs": [ + "@php ./vendor/bin/phpcbf" + ] } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 557065e..7a3b78b 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -5,11 +5,11 @@ - - + @@ -20,50 +20,172 @@ - . + - - + . /vendor/* - + /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - /wp-includes/*.php + + + + + + + + + + warning + + /tests/phpunit/* - - /wp-includes/*.php + + warning + + + warning + + + warning + + + warning - - - 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - 0 + + + + + + + + + + + + + + + + + + + + + + - - 0 + + + + + + /wp-includes/sqlite/class-wp-sqlite-translator.php + + + + + * - - + + /src/wp-includes/sqlite/*\.php /tests/* - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - + + + /tests/* + + /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - + + /tests/* + + /tests/* - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php /tests/* - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php diff --git a/tests/WP_SQLite_Translator_Tests.php b/tests/WP_SQLite_Translator_Tests.php index e6399c3..f30c6f6 100644 --- a/tests/WP_SQLite_Translator_Tests.php +++ b/tests/WP_SQLite_Translator_Tests.php @@ -33,17 +33,17 @@ public function setUp(): void { $this->engine = new WP_SQLite_Translator( $this->sqlite ); $this->engine->query( "CREATE TABLE _options ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" + ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, + option_name TEXT NOT NULL default '', + option_value TEXT NOT NULL default '' + );" ); $this->engine->query( "CREATE TABLE _dates ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value DATE NOT NULL - );" + ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, + option_name TEXT NOT NULL default '', + option_value DATE NOT NULL + );" ); } @@ -202,7 +202,7 @@ public function testUpdateWithoutWhereButWithSubSelect() { ); $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); + ); $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-27 10:08:48');" ); @@ -210,7 +210,7 @@ public function testUpdateWithoutWhereButWithSubSelect() { "UPDATE _dates SET option_value = (SELECT option_value from _options WHERE option_name = 'User 0000019')" ); $this->assertSame( 2, $return, 'UPDATE query did not return 2 when two row were changed' ); - + $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first'" ); $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second'" ); $this->assertEquals( 'second', $result1[0]->option_value ); @@ -220,7 +220,7 @@ public function testUpdateWithoutWhereButWithSubSelect() { public function testUpdateWithoutWhereButWithLimit() { $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); + ); $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-27 10:08:48');" ); @@ -228,7 +228,7 @@ public function testUpdateWithoutWhereButWithLimit() { "UPDATE _dates SET option_value = 'second' LIMIT 1" ); $this->assertSame( 1, $return, 'UPDATE query did not return 2 when two row were changed' ); - + $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first'" ); $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second'" ); $this->assertEquals( 'second', $result1[0]->option_value ); @@ -252,6 +252,84 @@ public function testSelectFromDual() { $this->assertEquals( 1, $result[0]->output ); } + public function testShowCreateTable1() { + $this->assertQuery( + "CREATE TABLE _tmp_table ( + ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, + option_name VARCHAR(255) default '', + option_value TEXT NOT NULL, + UNIQUE KEY option_name (option_name), + KEY composite (option_name, option_value) + );" + ); + + $this->assertQuery( + 'SHOW CREATE TABLE _tmp_table;' + ); + $results = $this->engine->get_query_results(); + $this->assertEquals( + "CREATE TABLE _tmp_table ( + `ID` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL, + `option_name` varchar(255) DEFAULT '', + `option_value` text NOT NULL, + KEY _tmp_table__composite (option_name, option_value), + UNIQUE KEY _tmp_table__option_name (option_name) + );", + $results[0]->{'Create Table'} + ); + } + + public function testShowCreateTableSimpleTable() { + $this->assertQuery( + 'CREATE TABLE _tmp_table ( + ID BIGINT NOT NULL + );' + ); + + $this->assertQuery( + 'SHOW CREATE TABLE _tmp_table;' + ); + $results = $this->engine->get_query_results(); + $this->assertEquals( + 'CREATE TABLE _tmp_table ( + `ID` bigint NOT NULL +);', + $results[0]->{'Create Table'} + ); + } + + public function testShowCreateTableWithAlterAndCreateIndex() { + $this->assertQuery( + "CREATE TABLE _tmp_table ( + ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, + option_name VARCHAR(255) default '', + option_value TEXT NOT NULL + );" + ); + + $this->assertQuery( + 'ALTER TABLE _tmp_table CHANGE COLUMN option_name option_name SMALLINT NOT NULL default 14' + ); + + $this->assertQuery( + 'ALTER TABLE _tmp_table ADD INDEX option_name (option_name);' + ); + + $this->assertQuery( + 'SHOW CREATE TABLE _tmp_table;' + ); + $results = $this->engine->get_query_results(); + $this->assertEquals( + 'CREATE TABLE _tmp_table ( + `ID` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL, + `option_name` smallint NOT NULL DEFAULT 14, + `option_value` text NOT NULL, + KEY _tmp_table__option_name (option_name) +);', + $results[0]->{'Create Table'} + ); + } + public function testSelectIndexHintForce() { $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); $result = $this->assertQuery( @@ -303,7 +381,6 @@ public function testInsertSelectFromDual() { $this->assertEquals( 1, $result ); } - public function testCreateTemporaryTable() { $this->assertQuery( "CREATE TEMPORARY TABLE _tmp_table ( @@ -346,11 +423,10 @@ public function testShowTablesLike() { ); } - public function testShowTableStatusFrom() - { + public function testShowTableStatusFrom() { // Created in setUp() function - $this->assertQuery("DROP TABLE _options"); - $this->assertQuery("DROP TABLE _dates"); + $this->assertQuery( 'DROP TABLE _options' ); + $this->assertQuery( 'DROP TABLE _dates' ); $this->assertQuery( "CREATE TABLE _tmp_table ( @@ -370,11 +446,10 @@ public function testShowTableStatusFrom() ); } - public function testShowTableStatusIn() - { + public function testShowTableStatusIn() { // Created in setUp() function - $this->assertQuery("DROP TABLE _options"); - $this->assertQuery("DROP TABLE _dates"); + $this->assertQuery( 'DROP TABLE _options' ); + $this->assertQuery( 'DROP TABLE _dates' ); $this->assertQuery( "CREATE TABLE _tmp_table ( @@ -394,11 +469,10 @@ public function testShowTableStatusIn() ); } - public function testShowTableStatusInTwoTables() - { + public function testShowTableStatusInTwoTables() { // Created in setUp() function - $this->assertQuery("DROP TABLE _options"); - $this->assertQuery("DROP TABLE _dates"); + $this->assertQuery( 'DROP TABLE _options' ); + $this->assertQuery( 'DROP TABLE _dates' ); $this->assertQuery( "CREATE TABLE _tmp_table ( @@ -427,8 +501,8 @@ public function testShowTableStatusInTwoTables() public function testShowTableStatusLike() { // Created in setUp() function - $this->assertQuery("DROP TABLE _options"); - $this->assertQuery("DROP TABLE _dates"); + $this->assertQuery( 'DROP TABLE _options' ); + $this->assertQuery( 'DROP TABLE _dates' ); $this->assertQuery( "CREATE TABLE _tmp_table1 ( @@ -769,7 +843,6 @@ public function testAlterTableAddFulltextIndex() { ); } - public function testAlterTableModifyColumn() { $this->assertQuery( "CREATE TABLE _tmp_table ( @@ -2060,8 +2133,7 @@ public function testTranslatesUtf8SELECT() { $this->assertQuery( 'DELETE FROM _options' ); } - public function testOnConflictReplace() - { + public function testOnConflictReplace() { $this->assertQuery( "CREATE TABLE _tmp_table ( ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, @@ -2076,15 +2148,15 @@ public function testOnConflictReplace() $this->assertQuery( "INSERT INTO _tmp_table VALUES (1, null, null, null, '');" ); - $result = $this->assertQuery("SELECT * FROM _tmp_table WHERE ID = 1"); + $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE ID = 1' ); $this->assertEquals( array( (object) array( - 'ID' => '1', - 'name' => 'default-value', - 'unique_name' => 'unique-default-value', + 'ID' => '1', + 'name' => 'default-value', + 'unique_name' => 'unique-default-value', 'inline_unique_name' => 'inline-unique-default-value', - 'no_default' => '', + 'no_default' => '', ), ), $result @@ -2094,10 +2166,10 @@ public function testOnConflictReplace() "INSERT INTO _tmp_table VALUES (2, '1', '2', '3', '4');" ); $this->assertQuery( - "UPDATE _tmp_table SET name = null WHERE ID = 2;" + 'UPDATE _tmp_table SET name = null WHERE ID = 2;' ); - $result = $this->assertQuery("SELECT name FROM _tmp_table WHERE ID = 2"); + $result = $this->assertQuery( 'SELECT name FROM _tmp_table WHERE ID = 2' ); $this->assertEquals( array( (object) array( @@ -2109,48 +2181,47 @@ public function testOnConflictReplace() // This should fail because of the UNIQUE constraint $this->assertQuery( - "UPDATE _tmp_table SET unique_name = NULL WHERE ID = 2;", + 'UPDATE _tmp_table SET unique_name = NULL WHERE ID = 2;', 'UNIQUE constraint failed: _tmp_table.unique_name' ); // Inline unique constraint aren't supported currently, so this should pass $this->assertQuery( - "UPDATE _tmp_table SET inline_unique_name = NULL WHERE ID = 2;", + 'UPDATE _tmp_table SET inline_unique_name = NULL WHERE ID = 2;', '' ); // WPDB allows for NULL values in columns that don't have a default value and a NOT NULL constraint $this->assertQuery( - "UPDATE _tmp_table SET no_default = NULL WHERE ID = 2;", + 'UPDATE _tmp_table SET no_default = NULL WHERE ID = 2;', '' ); - $result = $this->assertQuery("SELECT * FROM _tmp_table WHERE ID = 2"); + $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE ID = 2' ); $this->assertEquals( array( (object) array( - 'ID' => '2', - 'name' => 'default-value', - 'unique_name' => '2', + 'ID' => '2', + 'name' => 'default-value', + 'unique_name' => '2', 'inline_unique_name' => 'inline-unique-default-value', - 'no_default' => '', + 'no_default' => '', ), ), $result ); } - public function testDefaultNullValue() - { + public function testDefaultNullValue() { $this->assertQuery( - "CREATE TABLE _tmp_table ( + 'CREATE TABLE _tmp_table ( name varchar(20) NOT NULL default NULL, no_default varchar(20) NOT NULL - );" + );' ); $result = $this->assertQuery( - "DESCRIBE _tmp_table;" + 'DESCRIBE _tmp_table;' ); $this->assertEquals( array( diff --git a/wp-includes/sqlite/class-wp-sqlite-translator.php b/wp-includes/sqlite/class-wp-sqlite-translator.php index c56ec1f..6ef1eed 100644 --- a/wp-includes/sqlite/class-wp-sqlite-translator.php +++ b/wp-includes/sqlite/class-wp-sqlite-translator.php @@ -1128,23 +1128,23 @@ private function make_sqlite_field_definition( $field ) { * This mode allows the use of `NULL` when NOT NULL is set on a column that falls back to DEFAULT. * SQLite does not support this behavior, so we need to add the `ON CONFLICT REPLACE` clause to the column definition. */ - if ($field->not_null) { + if ( $field->not_null ) { $definition .= ' ON CONFLICT REPLACE'; } /** * The value of DEFAULT can be NULL. PHP would print this as an empty string, so we need a special case for it. */ - if (null === $field->default) { + if ( null === $field->default ) { $definition .= ' DEFAULT NULL'; - } else if (false !== $field->default) { + } elseif ( false !== $field->default ) { $definition .= ' DEFAULT ' . $field->default; - } else if ($field->not_null) { + } elseif ( $field->not_null ) { /** * If the column is NOT NULL, we need to provide a default value to match WPDB behavior caused by removing the STRICT_TRANS_TABLES mode. */ - if ('text' === $field->sqlite_data_type) { + if ( 'text' === $field->sqlite_data_type ) { $definition .= ' DEFAULT \'\''; - } else if (in_array($field->sqlite_data_type, array('integer', 'real'), true)) { + } elseif ( in_array( $field->sqlite_data_type, array( 'integer', 'real' ), true ) ) { $definition .= ' DEFAULT 0'; } } @@ -1501,7 +1501,7 @@ private function execute_select() { * [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list]) * | {IGNORE|FORCE} {INDEX|KEY} * [FOR {JOIN|ORDER BY|GROUP BY}] (index_list) - * + * * @see https://dev.mysql.com/doc/refman/8.3/en/index-hints.html * @return bool */ @@ -1524,8 +1524,8 @@ private function skip_index_hint() { return false; } - $this->rewriter->skip(); // USE, FORCE, IGNORE - $this->rewriter->skip(); // INDEX, KEY + $this->rewriter->skip(); // USE, FORCE, IGNORE. + $this->rewriter->skip(); // INDEX, KEY. $maybe_for = $this->rewriter->peek(); if ( $maybe_for && $maybe_for->matches( @@ -1533,7 +1533,7 @@ private function skip_index_hint() { WP_SQLite_Token::FLAG_KEYWORD_RESERVED, array( 'FOR' ) ) ) { - $this->rewriter->skip(); // FOR + $this->rewriter->skip(); // FOR. $token = $this->rewriter->peek(); if ( $token && $token->matches( @@ -1541,9 +1541,9 @@ private function skip_index_hint() { WP_SQLite_Token::FLAG_KEYWORD_RESERVED, array( 'JOIN', 'ORDER', 'GROUP' ) ) ) { - $this->rewriter->skip(); // JOIN, ORDER, GROUP + $this->rewriter->skip(); // JOIN, ORDER, GROUP. if ( 'BY' === strtoupper( $this->rewriter->peek()->value ) ) { - $this->rewriter->skip(); // BY + $this->rewriter->skip(); // BY. } } } @@ -1584,7 +1584,23 @@ private function execute_truncate() { private function execute_describe() { $this->rewriter->skip(); $this->table_name = $this->rewriter->consume()->value; - $stmt = $this->execute_sqlite_query( + $this->set_results_from_fetched_data( + $this->describe( $this->table_name ) + ); + if ( ! $this->results ) { + throw new PDOException( 'Table not found' ); + } + } + + /** + * Executes a SELECT statement. + * + * @param string $table_name The table name. + * + * @return array + */ + private function describe( $table_name ) { + return $this->execute_sqlite_query( "SELECT `name` as `Field`, ( @@ -1593,7 +1609,7 @@ private function execute_describe() { WHEN 1 THEN 'NO' END ) as `Null`, - IFNULL( + COALESCE( d.`mysql_type`, ( CASE `type` @@ -1613,19 +1629,14 @@ private function execute_describe() { ELSE 'PRI' END ) as `Key` - FROM pragma_table_info(\"$this->table_name\") p + FROM pragma_table_info(\"$table_name\") p LEFT JOIN " . self::DATA_TYPES_CACHE_TABLE . " d - ON d.`table` = \"$this->table_name\" + ON d.`table` = \"$table_name\" AND d.`column_or_index` = p.`name` ; " - ); - $this->set_results_from_fetched_data( - $stmt->fetchAll( $this->pdo_fetch_mode ) - ); - if ( ! $this->results ) { - throw new PDOException( 'Table not found' ); - } + ) + ->fetchAll( $this->pdo_fetch_mode ); } /** @@ -1642,9 +1653,9 @@ private function execute_describe() { */ private function execute_update() { $this->rewriter->consume(); // Consume the UPDATE keyword. - $has_where = false; + $has_where = false; $needs_closing_parenthesis = false; - $params = array(); + $params = array(); while ( true ) { $token = $this->rewriter->peek(); if ( ! $token ) { @@ -1659,20 +1670,20 @@ private function execute_update() { * will be rewritten to: * - UPDATE table SET column = value WHERE rowid IN (SELECT rowid FROM table WHERE condition LIMIT 1); */ - if ($this->rewriter->depth === 0) { - if (($token->value === 'LIMIT' || $token->value === 'ORDER') && !$has_where) { + if ( 0 === $this->rewriter->depth ) { + if ( ( 'LIMIT' === $token->value || 'ORDER' === $token->value ) && ! $has_where ) { $this->rewriter->add( - new WP_SQLite_Token('WHERE', WP_SQLite_Token::TYPE_KEYWORD) + new WP_SQLite_Token( 'WHERE', WP_SQLite_Token::TYPE_KEYWORD ) ); $needs_closing_parenthesis = true; - $this->preface_WHERE_clause_with_a_subquery(); - } else if ($token->value === 'WHERE') { - $has_where = true; + $this->preface_where_clause_with_a_subquery(); + } elseif ( 'WHERE' === $token->value ) { + $has_where = true; $needs_closing_parenthesis = true; $this->rewriter->consume(); - $this->preface_WHERE_clause_with_a_subquery(); + $this->preface_where_clause_with_a_subquery(); $this->rewriter->add( - new WP_SQLite_Token('WHERE', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED) + new WP_SQLite_Token( 'WHERE', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ) ); } } @@ -1705,7 +1716,7 @@ private function execute_update() { $this->rewriter->consume(); } - // Wrap up the WHERE clause with the nested SELECT statement + // Wrap up the WHERE clause with the nested SELECT statement. if ( $needs_closing_parenthesis ) { $this->rewriter->add( new WP_SQLite_Token( ')', WP_SQLite_Token::TYPE_OPERATOR ) ); } @@ -1720,16 +1731,16 @@ private function execute_update() { /** * Injects `rowid IN (SELECT rowid FROM table WHERE ...` into the WHERE clause at the current * position in the query. - * + * * This is necessary to emulate the behavior of MySQL's UPDATE LIMIT and DELETE LIMIT statement * as SQLite does not support LIMIT in UPDATE and DELETE statements. - * + * * The WHERE clause is wrapped in a subquery that selects the rowid of the rows that match the original - * WHERE clause. - * + * WHERE clause. + * * @return void */ - private function preface_WHERE_clause_with_a_subquery() { + private function preface_where_clause_with_a_subquery() { $this->rewriter->add_many( array( new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), @@ -3248,41 +3259,8 @@ private function execute_show() { // Fall through. case 'COLUMNS FROM': $table_name = $this->rewriter->consume()->token; - $stmt = $this->execute_sqlite_query( - "PRAGMA table_info(\"$table_name\");" - ); - /* @todo we may need to add the Extra column if anybdy needs it. 'auto_increment' is the value */ - $name_map = array( - 'name' => 'Field', - 'type' => 'Type', - 'dflt_value' => 'Default', - 'cid' => null, - 'notnull' => null, - 'pk' => null, - ); - $columns = $stmt->fetchAll( $this->pdo_fetch_mode ); - $columns = array_map( - function ( $row ) use ( $name_map ) { - $new = array(); - $is_object = is_object( $row ); - $row = $is_object ? (array) $row : $row; - foreach ( $row as $k => $v ) { - $k = array_key_exists( $k, $name_map ) ? $name_map [ $k ] : $k; - if ( $k ) { - $new[ $k ] = $v; - } - } - if ( array_key_exists( 'notnull', $row ) ) { - $new['Null'] = ( '1' === $row ['notnull'] ) ? 'NO' : 'YES'; - } - if ( array_key_exists( 'pk', $row ) ) { - $new['Key'] = ( '1' === $row ['pk'] ) ? 'PRI' : ''; - } - return $is_object ? (object) $new : $new; - }, - $columns - ); - $this->set_results_from_fetched_data( $columns ); + + $this->set_results_from_fetched_data( $this->get_columns_from( $table_name ) ); return; case 'INDEX FROM': @@ -3357,22 +3335,67 @@ function ( $row ) use ( $name_map ) { return; + case 'CREATE TABLE': + $table_name = $this->rewriter->consume()->token; + $columns = $this->get_columns_from( $table_name ); + $keys = $this->get_keys( $table_name ); + + foreach ( $columns as $column ) { + $column = (array) $column; + $definition = ''; + $definition .= '`' . $column['Field'] . '` '; + $definition .= $this->get_cached_mysql_data_type( + $table_name, + $column['Field'] + ) ?? $column['Type']; + $definition .= 'PRI' === $column['Key'] ? ' PRIMARY KEY' : ''; + $definition .= 'PRI' === $column['Key'] && 'INTEGER' === $column['Type'] ? ' AUTO_INCREMENT' : ''; + $definition .= 'NO' === $column['Null'] ? ' NOT NULL' : ''; + $definition .= $column['Default'] ? ' DEFAULT ' . $column['Default'] : ''; + $entries[] = $definition; + } + foreach ( $keys as $key ) { + $key = (array) $key; + $definition = ''; + $definition .= '1' === $key['index']['unique'] ? 'UNIQUE ' : ''; + $definition .= 'KEY '; + $definition .= $key['index']['name']; + $definition .= ' ('; + $definition .= implode( + ', ', + array_column( $key['columns'], 'name' ) + ); + $definition .= ')'; + $entries[] = $definition; + } + $create_table = "CREATE TABLE $table_name (\n\t"; + $create_table .= implode( ",\n\t", $entries ); + $create_table .= "\n);"; + $this->set_results_from_fetched_data( + array( + (object) array( + 'Create Table' => $create_table, + ), + ) + ); + return; + case 'TABLE STATUS': // FROM `database`. - // Match the optional [{FROM | IN} db_name] + // Match the optional [{FROM | IN} db_name]. $database_expression = $this->rewriter->consume(); - if ( $database_expression->token === 'FROM' || $database_expression->token === 'IN' ) { + if ( 'FROM' === $database_expression->token || 'IN' === $database_expression->token ) { $this->rewriter->consume(); $database_expression = $this->rewriter->consume(); } $pattern = '%'; // [LIKE 'pattern' | WHERE expr] - if($database_expression->token === 'LIKE') { + if ( 'LIKE' === $database_expression->token ) { $pattern = $this->rewriter->consume()->value; - } else if($database_expression->token === 'WHERE') { + } elseif ( 'WHERE' === $database_expression->token ) { // @TODO Support me please. - } else if($database_expression->token !== ';') { - throw new Exception( 'Syntax error: Unexpected token ' . $database_expression->token .' in query '. $this->mysql_query ); + } elseif ( ';' !== $database_expression->token ) { + throw new Exception( 'Syntax error: Unexpected token ' . $database_expression->token . ' in query ' . $this->mysql_query ); } $database_expression = $this->rewriter->skip(); @@ -3401,12 +3424,11 @@ function ( $row ) use ( $name_map ) { type='table' AND name LIKE :pattern ORDER BY name", - array( ':pattern' => $pattern, ) ); - $tables = $this->strip_sqlite_system_tables( $stmt->fetchAll( $this->pdo_fetch_mode ) ); + $tables = $this->strip_sqlite_system_tables( $stmt->fetchAll( $this->pdo_fetch_mode ) ); foreach ( $tables as $table ) { $table_name = $table->Name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $stmt = $this->execute_sqlite_query( "SELECT COUNT(1) as `Rows` FROM $table_name" ); @@ -3455,6 +3477,51 @@ function ( $row ) use ( $name_map ) { } } + /** + * Gets the columns from a table. + * + * @param string $table_name The table name. + * + * @return array The columns. + */ + private function get_columns_from( $table_name ) { + $stmt = $this->execute_sqlite_query( + "PRAGMA table_info(\"$table_name\");" + ); + /* @todo we may need to add the Extra column if anybdy needs it. 'auto_increment' is the value */ + $name_map = array( + 'name' => 'Field', + 'type' => 'Type', + 'dflt_value' => 'Default', + 'cid' => null, + 'notnull' => null, + 'pk' => null, + ); + $columns = $stmt->fetchAll( $this->pdo_fetch_mode ); + $columns = array_map( + function ( $row ) use ( $name_map ) { + $new = array(); + $is_object = is_object( $row ); + $row = $is_object ? (array) $row : $row; + foreach ( $row as $k => $v ) { + $k = array_key_exists( $k, $name_map ) ? $name_map [ $k ] : $k; + if ( $k ) { + $new[ $k ] = $v; + } + } + if ( array_key_exists( 'notnull', $row ) ) { + $new['Null'] = ( '1' === $row ['notnull'] ) ? 'NO' : 'YES'; + } + if ( array_key_exists( 'pk', $row ) ) { + $new['Key'] = ( '1' === $row ['pk'] ) ? 'PRI' : ''; + } + return $is_object ? (object) $new : $new; + }, + $columns + ); + return $columns; + } + /** * Consumes data types from the query. * diff --git a/wp-includes/sqlite/install-functions.php b/wp-includes/sqlite/install-functions.php index 5145164..cfce3a2 100644 --- a/wp-includes/sqlite/install-functions.php +++ b/wp-includes/sqlite/install-functions.php @@ -31,7 +31,7 @@ function sqlite_make_db_sqlite() { wp_die( $message, 'Database Error!' ); } - $translator = new WP_SQLite_Translator( $pdo, $GLOBALS['table_prefix'] ); + $translator = new WP_SQLite_Translator( $pdo ); $query = null; try {