From 8af4cb5befc24d54f3aec0072bea94959f78394d Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 17 Dec 2024 17:01:35 +0100 Subject: [PATCH] wip --- tests/WP_SQLite_Driver_Tests.php | 50 ++-- .../sqlite-ast/class-wp-sqlite-driver.php | 236 ++++++++++++--- ...s-wp-sqlite-information-schema-builder.php | 271 ++++++++++++++---- 3 files changed, 430 insertions(+), 127 deletions(-) diff --git a/tests/WP_SQLite_Driver_Tests.php b/tests/WP_SQLite_Driver_Tests.php index cd3c3ed7..7a1cd755 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/tests/WP_SQLite_Driver_Tests.php @@ -785,7 +785,7 @@ public function testCreateTable() { ), (object) array( 'Field' => 'user_status', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1049,7 +1049,7 @@ public function testColumnWithOnUpdate() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1076,7 +1076,7 @@ public function testColumnWithOnUpdate() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1147,7 +1147,7 @@ public function testColumnWithOnUpdate() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1218,7 +1218,7 @@ public function testChangeColumnWithOnUpdate() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1251,7 +1251,7 @@ public function testChangeColumnWithOnUpdate() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1283,7 +1283,7 @@ public function testChangeColumnWithOnUpdate() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1325,10 +1325,10 @@ public function testAlterTableWithColumnFirstAndAfter() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1336,7 +1336,7 @@ public function testAlterTableWithColumnFirstAndAfter() { 'Type' => 'varchar(20)', 'Null' => 'NO', 'Key' => '', - 'Default' => null, + 'Default' => '', 'Extra' => '', ), (object) array( @@ -1360,10 +1360,10 @@ public function testAlterTableWithColumnFirstAndAfter() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1403,7 +1403,7 @@ public function testAlterTableWithColumnFirstAndAfter() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1446,7 +1446,7 @@ public function testAlterTableWithColumnFirstAndAfter() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '0', @@ -1457,7 +1457,7 @@ public function testAlterTableWithColumnFirstAndAfter() { 'Type' => 'varchar(20)', 'Null' => 'NO', 'Key' => '', - 'Default' => null, + 'Default' => '', 'Extra' => '', ), (object) array( @@ -1500,10 +1500,10 @@ public function testAlterTableWithMultiColumnFirstAndAfter() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1546,15 +1546,15 @@ public function testAlterTableWithMultiColumnFirstAndAfter() { array( (object) array( 'Field' => 'id', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( 'Field' => 'new1', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => null, @@ -1562,7 +1562,7 @@ public function testAlterTableWithMultiColumnFirstAndAfter() { ), (object) array( 'Field' => 'new2', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '', @@ -1570,7 +1570,7 @@ public function testAlterTableWithMultiColumnFirstAndAfter() { ), (object) array( 'Field' => 'new3', - 'Type' => 'int(11)', + 'Type' => 'int', 'Null' => 'NO', 'Key' => '', 'Default' => '', @@ -2408,7 +2408,7 @@ public function testDescribeAccurate() { array( (object) array( 'Field' => 'object_id', - 'Type' => 'bigint(20) unsigned', + 'Type' => 'bigint unsigned', 'Null' => 'NO', 'Key' => 'PRI', 'Default' => '0', @@ -2416,7 +2416,7 @@ public function testDescribeAccurate() { ), (object) array( 'Field' => 'term_taxonomy_id', - 'Type' => 'bigint(20) unsigned', + 'Type' => 'bigint unsigned', 'Null' => 'NO', 'Key' => 'PRI', 'Default' => '0', @@ -2457,7 +2457,7 @@ public function testAlterTableAddColumnChangesMySQLDataType() { array( (object) array( 'Field' => 'object_id', - 'Type' => 'bigint(20) unsigned', + 'Type' => 'bigint unsigned', 'Null' => 'NO', 'Key' => '', 'Default' => '0', diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index d882e439..4d3d4ed7 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -901,6 +901,23 @@ private function execute_mysql_query( WP_Parser_Node $ast ) { */ $this->results = 0; break; + case 'utilityStatement': + $this->query_type = 'DESCRIBE'; + $subtree = $ast->get_child_node(); + switch ( $subtree->rule_name ) { + case 'describeStatement': + $this->execute_describe_statement( $subtree ); + break; + default: + throw new Exception( + sprintf( + 'Unsupported statement type: "%s" > "%s"', + $ast->rule_name, + $subtree->rule_name + ) + ); + } + break; default: throw new Exception( sprintf( 'Unsupported statement type: "%s"', $ast->rule_name ) ); } @@ -964,7 +981,7 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void { $table_name = trim( $this->translate( $node->get_descendant_node( 'tableName' ) ), '`"' ); // Save information to information schema tables. - $this->information_schema_builder->create_table( $node ); + $this->information_schema_builder->record_create_table( $node ); // Generate CREATE TABLE statement from the information schema tables. $queries = $this->get_sqlite_create_table_statement( $table_name ); @@ -974,7 +991,7 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void { $this->set_result_from_affected_rows(); } - private function get_sqlite_create_table_statement( string $table_name ): array { + private function get_sqlite_create_table_statement( string $table_name, ?string $new_table_name = null ): array { // 1. Get table info. $table_info = $this->execute_sqlite_query( ' @@ -1083,10 +1100,10 @@ function ( $column ) { } else { $is_unique = '0' === $info['NON_UNIQUE']; - $sql = sprintf( 'CREATE %sINDEX', $is_unique ? 'UNIQUE ' : '' ); - $sql .= sprintf( ' "%s"', $info['INDEX_NAME'] ); - $sql .= sprintf( ' ON "%s" (', $table_name ); - $sql .= implode( + $sql = sprintf( 'CREATE %sINDEX', $is_unique ? 'UNIQUE ' : '' ); + $sql .= sprintf( ' "%s"', $info['INDEX_NAME'] ); + $sql .= sprintf( ' ON "%s" (', $table_name ); + $sql .= implode( ', ', array_map( function ( $column ) { @@ -1095,21 +1112,167 @@ function ( $column ) { $constraint ) ); - $sql .= ')'; + $sql .= ')'; + $create_index_sqls[] = $sql; } } // 5. Compose the CREATE TABLE statement. - $sql = sprintf( 'CREATE TABLE "%s" (%s', str_replace( '"', '""', $table_name ), "\n" ); + $sql = sprintf( 'CREATE TABLE "%s" (%s', str_replace( '"', '""', $new_table_name ?? $table_name ), "\n" ); $sql .= implode( ",\n", $rows ); $sql .= "\n)"; return array_merge( array( $sql ), $create_index_sqls ); } + private function get_sqlite_column_definition( array $column, bool $has_compound_primary_key ): string { + $sql = ' '; + $sql .= sprintf( '"%s"', str_replace( '"', '""', $column['COLUMN_NAME'] ) ); + + $type = self::DATA_TYPE_STRING_MAP[ $column['DATA_TYPE'] ]; + + /* + * In SQLite, there is a PRIMARY KEY quirk for backward compatibility. + * This applies to ROWID tables and single-column primary keys only: + * 1. "INTEGER PRIMARY KEY" creates an alias of ROWID. + * 2. "INT PRIMARY KEY" will not alias of ROWID. + * + * Therefore, we want to: + * 1. Use "INT PRIMARY KEY" when we have a single-column integer + * PRIMARY KEY without AUTOINCREMENT (to avoid the ROWID alias). + * 2. Use "INTEGER PRIMARY KEY" otherwise. + * + * See: + * - https://www.sqlite.org/autoinc.html + * - https://www.sqlite.org/lang_createtable.html + */ + if ( + 'INTEGER' === $type + && 'PRI' === $column['COLUMN_KEY'] + && 'auto_increment' !== $column['EXTRA'] + && ! $has_compound_primary_key + ) { + $type = 'INT'; + } + + $sql .= ' ' . $type; + if ( 'NO' === $column['IS_NULLABLE'] ) { + $sql .= ' NOT NULL'; + } + if ( 'auto_increment' === $column['EXTRA'] ) { + $has_autoincrement = true; + $sql .= ' PRIMARY KEY AUTOINCREMENT'; + } + if ( null !== $column['COLUMN_DEFAULT'] ) { + // @TODO: Correctly quote based on the data type. + $sql .= ' DEFAULT ' . $this->pdo->quote( $column['COLUMN_DEFAULT'] ); + } + return $sql; + } + private function execute_alter_table_statement( WP_Parser_Node $node ): void { $table_name = $this->translate( $node->get_descendant_node( 'tableRef' ) ); - $actions = $node->get_descendant_nodes( 'alterListItem' ); + $table_name = trim( $table_name, '`"' ); + + // Save all column names from the original table. + $column_names = $this->execute_sqlite_query( + 'SELECT COLUMN_NAME FROM _mysql_information_schema_columns WHERE table_schema = ? AND table_name = ?', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_COLUMN ); + + // Track column renames and removals. + $column_map = array_combine( $column_names, $column_names ); + foreach ( $node->get_descendant_nodes( 'alterListItem' ) as $action ) { + $first_token = $action->get_child_token(); + + if ( WP_MySQL_Lexer::DROP_SYMBOL === $first_token->id ) { + $name = $this->translate( $action->get_child_node( 'columnInternalRef' ) ); + if ( null !== $name ) { + $name = trim( $name, '`"' ); + unset( $column_map[ $name ] ); + } + } + + if ( WP_MySQL_Lexer::CHANGE_SYMBOL === $first_token->id ) { + $old_name = $this->translate( $action->get_child_node( 'columnInternalRef' ) ); + $new_name = $this->translate( $action->get_child_node( 'identifier' ) ); + $old_name = trim( $old_name, '`"' ); + $new_name = trim( $new_name, '`"' ); + $column_map[ $old_name ] = $new_name; + } + + if ( WP_MySQL_Lexer::RENAME_SYMBOL === $first_token->id ) { + $column_ref = $action->get_child_node( 'columnInternalRef' ); + if ( null !== $column_ref ) { + $old_name = $this->translate( $column_ref ); + $new_name = $this->translate( $action->get_child_node( 'identifier' ) ); + $old_name = trim( $old_name, '`"' ); + $new_name = trim( $new_name, '`"' ); + $column_map[ $old_name ] = $new_name; + } + } + } + + $this->information_schema_builder->record_alter_table( $node ); + + /* + * See: + * https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes + */ + + // 1. If foreign key constraints are enabled, disable them. + // @TODO + + // 2. Create a new table with the new schema. + $tmp_table_name = "_tmp__{$table_name}_" . uniqid(); + $queries = $this->get_sqlite_create_table_statement( $table_name, $tmp_table_name ); + $create_table_query = $queries[0]; + $constraint_queries = array_slice( $queries, 1 ); + $this->execute_sqlite_query( $create_table_query ); + + // 3. Copy data from the original table to the new table. + $this->execute_sqlite_query( + sprintf( + 'INSERT INTO "%s" (%s) SELECT %s FROM "%s"', + $tmp_table_name, + implode( + ', ', + array_map( + function ( $column ) { + return '"' . $column . '"'; + }, + $column_map + ) + ), + implode( + ', ', + array_map( + function ( $column ) { + return '"' . $column . '"'; + }, + array_keys( $column_map ) + ) + ), + $table_name + ) + ); + + // 4. Drop the original table. + $this->execute_sqlite_query( sprintf( 'DROP TABLE "%s"', $table_name ) ); + + // 5. Rename the new table to the original table name. + $this->execute_sqlite_query( sprintf( 'ALTER TABLE "%s" RENAME TO "%s"', $tmp_table_name, $table_name ) ); + + // 6. Reconstruct indexes, triggers, and views. + foreach ( $constraint_queries as $query ) { + $this->execute_sqlite_query( $query ); + } + + // @TODO: Triggers and views. + + $this->results = 1; + $this->return_value = $this->results; + return; /* * SQLite supports only a small subset of MySQL ALTER TABLE statement. @@ -1131,42 +1294,29 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void { * * @TODO: Address these nuances. */ - foreach ( $actions as $action ) { - $token = $action->get_child_token(); - - // ADD column/constraint. - if ( WP_MySQL_Lexer::ADD_SYMBOL === $token->id ) { - // ADD COLUMN. - $field_definition = $action->get_descendant_node( 'fieldDefinition' ); - if ( null !== $field_definition ) { - $field_name = $this->translate( $action->get_child_node( 'identifier' ) ); - $field = $this->translate( $field_definition ); - $this->execute_sqlite_query( - sprintf( 'ALTER TABLE %s ADD COLUMN %s %s', $table_name, $field_name, $field ) - ); - } + } - // ADD CONSTRAINT. - $constraint = $action->get_descendant_node( 'tableConstraintDef' ); - if ( null !== $constraint ) { - $constraint_name = $this->translate( $constraint->get_child_node( 'identifier' ) ); - $constraint = $this->translate( $constraint ); - $this->execute_sqlite_query( - sprintf( 'ALTER TABLE %s ADD CONSTRAINT %s %s', $table_name, $constraint_name, $constraint ) - ); - } - } elseif ( WP_MySQL_Lexer::DROP_SYMBOL === $token->id ) { - // DROP COLUMN. - $field_name = $this->translate( $action->get_child_node( 'columnInternalRef' ) ); - if ( null !== $field_name ) { - $this->execute_sqlite_query( - sprintf( 'ALTER TABLE %s DROP COLUMN %s', $table_name, $field_name ) - ); - } - } - } + private function execute_describe_statement( WP_Parser_Node $node ): void { + $table_name = $this->translate( $node->get_child_node( 'tableRef' ) ); + $table_name = trim( $table_name, '`"' ); - $this->set_result_from_affected_rows(); + $column_info = $this->execute_sqlite_query( + ' + SELECT + column_name AS `Field`, + column_type AS `Type`, + is_nullable AS `Null`, + column_key AS `Key`, + column_default AS `Default`, + extra AS Extra + FROM _mysql_information_schema_columns + WHERE table_schema = ? + AND table_name = ? + ', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_OBJ ); + + $this->set_results_from_fetched_data( $column_info ); } private function translate( $ast ) { diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php index 42af4bc5..27318f39 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php @@ -327,7 +327,7 @@ public function ensure_tables(): void { } } - public function create_table( WP_Parser_Node $node ): void { + public function record_create_table( WP_Parser_Node $node ): void { $table_name = $this->get_value( $node->get_descendant_node( 'tableName' ) ); $engine = $this->get_table_engine( $node ); $row_format = 'MyISAM' === $engine ? 'FIXED' : 'DYNAMIC'; @@ -349,12 +349,13 @@ public function create_table( WP_Parser_Node $node ): void { // 2. INFORMATION_SCHEMA.COLUMNS: $position = 1; foreach ( $node->get_descendant_nodes( 'columnDefinition' ) as $column ) { - $this->add_column( - $table_name, - $this->get_value( $column->get_child_node( 'columnName' ) ), - $column->get_child_node( 'fieldDefinition' ), - $position - ); + $column_name = $this->get_value( $column->get_child_node( 'columnName' ) ); + $column_data = $this->prepare_column_data( $table_name, $column_name, $column, $position ); + $column_constraint_data = $this->prepare_column_constraint_data( $table_name, $column_name, $column, 'YES' === $column_data['is_nullable'] ); + $this->insert_values( '_mysql_information_schema_columns', $column_data ); + if ( null !== $column_constraint_data ) { + $this->insert_values( '_mysql_information_schema_statistics', $column_constraint_data ); + } $position += 1; } @@ -365,7 +366,129 @@ public function create_table( WP_Parser_Node $node ): void { } } - private function add_column( string $table_name, string $column_name, WP_Parser_Node $node, int $position ): void { + public function record_alter_table( WP_Parser_Node $node ): void { + $table_name = $this->get_value( $node->get_descendant_node( 'tableRef' ) ); + $actions = $node->get_descendant_nodes( 'alterListItem' ); + + foreach ( $actions as $action ) { + $first_token = $action->get_child_token(); + + // ADD + if ( WP_MySQL_Lexer::ADD_SYMBOL === $first_token->id ) { + // ADD [COLUMN] (...[, ...]) + $column_definitions = $action->get_descendant_nodes( 'columnDefinition' ); + if ( count( $column_definitions ) > 0 ) { + foreach ( $column_definitions as $column_definition ) { + $name = $this->get_value( $column_definition->get_child_node( 'identifier' ) ); + $this->record_add_column( $table_name, $name, $column_definition ); + } + continue; + } + + // ADD [COLUMN] ... + $field_definition = $action->get_descendant_node( 'fieldDefinition' ); + if ( null !== $field_definition ) { + $name = $this->get_value( $action->get_child_node( 'identifier' ) ); + $this->record_add_column( $table_name, $name, $field_definition ); + // @TODO: Handle FIRST/AFTER. + continue; + } + + // ADD CONSTRAINT. + $constraint = $action->get_descendant_node( 'tableConstraintDef' ); + if ( null !== $constraint ) { + $this->create_constraint( $table_name, $constraint ); + continue; + } + + throw new \Exception( sprintf( 'Unsupported ALTER TABLE ADD action: %s', $first_token->value ) ); + } + + // CHANGE [COLUMN] + if ( WP_MySQL_Lexer::CHANGE_SYMBOL === $first_token->id ) { + $old_name = $this->get_value( $action->get_child_node( 'columnInternalRef' ) ); + $new_name = $this->get_value( $action->get_child_node( 'identifier' ) ); + $this->record_change_column( + $table_name, + $old_name, + $new_name, + $action->get_descendant_node( 'fieldDefinition' ) + ); + continue; + } + + // MODIFY [COLUMN] + if ( WP_MySQL_Lexer::MODIFY_SYMBOL === $first_token->id ) { + $name = $this->get_value( $action->get_child_node( 'columnInternalRef' ) ); + $this->record_change_column( + $table_name, + $name, + $name, + $action->get_descendant_node( 'fieldDefinition' ) + ); + continue; + } + + // DROP + if ( WP_MySQL_Lexer::DROP_SYMBOL === $first_token->id ) { + // DROP [COLUMN] + $name = $this->translate( $action->get_child_node( 'columnInternalRef' ) ); + if ( null !== $name ) { + // @TODO + } + } + } + } + + public function record_add_column( string $table_name, string $column_name, WP_Parser_Node $node ): void { + $position = $this->query( + 'SELECT MAX(ordinal_position) FROM _mysql_information_schema_columns WHERE table_name = ?', + array( $table_name ) + )->fetchColumn(); + + $column_data = $this->prepare_column_data( $table_name, $column_name, $node, (int) $position + 1 ); + $column_constraint_data = $this->prepare_column_constraint_data( $table_name, $column_name, $node, true ); + + $this->insert_values( '_mysql_information_schema_columns', $column_data ); + if ( null !== $column_constraint_data ) { + $this->insert_values( '_mysql_information_schema_statistics', $column_constraint_data ); + } + } + + public function record_change_column( string $table_name, string $column_name, string $new_column_name, WP_Parser_Node $node ): void { + $column_data = $this->prepare_column_data( $table_name, $new_column_name, $node, 0 ); + $column_constraint_data = $this->prepare_column_constraint_data( $table_name, $new_column_name, $node, true ); + + $this->update_values( + '_mysql_information_schema_columns', + $column_data, + array( + 'table_name' => $table_name, + 'column_name' => $column_name, + ) + ); + + $this->update_values( + '_mysql_information_schema_statistics', + array( + 'column_name' => $new_column_name, + ), + array( + 'table_name' => $table_name, + 'column_name' => $column_name, + ) + ); + + if ( null !== $column_constraint_data ) { + $this->insert_values( '_mysql_information_schema_statistics', $column_constraint_data ); + } + } + + public function record_add_constraint( string $table_name, WP_Parser_Node $node ): void { + $this->create_constraint( $table_name, $node ); + } + + private function prepare_column_data( string $table_name, string $column_name, WP_Parser_Node $node, int $position ): array { $default = $this->get_column_default( $node ); $nullable = $this->get_column_nullable( $node ); $key = $this->get_column_key( $node ); @@ -379,69 +502,67 @@ private function add_column( string $table_name, string $column_name, WP_Parser_ $datetime_precision = $this->get_column_datetime_precision( $node, $data_type ); $generation_expression = $this->get_column_generation_expression( $node ); - $this->insert_values( - '_mysql_information_schema_columns', - array( - 'table_schema' => $this->db_name, - 'table_name' => $table_name, - 'column_name' => $column_name, - 'ordinal_position' => $position, - 'column_default' => $default, - 'is_nullable' => $nullable, - 'data_type' => $data_type, - 'character_maximum_length' => $char_length, - 'character_octet_length' => $octet_length, - 'numeric_precision' => $precision, - 'numeric_scale' => $scale, - 'datetime_precision' => $datetime_precision, - 'character_set_name' => $charset, - 'collation_name' => $collation, - 'column_type' => $column_type, - 'column_key' => $key, - 'extra' => $extra, - 'privileges' => 'select,insert,update,references', - 'column_comment' => $comment, - 'generation_expression' => $generation_expression, - 'srs_id' => null, // not implemented - ) + return array( + 'table_schema' => $this->db_name, + 'table_name' => $table_name, + 'column_name' => $column_name, + 'ordinal_position' => $position, + 'column_default' => $default, + 'is_nullable' => $nullable, + 'data_type' => $data_type, + 'character_maximum_length' => $char_length, + 'character_octet_length' => $octet_length, + 'numeric_precision' => $precision, + 'numeric_scale' => $scale, + 'datetime_precision' => $datetime_precision, + 'character_set_name' => $charset, + 'collation_name' => $collation, + 'column_type' => $column_type, + 'column_key' => $key, + 'extra' => $extra, + 'privileges' => 'select,insert,update,references', + 'column_comment' => $comment, + 'generation_expression' => $generation_expression, + 'srs_id' => null, // not implemented ); + } - // Inline PRIMARY KEY and UNIQUE constraints. + private function prepare_column_constraint_data( string $table_name, string $column_name, WP_Parser_Node $node, bool $nullable ): ?array { + // Handle inline PRIMARY KEY and UNIQUE constraints. $has_inline_primary_key = null !== $node->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ); $has_inline_unique_key = null !== $node->get_descendant_token( WP_MySQL_Lexer::UNIQUE_SYMBOL ); if ( $has_inline_primary_key || $has_inline_unique_key ) { $index_name = $has_inline_primary_key ? 'PRIMARY' : $column_name; - $this->insert_values( - '_mysql_information_schema_statistics', - array( - 'table_schema' => $this->db_name, - 'table_name' => $table_name, - 'non_unique' => 0, - 'index_schema' => $this->db_name, - 'index_name' => $index_name, - 'seq_in_index' => 1, - 'column_name' => $column_name, - 'collation' => 'A', - 'cardinality' => 0, // not implemented - 'sub_part' => null, - 'packed' => null, // not implemented - 'nullable' => 'YES' === $nullable ? 'YES' : '', - 'index_type' => 'BTREE', - 'comment' => '', // not implemented - 'index_comment' => '', // @TODO - 'is_visible' => 'YES', // @TODO: Save actual visibility value. - 'expression' => null, // @TODO - ) + return array( + 'table_schema' => $this->db_name, + 'table_name' => $table_name, + 'non_unique' => 0, + 'index_schema' => $this->db_name, + 'index_name' => $index_name, + 'seq_in_index' => 1, + 'column_name' => $column_name, + 'collation' => 'A', + 'cardinality' => 0, // not implemented + 'sub_part' => null, + 'packed' => null, // not implemented + 'nullable' => true === $nullable ? 'YES' : '', + 'index_type' => 'BTREE', + 'comment' => '', // not implemented + 'index_comment' => '', // @TODO + 'is_visible' => 'YES', // @TODO: Save actual visibility value. + 'expression' => null, // @TODO ); } + return null; } private function create_constraint( string $table_name, WP_Parser_Node $constraint ): void { // Get first constraint keyword. - $child = $constraint->get_child(); - $keyword = $child instanceof WP_MySQL_Token - ? $child - : $constraint->get_child_node()->get_descendant_token(); + $children = $constraint->get_children(); + $keyword = $children[0] instanceof WP_MySQL_Token ? $children[0] : $children[1]; + if ( ! $keyword instanceof WP_MySQL_Token ) { + $keyword = $keyword->get_child_token(); + } if ( WP_MySQL_Lexer::FOREIGN_SYMBOL === $keyword->id @@ -591,6 +712,27 @@ private function insert_values( string $table_name, array $data ): void { ); } + private function update_values( string $table_name, array $data, array $where ): void { + $set = array(); + foreach ( $data as $column => $value ) { + $set[] = $column . ' = ?'; + } + + $where_clause = array(); + foreach ( $where as $column => $value ) { + $where_clause[] = $column . ' = ?'; + } + + $this->query( + ' + UPDATE ' . $table_name . ' + SET ' . implode( ', ', $set ) . ' + WHERE ' . implode( ' AND ', $where_clause ) . ' + ', + array_merge( array_values( $data ), array_values( $where ) ) + ); + } + private function get_column_charset_and_collation( WP_Parser_Node $node, string $data_type ): array { if ( ! ( 'char' === $data_type @@ -740,6 +882,17 @@ private function get_column_data_types( WP_Parser_Node $node ): array { if ( null !== $field_length ) { if ( 'decimal' === $type || 'float' === $type || 'double' === $type ) { $full_type .= rtrim( $this->get_value( $field_length ), ')' ) . ',0)'; + } elseif ( + WP_MySQL_Lexer::TINYINT_SYMBOL === $token->id + || WP_MySQL_Lexer::SMALLINT_SYMBOL === $token->id + || WP_MySQL_Lexer::MEDIUMINT_SYMBOL === $token->id + || WP_MySQL_Lexer::INT_SYMBOL === $token->id + || WP_MySQL_Lexer::INTEGER_SYMBOL === $token->id + || WP_MySQL_Lexer::BIGINT_SYMBOL === $token->id + ) { + // As of MySQL 8.0.17, the display width attribute is deprecated + // for integer data types and is not stored anymore. + // @TODO: Is it important to store it for older versions? } else { $full_type .= $this->get_value( $field_length ); }