37 private const QUERY_CHANGE_MASK = (
38 SQLPlatform::QUERY_CHANGE_NONE |
39 SQLPlatform::QUERY_CHANGE_TRX |
40 SQLPlatform::QUERY_CHANGE_ROWS |
41 SQLPlatform::QUERY_CHANGE_SCHEMA |
42 SQLPlatform::QUERY_CHANGE_LOCKS
43 ) & ~SQLPlatform::QUERY_IGNORE_DBO_TRX;
45 private const SCHEMA_CHANGE_VERBS = [
57 private const TRX_VERBS = [
63 'ROLLBACK TO SAVEPOINT',
66 private static string $queryVerbRegex;
74 public static function buildQuery(
string $sql, $flags,
string $tablePrefix =
'' ) {
75 $verb = self::getQueryVerb( $sql );
77 if ( ( $flags & self::QUERY_CHANGE_MASK ) == 0 ) {
78 $isWriteQuery = self::isWriteQuery( $sql );
79 if ( $isWriteQuery ) {
80 if ( in_array( $verb, self::SCHEMA_CHANGE_VERBS,
true ) ) {
81 $flags |= SQLPlatform::QUERY_CHANGE_SCHEMA;
83 $flags |= SQLPlatform::QUERY_CHANGE_ROWS;
86 if ( in_array( $verb, self::TRX_VERBS,
true ) ) {
87 $flags |= SQLPlatform::QUERY_CHANGE_TRX;
89 $flags |= SQLPlatform::QUERY_CHANGE_NONE;
98 self::getWriteTable( $sql, $tablePrefix )
102 private static function isWriteQuery( $rawSql ) {
106 if ( preg_match(
'/^\s*\(?SELECT\b/i', $rawSql ) ) {
107 return (
bool)preg_match(
'/\bFOR\s+UPDATE\)?\s*$/i', $rawSql );
123 '/^\s*(BEGIN|ROLLBACK|COMMIT|SAVEPOINT|RELEASE|SET|SHOW|EXPLAIN|USE)\b/i',
132 private static function getQueryVerb( $sql ) {
134 if ( !isset( self::$queryVerbRegex ) ) {
135 $multiwordVerbsRegex = implode(
'|', array_map(
136 fn ( $words ) => str_replace(
' ',
'\s+', $words ),
139 self::$queryVerbRegex =
"/^\s*($multiwordVerbsRegex|[a-z]+)/i";
141 return preg_match( self::$queryVerbRegex, $sql, $m ) ? strtoupper( $m[1] ) :
'';
149 private static function getWriteTable( $sql, $tablePrefix ) {
156 (?:INSERT|REPLACE)\s+(?:\w+\s+)*?INTO
157 | UPDATE(?:\s+OR\s+\w+|\s+IGNORE|\s+ONLY)?
158 | DELETE\s+(?:\w+\s+)*?FROM(?:\s+ONLY)?
159 | CREATE\s+(?:TEMPORARY\s+)?TABLE(?:\s+IF\s+NOT\s+EXISTS)?
160 | DROP\s+(?:TEMPORARY\s+)?TABLE(?:\s+IF\s+EXISTS)?
161 | TRUNCATE\s+(?:TEMPORARY\s+)?TABLE
164 (\w+|`\w+`|
'\w+'|
"\w+")
167 if ( preg_match( $regex, $sql, $m ) ) {
168 $tableName = trim( $m[1],
"\"'`" );
169 if ( str_starts_with( $tableName, $tablePrefix ) ) {
170 $tableName = substr( $tableName, strlen( $tablePrefix ) );
185 public static function generalizeSQL( $sql ) {
186 # This does the same as the regexp below would do, but in such a way
187 # as to avoid crashing php on some large strings.
188 # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
190 $sql = str_replace(
"\\\\",
'', $sql );
191 $sql = str_replace(
"\\'",
'', $sql );
192 $sql = str_replace(
"\\\"",
'', $sql );
193 $sql = preg_replace(
"/'.*'/s",
"'X'", $sql );
194 $sql = preg_replace(
'/".*"/s',
"'X'", $sql );
196 # All newlines, tabs, etc replaced by single space
197 $sql = preg_replace(
'/\s+/',
' ', $sql );
200 # except the ones surrounded by characters, e.g. l10n
201 $sql = preg_replace(
'/-?\d+(,-?\d+)+/',
'N,...,N', $sql );
202 $sql = preg_replace(
'/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/',
'N', $sql );