From d1dfbdb1fc2c5a51645fecc14bcf900fb24c4eff Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 13 Dec 2024 17:17:25 +0100 Subject: [PATCH] WIP information_schema reusability for ALTER statements --- tests/WP_SQLite_Driver_Translation_Tests.php | 12 +- ...s-wp-sqlite-information-schema-builder.php | 373 +++++++++--------- 2 files changed, 192 insertions(+), 193 deletions(-) diff --git a/tests/WP_SQLite_Driver_Translation_Tests.php b/tests/WP_SQLite_Driver_Translation_Tests.php index 30775eb3..1af25d14 100644 --- a/tests/WP_SQLite_Driver_Translation_Tests.php +++ b/tests/WP_SQLite_Driver_Translation_Tests.php @@ -386,9 +386,11 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' . " VALUES ('wp', 't3', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' - . " VALUES ('wp', 't3', 'id', 1, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', 'PRI', 'auto_increment', 'select,insert,update,references', '', '', null)", + . " VALUES ('wp', 't3', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', 'auto_increment', 'select,insert,update,references', '', '', null)", + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM _mysql_information_schema_columns WHERE table_name = 't3' AND column_name IN ('id')", 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' . " VALUES ('wp', 't3', 0, 'wp', 'PRIMARY', 1, 'id', 'A', 0, null, null, '', 'BTREE', '', '', 'YES', null)", + "UPDATE _mysql_information_schema_columns AS c SET column_key = CASE WHEN s.index_name = 'PRIMARY' THEN 'PRI' WHEN s.non_unique = 0 AND s.seq_in_index = 1 THEN 'UNI' WHEN s.seq_in_index = 1 THEN 'MUL' ELSE '' END, is_nullable = CASE WHEN s.nullable = 'YES' THEN 'YES' ELSE 'NO' END FROM _mysql_information_schema_statistics AS s WHERE c.table_name = 't3' AND c.table_name = s.table_name AND c.column_name = s.column_name", "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't3'", "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't3'", "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't3'", @@ -439,13 +441,17 @@ public function testCreateTableWithStandaloneUniqueIndexes(): void { 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' - . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', 'UNI', '', 'select,insert,update,references', '', '', null)", + . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' - . " VALUES ('wp', 't', 'name', 2, null, 'YES', 'varchar', 100, 400, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'varchar(100)', 'UNI', '', 'select,insert,update,references', '', '', null)", + . " VALUES ('wp', 't', 'name', 2, null, 'YES', 'varchar', 100, 400, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'varchar(100)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM _mysql_information_schema_columns WHERE table_name = 't' AND column_name IN ('id')", 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' . " VALUES ('wp', 't', 0, 'wp', 'id', 1, 'id', 'A', 0, null, null, 'YES', 'BTREE', '', '', 'YES', null)", + "UPDATE _mysql_information_schema_columns AS c SET column_key = CASE WHEN s.index_name = 'PRIMARY' THEN 'PRI' WHEN s.non_unique = 0 AND s.seq_in_index = 1 THEN 'UNI' WHEN s.seq_in_index = 1 THEN 'MUL' ELSE '' END, is_nullable = CASE WHEN s.nullable = 'YES' THEN 'YES' ELSE 'NO' END FROM _mysql_information_schema_statistics AS s WHERE c.table_name = 't' AND c.table_name = s.table_name AND c.column_name = s.column_name", + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM _mysql_information_schema_columns WHERE table_name = 't' AND column_name IN ('name')", 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' . " VALUES ('wp', 't', 0, 'wp', 'name', 1, 'name', 'A', 0, null, null, 'YES', 'BTREE', '', '', 'YES', null)", + "UPDATE _mysql_information_schema_columns AS c SET column_key = CASE WHEN s.index_name = 'PRIMARY' THEN 'PRI' WHEN s.non_unique = 0 AND s.seq_in_index = 1 THEN 'UNI' WHEN s.seq_in_index = 1 THEN 'MUL' ELSE '' END, is_nullable = CASE WHEN s.nullable = 'YES' THEN 'YES' ELSE 'NO' END FROM _mysql_information_schema_statistics AS s WHERE c.table_name = 't' AND c.table_name = s.table_name AND c.column_name = s.column_name", "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", 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 58b0cf3e..42af4bc5 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 @@ -3,6 +3,7 @@ // @TODO: Remove the namespace and use statements when replacing the old driver. namespace WIP; +use PDO; use WP_MySQL_Lexer; use WP_MySQL_Token; use WP_Parser_Node; @@ -332,20 +333,6 @@ public function create_table( WP_Parser_Node $node ): void { $row_format = 'MyISAM' === $engine ? 'FIXED' : 'DYNAMIC'; $collate = $this->get_table_collation( $node ); - // Get list of columns that are part of standalone PRIMARY KEY constraint. - // This is needed to determine which columns are NOT NULL implicitly. - // The list doesn't include columns with PRIMARY KEY defined inline. - $primary_key_constraint_columns = array(); - foreach ( $node->get_descendant_nodes( 'tableConstraintDef' ) as $constraint ) { - if ( null !== $constraint->get_descendant_token( WP_MySQL_Lexer::PRIMARY_SYMBOL ) ) { - foreach ( $constraint->get_descendant_nodes( 'keyPart' ) as $key_part ) { - $primary_key_constraint_columns[] = $this->get_value( - $key_part->get_child_node( 'identifier' ) - ); - } - } - } - // 1. INFORMATION_SCHEMA.TABLES: $this->insert_values( '_mysql_information_schema_tables', @@ -360,76 +347,70 @@ public function create_table( WP_Parser_Node $node ): void { ); // 2. INFORMATION_SCHEMA.COLUMNS: - $position = 1; - $column_info_map = array(); + $position = 1; foreach ( $node->get_descendant_nodes( 'columnDefinition' ) as $column ) { - $name = $this->get_value( $column->get_child_node( 'columnName' ) ); - $default = $this->get_column_default( $column ); - $nullable = $this->get_column_nullable( $column, $name, $primary_key_constraint_columns ); - $key = $this->get_column_key( $node, $column ); - $comment = $this->get_column_comment( $column ); - $extra = $this->get_column_extra( $column ); - - list ( $data_type, $column_type ) = $this->get_column_data_types( $column ); - list ( $charset, $collation ) = $this->get_column_charset_and_collation( $column, $data_type ); - list ( $char_length, $octet_length ) = $this->get_column_lengths( $column, $data_type, $charset ); - list ( $precision, $scale ) = $this->get_column_numeric_attributes( $column, $data_type ); - $datetime_precision = $this->get_column_datetime_precision( $column, $data_type ); - $generation_expression = $this->get_column_generation_expression( $column ); - - $has_inline_primary_key = null !== $column->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ); - $has_inline_unique_key = null !== $column->get_descendant_token( WP_MySQL_Lexer::UNIQUE_SYMBOL ); - - $this->insert_values( - '_mysql_information_schema_columns', - array( - 'table_schema' => $this->db_name, - 'table_name' => $table_name, - 'column_name' => $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 - ) + $this->add_column( + $table_name, + $this->get_value( $column->get_child_node( 'columnName' ) ), + $column->get_child_node( 'fieldDefinition' ), + $position ); $position += 1; - - // Store column info needed for indexes and constraints. - $column_info_map[ $name ] = array( - 'nullable' => $nullable === 'YES', - 'data_type' => $data_type, - 'character_maximum_length' => $char_length, - 'has_inline_primary_key' => $has_inline_primary_key, - 'has_inline_unique_key' => $has_inline_unique_key, - ); } // 3. INFORMATION_SCHEMA.STATISTICS (indexes): + // Standalone constraint definitions. + foreach ( $node->get_descendant_nodes( 'tableConstraintDef' ) as $constraint ) { + $this->create_constraint( $table_name, $constraint ); + } + } - // Inline PRIMARY KEY and UNIQUE constraints. - foreach ( $column_info_map as $column_name => $column_info ) { - if ( true === $column_info['has_inline_primary_key'] ) { - $index_name = 'PRIMARY'; - } elseif ( true === $column_info['has_inline_unique_key'] ) { - $index_name = $column_name; - } else { - continue; - } + private function add_column( string $table_name, string $column_name, WP_Parser_Node $node, int $position ): void { + $default = $this->get_column_default( $node ); + $nullable = $this->get_column_nullable( $node ); + $key = $this->get_column_key( $node ); + $comment = $this->get_column_comment( $node ); + $extra = $this->get_column_extra( $node ); + + list ( $data_type, $column_type ) = $this->get_column_data_types( $node ); + list ( $charset, $collation ) = $this->get_column_charset_and_collation( $node, $data_type ); + list ( $char_length, $octet_length ) = $this->get_column_lengths( $node, $data_type, $charset ); + list ( $precision, $scale ) = $this->get_column_numeric_attributes( $node, $data_type ); + $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 + ) + ); + // 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( @@ -444,7 +425,7 @@ public function create_table( WP_Parser_Node $node ): void { 'cardinality' => 0, // not implemented 'sub_part' => null, 'packed' => null, // not implemented - 'nullable' => $column_info['nullable'] ? 'YES' : '', + 'nullable' => 'YES' === $nullable ? 'YES' : '', 'index_type' => 'BTREE', 'comment' => '', // not implemented 'index_comment' => '', // @TODO @@ -453,64 +434,128 @@ public function create_table( WP_Parser_Node $node ): void { ) ); } + } - // Standalone constraint definitions. - foreach ( $node->get_descendant_nodes( 'tableConstraintDef' ) as $constraint ) { - $child = $constraint->get_child(); - if ( $child instanceof WP_Parser_Node ) { - $child = $child->get_children()[1]; - } + 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(); - if ( ! $child instanceof WP_MySQL_Token ) { - continue; - } + if ( + WP_MySQL_Lexer::FOREIGN_SYMBOL === $keyword->id + || WP_MySQL_Lexer::CHECK_SYMBOL === $keyword->id + ) { + throw new \Exception( 'FOREIGN KEY and CHECK constraints are not supported yet.' ); + } + + // Collect constraint columns. + $columns = array(); + foreach ( $constraint->get_descendant_nodes( 'keyListVariants' ) as $key ) { + $column_name = $this->get_index_column_name( $key ); + $columns[ $column_name ] = $key; + } + + // Fetch column info. + $column_info = $this->query( + ' + SELECT column_name, data_type, is_nullable, character_maximum_length + FROM _mysql_information_schema_columns + WHERE table_name = ? + AND column_name IN (' . implode( ', ', array_fill( 0, count( $columns ), '?' ) ) . ') + ', + array_merge( array( $table_name ), array_keys( $columns ) ) + )->fetchAll( PDO::FETCH_ASSOC ); + + $column_info_map = array_combine( + array_column( $column_info, 'COLUMN_NAME' ), + $column_info + ); + + // Get first index column data type (needed for index type). + $first_column_name = $column_info[0]['COLUMN_NAME']; + $first_column_type = $column_info_map[ $first_column_name ]['DATA_TYPE'] ?? null; + $has_spatial_column = null !== $first_column_type && $this->is_spatial_data_type( $first_column_type ); - // Get first index column data type (needed for index type). - $first_index_part = $constraint->get_descendant_node( 'keyListVariants' ); - $first_column_name = $this->get_index_column_name( $first_index_part ); - $first_column_type = $column_info_map[ $first_column_name ]['data_type'] ?? null; - $has_spatial_column = null !== $first_column_type && $this->is_spatial_data_type( $first_column_type ); - - $non_unique = $this->get_index_non_unique( $child ); - $index_name = $this->get_index_name( $constraint ); - $index_type = $this->get_index_type( $constraint, $child, $has_spatial_column ); - - $seq_in_index = 1; - foreach ( $constraint->get_descendant_nodes( 'keyListVariants' ) as $key ) { - $column_name = $this->get_index_column_name( $key ); - $collation = $this->get_index_column_collation( $key, $index_type ); - $nullable = true === $column_info_map[ $column_name ]['nullable'] ? 'YES' : ''; - - $sub_part = $this->get_index_column_sub_part( - $key, - $column_info_map[ $column_name ]['character_maximum_length'], - $has_spatial_column - ); - - $this->insert_values( - '_mysql_information_schema_statistics', - array( - 'table_schema' => $this->db_name, - 'table_name' => $table_name, - 'non_unique' => $non_unique, - 'index_schema' => $this->db_name, - 'index_name' => $index_name, - 'seq_in_index' => $seq_in_index, - 'column_name' => $column_name, - 'collation' => $collation, - 'cardinality' => 0, // not implemented - 'sub_part' => $sub_part, - 'packed' => null, // not implemented - 'nullable' => $nullable, - 'index_type' => $index_type, - 'comment' => '', // not implemented - 'index_comment' => '', // @TODO - 'is_visible' => 'YES', // @TODO: Save actual visibility value. - 'expression' => null, // @TODO - ) - ); + $non_unique = $this->get_index_non_unique( $keyword ); + $index_name = $this->get_index_name( $constraint ); + $index_type = $this->get_index_type( $constraint, $keyword, $has_spatial_column ); + + $seq_in_index = 1; + foreach ( $columns as $column_name => $key ) { + $collation = $this->get_index_column_collation( $key, $index_type ); + if ( + 'PRIMARY' === $index_name + || 'NO' === $column_info_map[ $column_name ]['IS_NULLABLE'] + ) { + $nullable = ''; + } else { + $nullable = 'YES'; } + + $sub_part = $this->get_index_column_sub_part( + $key, + $column_info_map[ $column_name ]['CHARACTER_MAXIMUM_LENGTH'], + $has_spatial_column + ); + + $this->insert_values( + '_mysql_information_schema_statistics', + array( + 'table_schema' => $this->db_name, + 'table_name' => $table_name, + 'non_unique' => $non_unique, + 'index_schema' => $this->db_name, + 'index_name' => $index_name, + 'seq_in_index' => $seq_in_index, + 'column_name' => $column_name, + 'collation' => $collation, + 'cardinality' => 0, // not implemented + 'sub_part' => $sub_part, + 'packed' => null, // not implemented + 'nullable' => $nullable, + 'index_type' => $index_type, + 'comment' => '', // not implemented + 'index_comment' => '', // @TODO + 'is_visible' => 'YES', // @TODO: Save actual visibility value. + 'expression' => null, // @TODO + ) + ); } + + /* + * Update column info for PRIMARY and UNIQUE constraints. + * A) COLUMN_KEY (priority from 1 to 4): + * 1. "PRI": Column is any component of a PRIMARY KEY. + * 2. "UNI": Column is the first column of a UNIQUE KEY. + * 3. "MUL": Column is the first column of a non-unique index. + * 4. "": Column is not indexed. + * + * B) IS_NULLABLE: In COLUMNS, "YES"/"NO". In STATISTICS, "YES"/"". + */ + // @TODO: Add schema check, list only affected columns. + $this->query( + " + UPDATE _mysql_information_schema_columns AS c + SET + column_key = CASE + WHEN s.index_name = 'PRIMARY' THEN 'PRI' + WHEN s.non_unique = 0 AND s.seq_in_index = 1 THEN 'UNI' + WHEN s.seq_in_index = 1 THEN 'MUL' + ELSE '' + END, + is_nullable = CASE + WHEN s.nullable = 'YES' THEN 'YES' + ELSE 'NO' + END + FROM _mysql_information_schema_statistics AS s + WHERE c.table_name = ? + AND c.table_name = s.table_name + AND c.column_name = s.column_name + ", + array( $table_name ) + ); } private function get_table_engine( WP_Parser_Node $node ): string { @@ -753,15 +798,7 @@ private function get_column_default( WP_Parser_Node $node ): ?string { return null; } - private function get_column_nullable( - WP_Parser_Node $node, - string $column_name, - array $primary_key_constraint_columns - ): string { - if ( in_array( $column_name, $primary_key_constraint_columns, true ) ) { - return 'NO'; - } - + private function get_column_nullable( WP_Parser_Node $node ): string { // SERIAL is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE. $data_type = $node->get_descendant_node( 'dataType' ); if ( null !== $data_type->get_descendant_token( WP_MySQL_Lexer::SERIAL_SYMBOL ) ) { @@ -785,10 +822,7 @@ private function get_column_nullable( return 'YES'; } - private function get_column_key( - WP_Parser_Node $table_node, - WP_Parser_Node $column_node - ): string { + private function get_column_key( WP_Parser_Node $column_node ): string { // 1. PRI: Column is a primary key or its any component. if ( null !== $column_node->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ) @@ -802,54 +836,13 @@ private function get_column_key( return 'PRI'; } - $first_in_unique = false; - $first_in_index = false; - foreach ( $table_node->get_descendant_nodes( 'tableConstraintDef' ) as $constraint ) { - $is_primary = null !== $constraint->get_descendant_token( WP_MySQL_Lexer::PRIMARY_SYMBOL ); - $is_unique = null !== $constraint->get_descendant_token( WP_MySQL_Lexer::UNIQUE_SYMBOL ); - $is_index = - null !== $constraint->get_descendant_token( WP_MySQL_Lexer::INDEX_SYMBOL ) - || null !== $constraint->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ); - - if ( ! $is_primary && ! $is_unique && ! $is_index ) { - continue; - } - - $list = $constraint->get_descendant_node( 'keyListVariants' ); - foreach ( $list->get_descendant_nodes( 'identifier' ) as $i => $identifier ) { - // @TODO: case-insensitive comparison with UTF-8 support. - $column_name = $this->get_value( - $column_node->get_child_node( 'columnName' ) - ); - if ( $column_name !== $this->get_value( $identifier ) ) { - continue; - } - - if ( $is_primary ) { - return 'PRI'; - } - - if ( 0 === $i && $is_unique ) { - $first_in_unique = true; - } elseif ( 0 === $i && $is_index ) { - $first_in_index = true; - } - } - } - - // 2. UNI: Column is UNIQUE or its first component. - if ( - $first_in_unique - || null !== $column_node->get_descendant_token( WP_MySQL_Lexer::UNIQUE_SYMBOL ) - ) { + // 2. UNI: Column has UNIQUE constraint. + if ( null !== $column_node->get_descendant_token( WP_MySQL_Lexer::UNIQUE_SYMBOL ) ) { return 'UNI'; } - // 3. MUL: Column is first component of a non-unique index. - if ( - $first_in_index - || null !== $column_node->get_descendant_token( WP_MySQL_Lexer::INDEX_SYMBOL ) - ) { + // 3. MUL: Column has INDEX. + if ( null !== $column_node->get_descendant_token( WP_MySQL_Lexer::INDEX_SYMBOL ) ) { return 'MUL'; } @@ -1172,7 +1165,7 @@ private function get_value( WP_Parser_Node $node ): string { return $full_value; } - private function query( string $query, array $params = array() ): void { - ( $this->query_callback )( $query, $params ); + private function query( string $query, array $params = array() ) { + return ( $this->query_callback )( $query, $params ); } }