← All changes
|
wp-includes/database/sqlite/class-wp-sqlite-information-schema-builder.php
+103
-19
2.2.23
→
3.1.0
View file →
| @@ -5,8 +5,10 @@ | ||
| 5 | 5 | * |
| 6 | 6 | * This class builds and maintains MySQL INFORMATION_SCHEMA tables in SQLite. |
| 7 | 7 | * It consumes the AST of MySQL DDL queries and records the schema information |
| 8 | 8 | * in SQLite tables that emulate the MySQL INFORMATION_SCHEMA. |
| 9 | + * | |
| 10 | + * @access private | |
| 9 | 11 | */ |
| 10 | 12 | class WP_SQLite_Information_Schema_Builder { |
| 11 | 13 | /** |
| 12 | 14 | * The name of the database that is saved in the information schema tables. |
| @@ -447,8 +449,9 @@ | ||
| 447 | 449 | ('utf8_general_ci', 'utf8', 33, 'Yes', 'Yes', 1, 'PAD SPACE'), |
| 448 | 450 | ('utf8_unicode_ci', 'utf8', 192, '', 'Yes', 8, 'PAD SPACE'), |
| 449 | 451 | ('utf8mb4_bin', 'utf8mb4', 46, '', 'Yes', 1, 'PAD SPACE'), |
| 450 | 452 | ('utf8mb4_unicode_ci', 'utf8mb4', 224, '', 'Yes', 8, 'PAD SPACE'), |
| 453 | + ('utf8mb4_unicode_520_ci', 'utf8mb4', 246, '', 'Yes', 8, 'PAD SPACE'), | |
| 451 | 454 | ('utf8mb4_0900_ai_ci', 'utf8mb4', 255, 'Yes', 'Yes', 0, 'NO PAD') |
| 452 | 455 | )"; |
| 453 | 456 | default: |
| 454 | 457 | return null; |
| @@ -1502,15 +1505,16 @@ | ||
| 1502 | 1505 | * @param int $position The ordinal position of the column in the table. |
| 1503 | 1506 | * @return array Column data for the information schema. |
| 1504 | 1507 | */ |
| 1505 | 1508 | private function extract_column_data( string $table_name, string $column_name, WP_Parser_Node $node, int $position ): array { |
| 1506 | - $default = $this->get_column_default( $node ); | |
| 1509 | + list ( $data_type, $column_type ) = $this->get_column_data_types( $node ); | |
| 1510 | + | |
| 1511 | + $default = $this->get_column_default( $node, $data_type, $column_name ); | |
| 1507 | 1512 | $nullable = $this->get_column_nullable( $node ); |
| 1508 | 1513 | $key = $this->get_column_key( $node ); |
| 1509 | 1514 | $extra = $this->get_column_extra( $node ); |
| 1510 | 1515 | $comment = $this->get_column_comment( $node ); |
| 1511 | 1516 | |
| 1512 | - list ( $data_type, $column_type ) = $this->get_column_data_types( $node ); | |
| 1513 | 1517 | list ( $charset, $collation ) = $this->get_column_charset_and_collation( $node, $data_type ); |
| 1514 | 1518 | list ( $char_length, $octet_length ) = $this->get_column_lengths( $node, $data_type, $charset ); |
| 1515 | 1519 | list ( $precision, $scale ) = $this->get_column_numeric_attributes( $node, $data_type ); |
| 1516 | 1520 | $datetime_precision = $this->get_column_datetime_precision( $node, $data_type ); |
| @@ -2039,12 +2043,14 @@ | ||
| 2039 | 2043 | |
| 2040 | 2044 | /** |
| 2041 | 2045 | * Extract column default value from the "columnDefinition" or "fieldDefinition" AST node. |
| 2042 | 2046 | * |
| 2043 | - * @param WP_Parser_Node $node The "columnDefinition" or "fieldDefinition" AST node. | |
| 2044 | - * @return string The column default as stored in information schema. | |
| 2047 | + * @param WP_Parser_Node $node The "columnDefinition" or "fieldDefinition" AST node. | |
| 2048 | + * @param string $data_type The column data type as stored in information schema. | |
| 2049 | + * @param string $column_name The column name. | |
| 2050 | + * @return string|null The column default as stored in information schema. | |
| 2045 | 2051 | */ |
| 2046 | - private function get_column_default( WP_Parser_Node $node ): ?string { | |
| 2052 | + private function get_column_default( WP_Parser_Node $node, string $data_type, string $column_name ): ?string { | |
| 2047 | 2053 | $default_attr = null; |
| 2048 | 2054 | foreach ( $node->get_descendant_nodes( 'columnAttribute' ) as $attr ) { |
| 2049 | 2055 | if ( $attr->has_child_token( WP_MySQL_Lexer::DEFAULT_SYMBOL ) ) { |
| 2050 | 2056 | $default_attr = $attr; |
| @@ -2072,23 +2078,13 @@ | ||
| 2072 | 2078 | // DEFAULT signedLiteral |
| 2073 | 2079 | $signed_literal = $default_attr->get_first_child_node( 'signedLiteral' ); |
| 2074 | 2080 | if ( $signed_literal ) { |
| 2075 | 2081 | $literal = $signed_literal->get_first_child_node( 'literal' ); |
| 2076 | - | |
| 2077 | - // DEFAULT NULL | |
| 2078 | - if ( $literal && $literal->has_child_node( 'nullLiteral' ) ) { | |
| 2079 | - return null; | |
| 2082 | + if ( null === $literal ) { | |
| 2083 | + // A signed number, such as "-5", has no "literal" child node. | |
| 2084 | + return $this->get_value( $signed_literal ); | |
| 2080 | 2085 | } |
| 2081 | - | |
| 2082 | - // DEFAULT TRUE or DEFAULT FALSE | |
| 2083 | - if ( $literal && $literal->has_child_node( 'boolLiteral' ) ) { | |
| 2084 | - $bool_literal = $literal->get_first_child_node( 'boolLiteral' ); | |
| 2085 | - return $bool_literal->has_child_token( WP_MySQL_Lexer::TRUE_SYMBOL ) ? '1' : '0'; | |
| 2086 | - } | |
| 2087 | - | |
| 2088 | - // @TODO: MySQL seems to normalize default values for numeric | |
| 2089 | - // columns, such as 1.0 to 1, 1e3 to 1000, etc. | |
| 2090 | - return $this->get_value( $signed_literal ); | |
| 2086 | + return $this->get_literal_default( $literal, $data_type, $column_name ); | |
| 2091 | 2087 | } |
| 2092 | 2088 | |
| 2093 | 2089 | // DEFAULT (expression) - MySQL 8.0.13+ supports exprWithParentheses |
| 2094 | 2090 | $expr_with_parens = $default_attr->get_first_child_node( 'exprWithParentheses' ); |
| @@ -2096,8 +2092,96 @@ | ||
| 2096 | 2092 | return $this->serialize_mysql_expression( $expr_with_parens ); |
| 2097 | 2093 | } |
| 2098 | 2094 | |
| 2099 | 2095 | throw new Exception( 'DEFAULT value of this type is not supported.' ); |
| 2096 | + } | |
| 2097 | + | |
| 2098 | + /** | |
| 2099 | + * Extract and normalize a literal default value. | |
| 2100 | + * | |
| 2101 | + * @param WP_Parser_Node $literal The "literal" AST node. | |
| 2102 | + * @param string $data_type The column data type as stored in information schema. | |
| 2103 | + * @param string $column_name The column name. | |
| 2104 | + * @return string|null The default value as stored in information schema. | |
| 2105 | + */ | |
| 2106 | + private function get_literal_default( WP_Parser_Node $literal, string $data_type, string $column_name ): ?string { | |
| 2107 | + // DEFAULT NULL | |
| 2108 | + if ( $literal->has_child_node( 'nullLiteral' ) ) { | |
| 2109 | + return null; | |
| 2110 | + } | |
| 2111 | + | |
| 2112 | + // DEFAULT TRUE or DEFAULT FALSE | |
| 2113 | + if ( $literal->has_child_node( 'boolLiteral' ) ) { | |
| 2114 | + $bool_literal = $literal->get_first_child_node( 'boolLiteral' ); | |
| 2115 | + $bool_value = $bool_literal->has_child_token( WP_MySQL_Lexer::TRUE_SYMBOL ) ? '1' : '0'; | |
| 2116 | + return 'bit' === $data_type ? "b'{$bool_value}'" : $bool_value; | |
| 2117 | + } | |
| 2118 | + | |
| 2119 | + $default = $this->get_value( $literal ); | |
| 2120 | + | |
| 2121 | + if ( 'bit' === $data_type ) { | |
| 2122 | + /* | |
| 2123 | + * @TODO: Validate and normalize defaults from their AST and the full | |
| 2124 | + * column definition before storing them in the information schema. | |
| 2125 | + */ | |
| 2126 | + $bit_default = $this->get_bit_default( $default ); | |
| 2127 | + if ( null === $bit_default ) { | |
| 2128 | + throw WP_SQLite_Information_Schema_Exception::invalid_default_value( $column_name ); | |
| 2129 | + } | |
| 2130 | + return $bit_default; | |
| 2131 | + } | |
| 2132 | + | |
| 2133 | + /* | |
| 2134 | + * @TODO: Non-BIT literal defaults are currently stored verbatim. To | |
| 2135 | + * match MySQL, they should be normalized. For example: | |
| 2136 | + * - BINARY/VARBINARY defaults stored as 0x hex literals. | |
| 2137 | + * - Hex and bit literals coerced to raw bytes for text columns and to | |
| 2138 | + * their decimal value for numeric columns. | |
| 2139 | + * - The charset introducer dropped from text-literal defaults. | |
| 2140 | + * - Numeric defaults normalized, e.g. 1.0 to 1 or 1e3 to 1000. | |
| 2141 | + */ | |
| 2142 | + return $default; | |
| 2143 | + } | |
| 2144 | + | |
| 2145 | + /** | |
| 2146 | + * Normalize a BIT default value to a MySQL bit literal. | |
| 2147 | + * | |
| 2148 | + * The value may be a bit literal, a hex literal, or a decimal. | |
| 2149 | + * | |
| 2150 | + * @param string $default_value The BIT default value. | |
| 2151 | + * @return string|null The default as a bit literal, e.g. "b'101'", or null when not a bit value. | |
| 2152 | + */ | |
| 2153 | + private function get_bit_default( string $default_value ): ?string { | |
| 2154 | + $value = strtolower( $default_value ); | |
| 2155 | + | |
| 2156 | + // An empty string coerces to zero. | |
| 2157 | + if ( '' === $value ) { | |
| 2158 | + return "b'0'"; | |
| 2159 | + } | |
| 2160 | + | |
| 2161 | + // Bit literal, e.g. b'101' or 0b101. | |
| 2162 | + if ( | |
| 2163 | + preg_match( "/\Ab'([01]*)'\z/", $value, $matches ) | |
| 2164 | + || preg_match( '/\A0b([01]+)\z/', $value, $matches ) | |
| 2165 | + ) { | |
| 2166 | + $bits = ltrim( $matches[1], '0' ); | |
| 2167 | + return "b'" . ( '' === $bits ? '0' : $bits ) . "'"; | |
| 2168 | + } | |
| 2169 | + | |
| 2170 | + // Hex literal, e.g. x'05' or 0x05. | |
| 2171 | + if ( | |
| 2172 | + preg_match( "/\Ax'([0-9a-f]*)'\z/", $value, $matches ) | |
| 2173 | + || preg_match( '/\A0x([0-9a-f]+)\z/', $value, $matches ) | |
| 2174 | + ) { | |
| 2175 | + return "b'" . decbin( hexdec( $matches[1] ) ) . "'"; | |
| 2176 | + } | |
| 2177 | + | |
| 2178 | + // Decimal, e.g. 5, or a numeric string literal such as '0'. | |
| 2179 | + if ( is_numeric( $value ) ) { | |
| 2180 | + return "b'" . decbin( (int) $value ) . "'"; | |
| 2181 | + } | |
| 2182 | + | |
| 2183 | + return null; | |
| 2100 | 2184 | } |
| 2101 | 2185 | |
| 2102 | 2186 | /** |
| 2103 | 2187 | * Extract column nullability from the "columnDefinition" or "fieldDefinition" AST node. |