← All changes
|
wp-includes/sqlite/class-wp-sqlite-translator.php
+80
-137
2.2.21
→
2.2.0
View file →
| @@ -368,11 +368,10 @@ | ||
| 368 | 368 | PDO::ATTR_STRINGIFY_FETCHES => true, |
| 369 | 369 | PDO::ATTR_TIMEOUT => 5, |
| 370 | 370 | ); |
| 371 | 371 | |
| 372 | - $dsn = 'sqlite:' . FQDB; | |
| 373 | - $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class; | |
| 374 | - $pdo = new $pdo_class( $dsn, null, null, $options ); | |
| 372 | + $dsn = 'sqlite:' . FQDB; | |
| 373 | + $pdo = new PDO( $dsn, null, null, $options ); // phpcs:ignore WordPress.DB.RestrictedClasses | |
| 375 | 374 | } catch ( PDOException $ex ) { |
| 376 | 375 | $status = $ex->getCode(); |
| 377 | 376 | if ( self::SQLITE_BUSY === $status || self::SQLITE_LOCKED === $status ) { |
| 378 | 377 | $locked = true; |
| @@ -915,14 +914,14 @@ | ||
| 915 | 914 | ); |
| 916 | 915 | } |
| 917 | 916 | |
| 918 | 917 | if ( count( $table->primary_key ) > 1 ) { |
| 919 | - $definitions[] = 'PRIMARY KEY (' . implode( ', ', array_map( array( $this, 'quote_identifier' ), $table->primary_key ) ) . ')'; | |
| 918 | + $definitions[] = 'PRIMARY KEY ("' . implode( '", "', $table->primary_key ) . '")'; | |
| 920 | 919 | } |
| 921 | 920 | |
| 922 | 921 | $create_query = ( |
| 923 | 922 | $table->create_table . |
| 924 | - $this->quote_identifier( $table->name ) . ' (' . "\n" . | |
| 923 | + '"' . $table->name . '" (' . "\n" . | |
| 925 | 924 | implode( ",\n", $definitions ) . |
| 926 | 925 | ')' |
| 927 | 926 | ); |
| 928 | 927 | |
| @@ -939,9 +938,9 @@ | ||
| 939 | 938 | $unique = 'UNIQUE '; |
| 940 | 939 | } |
| 941 | 940 | $index_name = $this->generate_index_name( $table->name, $constraint->name ); |
| 942 | 941 | $this->execute_sqlite_query( |
| 943 | - 'CREATE ' . $unique . 'INDEX ' . $if_not_exists . ' ' . $this->quote_identifier( $index_name ) . ' ON ' . $this->quote_identifier( $table->name ) . ' (' . implode( ', ', array_map( array( $this, 'quote_identifier' ), $constraint->columns ) ) . ')' | |
| 942 | + "CREATE $unique INDEX $if_not_exists \"$index_name\" ON \"{$table->name}\" (\"" . implode( '", "', $constraint->columns ) . '")' | |
| 944 | 943 | ); |
| 945 | 944 | $this->update_data_type_cache( |
| 946 | 945 | $table->name, |
| 947 | 946 | $index_name, |
| @@ -1149,30 +1148,9 @@ | ||
| 1149 | 1148 | WP_SQLite_Token::TYPE_KEYWORD, |
| 1150 | 1149 | WP_SQLite_Token::FLAG_KEYWORD_FUNCTION, |
| 1151 | 1150 | array( 'DEFAULT' ) |
| 1152 | 1151 | ) ) { |
| 1153 | - // Consume the next token (could be a value, opening paren, etc.) | |
| 1154 | - $default_token = $this->rewriter->consume(); | |
| 1155 | - $result->default = $default_token->token; | |
| 1156 | - | |
| 1157 | - // Check if the default value is wrapped in parentheses (for function calls like (now())) | |
| 1158 | - if ( $default_token->matches( WP_SQLite_Token::TYPE_OPERATOR, null, array( '(' ) ) ) { | |
| 1159 | - // Track parenthesis depth to consume the complete expression | |
| 1160 | - $paren_depth = 1; | |
| 1161 | - $default_value = '('; | |
| 1162 | - | |
| 1163 | - while ( $paren_depth > 0 && ( $next_token = $this->rewriter->consume() ) ) { | |
| 1164 | - $default_value .= $next_token->token; | |
| 1165 | - | |
| 1166 | - if ( $next_token->matches( WP_SQLite_Token::TYPE_OPERATOR, null, array( '(' ) ) ) { | |
| 1167 | - ++$paren_depth; | |
| 1168 | - } elseif ( $next_token->matches( WP_SQLite_Token::TYPE_OPERATOR, null, array( ')' ) ) ) { | |
| 1169 | - --$paren_depth; | |
| 1170 | - } | |
| 1171 | - } | |
| 1172 | - | |
| 1173 | - $result->default = $default_value; | |
| 1174 | - } | |
| 1152 | + $result->default = $this->rewriter->consume()->token; | |
| 1175 | 1153 | continue; |
| 1176 | 1154 | } |
| 1177 | 1155 | |
| 1178 | 1156 | if ( |
| @@ -1207,9 +1185,9 @@ | ||
| 1207 | 1185 | * |
| 1208 | 1186 | * @return string |
| 1209 | 1187 | */ |
| 1210 | 1188 | private function make_sqlite_field_definition( $field ) { |
| 1211 | - $definition = $this->quote_identifier( $field->name ) . ' ' . $field->sqlite_data_type; | |
| 1189 | + $definition = '"' . $field->name . '" ' . $field->sqlite_data_type; | |
| 1212 | 1190 | if ( $field->auto_increment ) { |
| 1213 | 1191 | $definition .= ' PRIMARY KEY AUTOINCREMENT'; |
| 1214 | 1192 | } elseif ( $field->primary_key ) { |
| 1215 | 1193 | $definition .= ' PRIMARY KEY '; |
| @@ -1381,11 +1359,10 @@ | ||
| 1381 | 1359 | // Naive rewriting of DELETE JOIN query. |
| 1382 | 1360 | // @TODO: Actually rewrite the query instead of using a hardcoded workaround. |
| 1383 | 1361 | if ( str_contains( $updated_query, ' JOIN ' ) ) { |
| 1384 | 1362 | $table_prefix = isset( $GLOBALS['table_prefix'] ) ? $GLOBALS['table_prefix'] : 'wp_'; |
| 1385 | - $quoted_table = $this->quote_identifier( $table_prefix . 'options' ); | |
| 1386 | 1363 | $this->execute_sqlite_query( |
| 1387 | - "DELETE FROM $quoted_table WHERE option_id IN (SELECT MIN(option_id) FROM $quoted_table GROUP BY option_name HAVING COUNT(*) > 1)" | |
| 1364 | + "DELETE FROM {$table_prefix}options WHERE option_id IN (SELECT MIN(option_id) FROM {$table_prefix}options GROUP BY option_name HAVING COUNT(*) > 1)" | |
| 1388 | 1365 | ); |
| 1389 | 1366 | $this->set_result_from_affected_rows(); |
| 1390 | 1367 | return; |
| 1391 | 1368 | } |
| @@ -1417,9 +1394,9 @@ | ||
| 1417 | 1394 | // The DELETE query targets multiple tables – rewrite it into a |
| 1418 | 1395 | // SELECT to fetch the IDs of the rows to delete, then delete them |
| 1419 | 1396 | // using a separate DELETE query. |
| 1420 | 1397 | |
| 1421 | - $this->table_name = $this->normalize_column_name( $rewriter->skip()->value ); | |
| 1398 | + $this->table_name = $rewriter->skip()->value; | |
| 1422 | 1399 | $rewriter->add( new WP_SQLite_Token( 'SELECT', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ) ); |
| 1423 | 1400 | |
| 1424 | 1401 | /* |
| 1425 | 1402 | * Get table name. |
| @@ -1433,9 +1410,9 @@ | ||
| 1433 | 1410 | $index = array_search( $from, $rewriter->input_tokens, true ); |
| 1434 | 1411 | for ( $i = $index + 1; $i < $rewriter->max; $i++ ) { |
| 1435 | 1412 | // Assume the table name is the first token after FROM. |
| 1436 | 1413 | if ( ! $rewriter->input_tokens[ $i ]->is_semantically_void() ) { |
| 1437 | - $this->table_name = $this->normalize_column_name( $rewriter->input_tokens[ $i ]->value ); | |
| 1414 | + $this->table_name = $rewriter->input_tokens[ $i ]->value; | |
| 1438 | 1415 | break; |
| 1439 | 1416 | } |
| 1440 | 1417 | } |
| 1441 | 1418 | if ( ! $this->table_name ) { |
| @@ -1445,9 +1422,9 @@ | ||
| 1445 | 1422 | /* |
| 1446 | 1423 | * Now, let's figure out the primary key name. |
| 1447 | 1424 | * This assumes that all listed table names are the same. |
| 1448 | 1425 | */ |
| 1449 | - $q = $this->execute_sqlite_query( 'SELECT l.name FROM pragma_table_info(' . $this->pdo->quote( $this->table_name ) . ') as l WHERE l.pk = 1;' ); | |
| 1426 | + $q = $this->execute_sqlite_query( 'SELECT l.name FROM pragma_table_info("' . $this->table_name . '") as l WHERE l.pk = 1;' ); | |
| 1450 | 1427 | $pk_name = $q->fetch()['name']; |
| 1451 | 1428 | |
| 1452 | 1429 | /* |
| 1453 | 1430 | * Good, we can finally create the SELECT query. |
| @@ -1468,9 +1445,9 @@ | ||
| 1468 | 1445 | // Insert .id AS id_1 after the table alias. |
| 1469 | 1446 | $rewriter->add_many( |
| 1470 | 1447 | array( |
| 1471 | 1448 | new WP_SQLite_Token( '.', WP_SQLite_Token::TYPE_OPERATOR, WP_SQLite_Token::FLAG_OPERATOR_SQL ), |
| 1472 | - new WP_SQLite_Token( $this->quote_identifier( $pk_name ), WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 1449 | + new WP_SQLite_Token( $pk_name, WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 1473 | 1450 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 1474 | 1451 | new WP_SQLite_Token( 'AS', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), |
| 1475 | 1452 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 1476 | 1453 | new WP_SQLite_Token( 'id_' . $alias_nb, WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), |
| @@ -1491,17 +1468,14 @@ | ||
| 1491 | 1468 | $ids_to_delete[] = $id['id_0']; |
| 1492 | 1469 | $ids_to_delete[] = $id['id_1']; |
| 1493 | 1470 | } |
| 1494 | 1471 | |
| 1495 | - $quoted_table = $this->quote_identifier( $this->table_name ); | |
| 1496 | - $quoted_pk = $this->quote_identifier( $pk_name ); | |
| 1497 | - if ( count( $ids_to_delete ) ) { | |
| 1498 | - $placeholders = implode( ',', array_fill( 0, count( $ids_to_delete ), '?' ) ); | |
| 1499 | - $stmt = $this->execute_sqlite_query( "DELETE FROM {$quoted_table} WHERE {$quoted_pk} IN ({$placeholders})" ); | |
| 1500 | - $stmt->execute( $ids_to_delete ); | |
| 1501 | - } else { | |
| 1502 | - $this->execute_sqlite_query( "DELETE FROM {$quoted_table} WHERE 0=1" ); | |
| 1503 | - } | |
| 1472 | + $query = ( | |
| 1473 | + count( $ids_to_delete ) | |
| 1474 | + ? "DELETE FROM {$this->table_name} WHERE {$pk_name} IN (" . implode( ',', $ids_to_delete ) . ')' | |
| 1475 | + : "DELETE FROM {$this->table_name} WHERE 0=1" | |
| 1476 | + ); | |
| 1477 | + $this->execute_sqlite_query( $query ); | |
| 1504 | 1478 | $this->set_result_from_affected_rows( |
| 1505 | 1479 | count( $ids_to_delete ) |
| 1506 | 1480 | ); |
| 1507 | 1481 | } |
| @@ -1722,9 +1696,9 @@ | ||
| 1722 | 1696 | * @throws PDOException When the table is not found. |
| 1723 | 1697 | */ |
| 1724 | 1698 | private function execute_describe() { |
| 1725 | 1699 | $this->rewriter->skip(); |
| 1726 | - $this->table_name = $this->normalize_column_name( $this->rewriter->consume()->value ); | |
| 1700 | + $this->table_name = $this->rewriter->consume()->value; | |
| 1727 | 1701 | $this->set_results_from_fetched_data( |
| 1728 | 1702 | $this->describe( $this->table_name ) |
| 1729 | 1703 | ); |
| 1730 | 1704 | if ( ! $this->results ) { |
| @@ -1768,14 +1742,14 @@ | ||
| 1768 | 1742 | WHEN 0 THEN '' |
| 1769 | 1743 | ELSE 'PRI' |
| 1770 | 1744 | END |
| 1771 | 1745 | ) as `Key` |
| 1772 | - FROM pragma_table_info(" . $this->pdo->quote( $table_name ) . ') p | |
| 1773 | - LEFT JOIN ' . self::DATA_TYPES_CACHE_TABLE . ' d | |
| 1774 | - ON d.`table` = ' . $this->pdo->quote( $table_name ) . ' | |
| 1746 | + FROM pragma_table_info(\"$table_name\") p | |
| 1747 | + LEFT JOIN " . self::DATA_TYPES_CACHE_TABLE . " d | |
| 1748 | + ON d.`table` = \"$table_name\" | |
| 1775 | 1749 | AND d.`column_or_index` = p.`name` |
| 1776 | 1750 | ; |
| 1777 | - ' | |
| 1751 | + " | |
| 1778 | 1752 | ) |
| 1779 | 1753 | ->fetchAll( $this->pdo_fetch_mode ); |
| 1780 | 1754 | } |
| 1781 | 1755 | |
| @@ -1840,9 +1814,9 @@ | ||
| 1840 | 1814 | WP_SQLite_Token::TYPE_KEYWORD, |
| 1841 | 1815 | WP_SQLite_Token::FLAG_KEYWORD_RESERVED |
| 1842 | 1816 | ) |
| 1843 | 1817 | ) { |
| 1844 | - $this->table_name = $this->normalize_column_name( $token->value ); | |
| 1818 | + $this->table_name = $token->value; | |
| 1845 | 1819 | } |
| 1846 | 1820 | |
| 1847 | 1821 | $this->remember_last_reserved_keyword( $token ); |
| 1848 | 1822 | |
| @@ -1894,9 +1868,9 @@ | ||
| 1894 | 1868 | new WP_SQLite_Token( 'rowid', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), |
| 1895 | 1869 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 1896 | 1870 | new WP_SQLite_Token( 'FROM', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), |
| 1897 | 1871 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 1898 | - new WP_SQLite_Token( $this->quote_identifier( $this->table_name ), WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 1872 | + new WP_SQLite_Token( $this->table_name, WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), | |
| 1899 | 1873 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 1900 | 1874 | ) |
| 1901 | 1875 | ); |
| 1902 | 1876 | } |
| @@ -1919,9 +1893,9 @@ | ||
| 1919 | 1893 | |
| 1920 | 1894 | // Consume and record the table name. |
| 1921 | 1895 | $this->insert_columns = array(); |
| 1922 | 1896 | $this->rewriter->consume(); // INTO. |
| 1923 | - $this->table_name = $this->normalize_column_name( $this->rewriter->consume()->value ); // Table name. | |
| 1897 | + $this->table_name = $this->rewriter->consume()->value; // Table name. | |
| 1924 | 1898 | |
| 1925 | 1899 | /* |
| 1926 | 1900 | * A list of columns is given if the opening parenthesis |
| 1927 | 1901 | * is earlier than the VALUES keyword. |
| @@ -2198,9 +2172,9 @@ | ||
| 2198 | 2172 | ) |
| 2199 | 2173 | ) { |
| 2200 | 2174 | return false; |
| 2201 | 2175 | } |
| 2202 | - $table_name = $this->normalize_column_name( $this->rewriter->peek_nth( 2 )->value ); | |
| 2176 | + $table_name = $this->rewriter->peek_nth( 2 )->value; | |
| 2203 | 2177 | if ( 'dual' === strtolower( $table_name ) ) { |
| 2204 | 2178 | return false; |
| 2205 | 2179 | } |
| 2206 | 2180 | return $table_name; |
| @@ -2251,9 +2225,12 @@ | ||
| 2251 | 2225 | * |
| 2252 | 2226 | * @return bool True if the parameter was extracted successfully, false otherwise. |
| 2253 | 2227 | */ |
| 2254 | 2228 | private function extract_bound_parameter( $token, &$params ) { |
| 2255 | - if ( ! $token->matches( WP_SQLite_Token::TYPE_STRING ) | |
| 2229 | + if ( ! $token->matches( | |
| 2230 | + WP_SQLite_Token::TYPE_STRING, | |
| 2231 | + WP_SQLite_Token::FLAG_STRING_SINGLE_QUOTES | |
| 2232 | + ) | |
| 2256 | 2233 | || 'AS' === $this->last_reserved_keyword |
| 2257 | 2234 | ) { |
| 2258 | 2235 | return false; |
| 2259 | 2236 | } |
| @@ -2536,9 +2513,9 @@ | ||
| 2536 | 2513 | } |
| 2537 | 2514 | |
| 2538 | 2515 | $this->rewriter->add( new WP_SQLite_Token( 'STRFTIME', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_FUNCTION ) ); |
| 2539 | 2516 | $this->rewriter->add( new WP_SQLite_Token( '(', WP_SQLite_Token::TYPE_OPERATOR ) ); |
| 2540 | - $this->rewriter->add( new WP_SQLite_Token( $this->pdo->quote( $new_format ), WP_SQLite_Token::TYPE_STRING ) ); | |
| 2517 | + $this->rewriter->add( new WP_SQLite_Token( "'$new_format'", WP_SQLite_Token::TYPE_STRING ) ); | |
| 2541 | 2518 | $this->rewriter->add( new WP_SQLite_Token( ',', WP_SQLite_Token::TYPE_OPERATOR ) ); |
| 2542 | 2519 | |
| 2543 | 2520 | // Add the buffered tokens back to the stream. |
| 2544 | 2521 | $this->rewriter->add_many( $first_arg ); |
| @@ -2611,9 +2588,9 @@ | ||
| 2611 | 2588 | break; |
| 2612 | 2589 | } |
| 2613 | 2590 | } |
| 2614 | 2591 | |
| 2615 | - $this->rewriter->add( new WP_SQLite_Token( $this->pdo->quote( "{$interval_op}$num $unit" ), WP_SQLite_Token::TYPE_STRING ) ); | |
| 2592 | + $this->rewriter->add( new WP_SQLite_Token( "'{$interval_op}$num $unit'", WP_SQLite_Token::TYPE_STRING ) ); | |
| 2616 | 2593 | return true; |
| 2617 | 2594 | } |
| 2618 | 2595 | |
| 2619 | 2596 | /** |
| @@ -2709,11 +2686,18 @@ | ||
| 2709 | 2686 | * @param string $pattern The LIKE pattern. |
| 2710 | 2687 | * @return string The escaped GLOB pattern. |
| 2711 | 2688 | */ |
| 2712 | 2689 | private function escape_like_to_glob( $pattern ) { |
| 2690 | + // Remove surrounding quotes | |
| 2691 | + $pattern = trim( $pattern, "'\"" ); | |
| 2692 | + | |
| 2713 | 2693 | $pattern = str_replace( '%', '*', $pattern ); |
| 2714 | 2694 | $pattern = str_replace( '_', '?', $pattern ); |
| 2715 | - return $this->pdo->quote( $pattern ); | |
| 2695 | + | |
| 2696 | + // No need to escape special characters in this case | |
| 2697 | + // because GLOB doesn't require escaping in the same way LIKE does | |
| 2698 | + // Return the pattern wrapped in single quotes | |
| 2699 | + return "'" . $pattern . "'"; | |
| 2716 | 2700 | } |
| 2717 | 2701 | |
| 2718 | 2702 | /** |
| 2719 | 2703 | * Detect GROUP BY. |
| @@ -2948,9 +2932,9 @@ | ||
| 2948 | 2932 | |
| 2949 | 2933 | $max = count( $conflict_columns ); |
| 2950 | 2934 | $i = 0; |
| 2951 | 2935 | foreach ( $conflict_columns as $conflict_column ) { |
| 2952 | - $this->rewriter->add( new WP_SQLite_Token( $this->quote_identifier( $conflict_column ), WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ) ); | |
| 2936 | + $this->rewriter->add( new WP_SQLite_Token( '"' . $conflict_column . '"', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ) ); | |
| 2953 | 2937 | if ( ++$i < $max ) { |
| 2954 | 2938 | $this->rewriter->add( new WP_SQLite_Token( ',', WP_SQLite_Token::TYPE_OPERATOR ) ); |
| 2955 | 2939 | $this->rewriter->add( new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ) ); |
| 2956 | 2940 | } |
| @@ -2986,14 +2970,14 @@ | ||
| 2986 | 2970 | * |
| 2987 | 2971 | * @return array |
| 2988 | 2972 | */ |
| 2989 | 2973 | private function get_keys( $table_name, $only_unique = false ) { |
| 2990 | - $query = $this->execute_sqlite_query( 'SELECT * FROM pragma_index_list(' . $this->pdo->quote( $table_name ) . ') as l;' ); | |
| 2974 | + $query = $this->execute_sqlite_query( 'SELECT * FROM pragma_index_list("' . $table_name . '") as l;' ); | |
| 2991 | 2975 | $indices = $query->fetchAll(); |
| 2992 | 2976 | $results = array(); |
| 2993 | 2977 | foreach ( $indices as $index ) { |
| 2994 | 2978 | if ( ! $only_unique || '1' === $index['unique'] ) { |
| 2995 | - $query = $this->execute_sqlite_query( 'SELECT * FROM pragma_index_info(' . $this->pdo->quote( $index['name'] ) . ') as l;' ); | |
| 2979 | + $query = $this->execute_sqlite_query( 'SELECT * FROM pragma_index_info("' . $index['name'] . '") as l;' ); | |
| 2996 | 2980 | $results[] = array( |
| 2997 | 2981 | 'index' => $index, |
| 2998 | 2982 | 'columns' => $query->fetchAll(), |
| 2999 | 2983 | ); |
| @@ -3042,9 +3026,9 @@ | ||
| 3042 | 3026 | new WP_SQLite_Token( 'ALTER', WP_SQLite_Token::TYPE_KEYWORD ), |
| 3043 | 3027 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3044 | 3028 | new WP_SQLite_Token( 'TABLE', WP_SQLite_Token::TYPE_KEYWORD ), |
| 3045 | 3029 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3046 | - new WP_SQLite_Token( $this->quote_identifier( $this->table_name ), WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 3030 | + new WP_SQLite_Token( $this->table_name, WP_SQLite_Token::TYPE_KEYWORD ), | |
| 3047 | 3031 | ) |
| 3048 | 3032 | ); |
| 3049 | 3033 | $op_type = strtoupper( $this->rewriter->consume()->token ?? '' ); |
| 3050 | 3034 | $op_raw_subject = $this->rewriter->consume()->token ?? ''; |
| @@ -3146,9 +3130,9 @@ | ||
| 3146 | 3130 | ); |
| 3147 | 3131 | |
| 3148 | 3132 | // Drop ON UPDATE trigger by the old column name. |
| 3149 | 3133 | $on_update_trigger_name = $this->get_column_on_update_current_timestamp_trigger_name( $this->table_name, $from_name ); |
| 3150 | - $this->execute_sqlite_query( 'DROP TRIGGER IF EXISTS ' . $this->quote_identifier( $on_update_trigger_name ) ); | |
| 3134 | + $this->execute_sqlite_query( "DROP TRIGGER IF EXISTS \"$on_update_trigger_name\"" ); | |
| 3151 | 3135 | |
| 3152 | 3136 | /* |
| 3153 | 3137 | * In SQLite, there is no direct equivalent to the CHANGE COLUMN |
| 3154 | 3138 | * statement from MySQL. We need to do a bit of work to emulate it. |
| @@ -3179,9 +3163,9 @@ | ||
| 3179 | 3163 | $token = $create_table->consume(); |
| 3180 | 3164 | if ( ! $token ) { |
| 3181 | 3165 | break; |
| 3182 | 3166 | } |
| 3183 | - if ( ( WP_SQLite_Token::TYPE_STRING !== $token->type && WP_SQLite_Token::TYPE_SYMBOL !== $token->type ) | |
| 3167 | + if ( WP_SQLite_Token::TYPE_STRING !== $token->type | |
| 3184 | 3168 | || $from_name !== $this->normalize_column_name( $token->value ) ) { |
| 3185 | 3169 | continue; |
| 3186 | 3170 | } |
| 3187 | 3171 | |
| @@ -3215,11 +3199,10 @@ | ||
| 3215 | 3199 | } else { |
| 3216 | 3200 | // Otherwise, just add the new name in place of the old name we dropped. |
| 3217 | 3201 | $create_table->add( |
| 3218 | 3202 | new WP_SQLite_Token( |
| 3219 | - $this->quote_identifier( $new_field->name ), | |
| 3220 | - WP_SQLite_Token::TYPE_KEYWORD, | |
| 3221 | - WP_SQLite_Token::FLAG_KEYWORD_KEY | |
| 3203 | + "`$new_field->name`", | |
| 3204 | + WP_SQLite_Token::TYPE_KEYWORD | |
| 3222 | 3205 | ) |
| 3223 | 3206 | ); |
| 3224 | 3207 | } |
| 3225 | 3208 | } |
| @@ -3224,26 +3207,24 @@ | ||
| 3224 | 3207 | } |
| 3225 | 3208 | } |
| 3226 | 3209 | |
| 3227 | 3210 | // 3. Copy the data out of the old table |
| 3228 | - $cache_table_name = "_tmp__{$this->table_name}_" . rand( 10000000, 99999999 ); | |
| 3229 | - $quoted_cache_table = $this->quote_identifier( $cache_table_name ); | |
| 3230 | - $quoted_table = $this->quote_identifier( $this->table_name ); | |
| 3211 | + $cache_table_name = "_tmp__{$this->table_name}_" . rand( 10000000, 99999999 ); | |
| 3231 | 3212 | $this->execute_sqlite_query( |
| 3232 | - "CREATE TABLE $quoted_cache_table as SELECT * FROM $quoted_table" | |
| 3213 | + "CREATE TABLE `$cache_table_name` as SELECT * FROM `$this->table_name`" | |
| 3233 | 3214 | ); |
| 3234 | 3215 | |
| 3235 | 3216 | // 4. Drop the old table to free up the indexes names |
| 3236 | - $this->execute_sqlite_query( "DROP TABLE $quoted_table" ); | |
| 3217 | + $this->execute_sqlite_query( "DROP TABLE `$this->table_name`" ); | |
| 3237 | 3218 | |
| 3238 | 3219 | // 5. Create a new table from the updated schema |
| 3239 | 3220 | $this->execute_sqlite_query( $create_table->get_updated_query() ); |
| 3240 | 3221 | |
| 3241 | 3222 | // 6. Copy the data from step 3 to the new table |
| 3242 | - $this->execute_sqlite_query( "INSERT INTO $quoted_table SELECT * FROM $quoted_cache_table" ); | |
| 3223 | + $this->execute_sqlite_query( "INSERT INTO {$this->table_name} SELECT * FROM $cache_table_name" ); | |
| 3243 | 3224 | |
| 3244 | 3225 | // 7. Drop the old table copy |
| 3245 | - $this->execute_sqlite_query( "DROP TABLE $quoted_cache_table" ); | |
| 3226 | + $this->execute_sqlite_query( "DROP TABLE `$cache_table_name`" ); | |
| 3246 | 3227 | |
| 3247 | 3228 | // 8. Restore any indexes that were dropped in step 4 |
| 3248 | 3229 | foreach ( $old_indexes as $row ) { |
| 3249 | 3230 | /* |
| @@ -3256,10 +3237,10 @@ | ||
| 3256 | 3237 | |
| 3257 | 3238 | $columns = array(); |
| 3258 | 3239 | foreach ( $row['columns'] as $column ) { |
| 3259 | 3240 | $columns[] = ( $column['name'] === $from_name ) |
| 3260 | - ? $this->quote_identifier( $new_field->name ) | |
| 3261 | - : $this->quote_identifier( $column['name'] ); | |
| 3241 | + ? '`' . $new_field->name . '`' | |
| 3242 | + : '`' . $column['name'] . '`'; | |
| 3262 | 3243 | } |
| 3263 | 3244 | |
| 3264 | 3245 | $unique = '1' === $row['index']['unique'] ? 'UNIQUE' : ''; |
| 3265 | 3246 | |
| @@ -3267,9 +3248,9 @@ | ||
| 3267 | 3248 | * Use IF NOT EXISTS to avoid collisions with indexes that were |
| 3268 | 3249 | * a part of the CREATE TABLE statement |
| 3269 | 3250 | */ |
| 3270 | 3251 | $this->execute_sqlite_query( |
| 3271 | - "CREATE $unique INDEX IF NOT EXISTS " . $this->quote_identifier( $row['index']['name'] ) . " ON $quoted_table (" . implode( ', ', $columns ) . ')' | |
| 3252 | + "CREATE $unique INDEX IF NOT EXISTS `{$row['index']['name']}` ON $this->table_name (" . implode( ', ', $columns ) . ')' | |
| 3272 | 3253 | ); |
| 3273 | 3254 | } |
| 3274 | 3255 | |
| 3275 | 3256 | // Add the ON UPDATE trigger if needed. |
| @@ -3296,13 +3277,13 @@ | ||
| 3296 | 3277 | new WP_SQLite_Token( 'CREATE', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), |
| 3297 | 3278 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3298 | 3279 | new WP_SQLite_Token( $sqlite_index_type, WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), |
| 3299 | 3280 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3300 | - new WP_SQLite_Token( $this->quote_identifier( $sqlite_index_name ), WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 3281 | + new WP_SQLite_Token( "\"$sqlite_index_name\"", WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 3301 | 3282 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3302 | 3283 | new WP_SQLite_Token( 'ON', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), |
| 3303 | 3284 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3304 | - new WP_SQLite_Token( $this->quote_identifier( $this->table_name ), WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 3285 | + new WP_SQLite_Token( "\"$this->table_name\"", WP_SQLite_Token::TYPE_STRING, WP_SQLite_Token::FLAG_STRING_DOUBLE_QUOTES ), | |
| 3305 | 3286 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3306 | 3287 | new WP_SQLite_Token( '(', WP_SQLite_Token::TYPE_OPERATOR ), |
| 3307 | 3288 | ) |
| 3308 | 3289 | ); |
| @@ -3328,10 +3309,10 @@ | ||
| 3328 | 3309 | break; |
| 3329 | 3310 | } |
| 3330 | 3311 | // $token is field name. |
| 3331 | 3312 | if ( ! $token->matches( WP_SQLite_Token::TYPE_OPERATOR ) ) { |
| 3332 | - $token->token = $this->quote_identifier( $this->normalize_column_name( $token->token ) ); | |
| 3333 | - $token->value = $token->token; | |
| 3313 | + $token->token = '`' . $this->normalize_column_name( $token->token ) . '`'; | |
| 3314 | + $token->value = '`' . $this->normalize_column_name( $token->token ) . '`'; | |
| 3334 | 3315 | } |
| 3335 | 3316 | |
| 3336 | 3317 | /* |
| 3337 | 3318 | * Optionally, it may be followed by a size like `(20)`. |
| @@ -3354,9 +3335,9 @@ | ||
| 3354 | 3335 | new WP_SQLite_Token( 'DROP', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), |
| 3355 | 3336 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3356 | 3337 | new WP_SQLite_Token( 'INDEX', WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_RESERVED ), |
| 3357 | 3338 | new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), |
| 3358 | - new WP_SQLite_Token( $this->quote_identifier( $this->table_name . '__' . $key_name ), WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 3339 | + new WP_SQLite_Token( "\"{$this->table_name}__$key_name\"", WP_SQLite_Token::TYPE_KEYWORD, WP_SQLite_Token::FLAG_KEYWORD_KEY ), | |
| 3359 | 3340 | ) |
| 3360 | 3341 | ); |
| 3361 | 3342 | } else { |
| 3362 | 3343 | throw new Exception( 'Unknown operation: ' . $op_type ); |
| @@ -3614,9 +3595,9 @@ | ||
| 3614 | 3595 | ); |
| 3615 | 3596 | $tables = $this->strip_sqlite_system_tables( $stmt->fetchAll( $this->pdo_fetch_mode ) ); |
| 3616 | 3597 | foreach ( $tables as $table ) { |
| 3617 | 3598 | $table_name = $table->Name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase |
| 3618 | - $stmt = $this->execute_sqlite_query( 'SELECT COUNT(1) as `Rows` FROM ' . $this->quote_identifier( $table_name ) ); | |
| 3599 | + $stmt = $this->execute_sqlite_query( "SELECT COUNT(1) as `Rows` FROM $table_name" ); | |
| 3619 | 3600 | $rows = $stmt->fetchall( $this->pdo_fetch_mode ); |
| 3620 | 3601 | $table->Rows = $rows[0]->Rows; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase |
| 3621 | 3602 | } |
| 3622 | 3603 | |
| @@ -3684,9 +3665,9 @@ | ||
| 3684 | 3665 | array_unshift( $key_definitions, $pk_definition ); |
| 3685 | 3666 | } |
| 3686 | 3667 | |
| 3687 | 3668 | $sql_parts = array( |
| 3688 | - 'CREATE TABLE ' . $this->quote_identifier( $table_name ) . ' (', | |
| 3669 | + "CREATE TABLE `$table_name` (", | |
| 3689 | 3670 | "\t" . implode( ",\n\t", array_merge( $column_definitions, $key_definitions ) ), |
| 3690 | 3671 | ');', |
| 3691 | 3672 | ); |
| 3692 | 3673 | |
| @@ -3706,9 +3687,9 @@ | ||
| 3706 | 3687 | * |
| 3707 | 3688 | * @return stdClass[] |
| 3708 | 3689 | */ |
| 3709 | 3690 | protected function get_table_columns( $table_name ) { |
| 3710 | - return $this->execute_sqlite_query( 'PRAGMA table_info(' . $this->pdo->quote( $table_name ) . ');' ) | |
| 3691 | + return $this->execute_sqlite_query( "PRAGMA table_info(\"$table_name\");" ) | |
| 3711 | 3692 | ->fetchAll( $this->pdo_fetch_mode ); |
| 3712 | 3693 | } |
| 3713 | 3694 | |
| 3714 | 3695 | /** |
| @@ -3725,9 +3706,9 @@ | ||
| 3725 | 3706 | foreach ( $columns as $column ) { |
| 3726 | 3707 | $mysql_type = $this->get_cached_mysql_data_type( $table_name, $column->name ); |
| 3727 | 3708 | $is_auto_incr = $auto_increment_column && strtolower( $auto_increment_column ) === strtolower( $column->name ); |
| 3728 | 3709 | $definition = array(); |
| 3729 | - $definition[] = $this->quote_identifier( $column->name ); | |
| 3710 | + $definition[] = '`' . $column->name . '`'; | |
| 3730 | 3711 | $definition[] = $mysql_type ?? $column->name; |
| 3731 | 3712 | |
| 3732 | 3713 | if ( '1' === $column->notnull ) { |
| 3733 | 3714 | $definition[] = 'NOT NULL'; |
| @@ -3783,9 +3764,9 @@ | ||
| 3783 | 3764 | |
| 3784 | 3765 | // Remove the prefix from the index name if there is any. We use __ as a separator. |
| 3785 | 3766 | $index_name = explode( '__', $key['index']['name'], 2 )[1] ?? $key['index']['name']; |
| 3786 | 3767 | |
| 3787 | - $key_definition[] = $this->quote_identifier( $index_name ); | |
| 3768 | + $key_definition[] = sprintf( '`%s`', $index_name ); | |
| 3788 | 3769 | |
| 3789 | 3770 | $cols = array_map( |
| 3790 | 3771 | function ( $column ) use ( $table_name, $key_length_limit ) { |
| 3791 | 3772 | $data_type = strtolower( $this->get_cached_mysql_data_type( $table_name, $column['name'] ) ); |
| @@ -3803,11 +3784,11 @@ | ||
| 3803 | 3784 | str_ends_with( $data_type, 'text' ) || |
| 3804 | 3785 | str_ends_with( $data_type, 'blob' ) || |
| 3805 | 3786 | str_starts_with( $data_type, 'var' ) |
| 3806 | 3787 | ) { |
| 3807 | - return $this->quote_identifier( $column['name'] ) . '(' . $data_length . ')'; | |
| 3788 | + return sprintf( '`%s`(%s)', $column['name'], $data_length ); | |
| 3808 | 3789 | } |
| 3809 | - return $this->quote_identifier( $column['name'] ); | |
| 3790 | + return sprintf( '`%s`', $column['name'] ); | |
| 3810 | 3791 | }, |
| 3811 | 3792 | $key['columns'] |
| 3812 | 3793 | ); |
| 3813 | 3794 | |
| @@ -3838,9 +3819,9 @@ | ||
| 3838 | 3819 | ); |
| 3839 | 3820 | |
| 3840 | 3821 | foreach ( $columns as $column ) { |
| 3841 | 3822 | if ( '0' !== $column->pk ) { |
| 3842 | - $primary_keys[] = $this->quote_identifier( $column->name ); | |
| 3823 | + $primary_keys[] = sprintf( '`%s`', $column->name ); | |
| 3843 | 3824 | } |
| 3844 | 3825 | } |
| 3845 | 3826 | |
| 3846 | 3827 | return ! empty( $primary_keys ) |
| @@ -3855,21 +3836,15 @@ | ||
| 3855 | 3836 | * |
| 3856 | 3837 | * @return string|null |
| 3857 | 3838 | */ |
| 3858 | 3839 | private function get_autoincrement_column( $table_name ) { |
| 3859 | - $create_table = $this->get_sqlite_create_table( $table_name ); | |
| 3840 | + preg_match( | |
| 3841 | + '/"([^"]+)"\s+integer\s+primary\s+key\s+autoincrement/i', | |
| 3842 | + $this->get_sqlite_create_table( $table_name ), | |
| 3843 | + $matches | |
| 3844 | + ); | |
| 3860 | 3845 | |
| 3861 | - // Match backtick-quoted identifiers (with escaped backticks ``). | |
| 3862 | - if ( preg_match( '/`((?:[^`]|``)+)`\s+integer\s+primary\s+key\s+autoincrement/i', $create_table, $matches ) ) { | |
| 3863 | - return str_replace( '``', '`', $matches[1] ); | |
| 3864 | - } | |
| 3865 | - | |
| 3866 | - // Match double-quote-quoted identifiers (with escaped double-quotes ""). | |
| 3867 | - if ( preg_match( '/"((?:[^"]|"")+)"\s+integer\s+primary\s+key\s+autoincrement/i', $create_table, $matches ) ) { | |
| 3868 | - return str_replace( '""', '"', $matches[1] ); | |
| 3869 | - } | |
| 3870 | - | |
| 3871 | - return null; | |
| 3846 | + return $matches[1] ?? null; | |
| 3872 | 3847 | } |
| 3873 | 3848 | |
| 3874 | 3849 | /** |
| 3875 | 3850 | * Gets the columns from a table for the SHOW COLUMNS query. |
| @@ -4049,41 +4024,12 @@ | ||
| 4049 | 4024 | * |
| 4050 | 4025 | * @return string The normalized column name. |
| 4051 | 4026 | */ |
| 4052 | 4027 | private function normalize_column_name( $column_name ) { |
| 4053 | - $first = substr( $column_name, 0, 1 ); | |
| 4054 | - $last = substr( $column_name, -1 ); | |
| 4055 | - | |
| 4056 | - // Strip matching surrounding quotes and unescape doubled instances. | |
| 4057 | - if ( '`' === $first && '`' === $last && strlen( $column_name ) >= 2 ) { | |
| 4058 | - return str_replace( '``', '`', substr( $column_name, 1, -1 ) ); | |
| 4059 | - } | |
| 4060 | - if ( '"' === $first && '"' === $last && strlen( $column_name ) >= 2 ) { | |
| 4061 | - return str_replace( '""', '"', substr( $column_name, 1, -1 ) ); | |
| 4062 | - } | |
| 4063 | - if ( "'" === $first && "'" === $last && strlen( $column_name ) >= 2 ) { | |
| 4064 | - return str_replace( "''", "'", substr( $column_name, 1, -1 ) ); | |
| 4065 | - } | |
| 4066 | - | |
| 4067 | - return $column_name; | |
| 4028 | + return trim( $column_name, '`\'"' ); | |
| 4068 | 4029 | } |
| 4069 | 4030 | |
| 4070 | 4031 | /** |
| 4071 | - * Quotes an identifier for safe use in SQLite queries. | |
| 4072 | - * | |
| 4073 | - * Wraps the identifier in backticks and escapes any internal backticks | |
| 4074 | - * by doubling them. This ensures identifiers with special characters | |
| 4075 | - * are properly escaped in the target SQLite query context. | |
| 4076 | - * | |
| 4077 | - * @param string $identifier The unquoted identifier. | |
| 4078 | - * | |
| 4079 | - * @return string The properly quoted identifier. | |
| 4080 | - */ | |
| 4081 | - private function quote_identifier( $identifier ) { | |
| 4082 | - return '`' . str_replace( '`', '``', $identifier ) . '`'; | |
| 4083 | - } | |
| 4084 | - | |
| 4085 | - /** | |
| 4086 | 4032 | * Normalizes an index type. |
| 4087 | 4033 | * |
| 4088 | 4034 | * @param string $index_type The index type. |
| 4089 | 4035 | * |
| @@ -4518,17 +4464,14 @@ | ||
| 4518 | 4464 | |
| 4519 | 4465 | // The trigger wouldn't work for virtual and "WITHOUT ROWID" tables, |
| 4520 | 4466 | // but currently that can't happen as we're not creating such tables. |
| 4521 | 4467 | // See: https://www.sqlite.org/rowidtable.html |
| 4522 | - $quoted_trigger = $this->quote_identifier( $trigger_name ); | |
| 4523 | - $quoted_table = $this->quote_identifier( $table ); | |
| 4524 | - $quoted_column = $this->quote_identifier( $column ); | |
| 4525 | 4468 | $this->execute_sqlite_query( |
| 4526 | - "CREATE TRIGGER $quoted_trigger | |
| 4527 | - AFTER UPDATE ON $quoted_table | |
| 4469 | + "CREATE TRIGGER \"$trigger_name\" | |
| 4470 | + AFTER UPDATE ON \"$table\" | |
| 4528 | 4471 | FOR EACH ROW |
| 4529 | 4472 | BEGIN |
| 4530 | - UPDATE $quoted_table SET $quoted_column = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid; | |
| 4473 | + UPDATE \"$table\" SET \"$column\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid; | |
| 4531 | 4474 | END" |
| 4532 | 4475 | ); |
| 4533 | 4476 | } |
| 4534 | 4477 | |