1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace Wikimedia\Rdbms;
22
23/**
24 * Lazy-loaded wrapper for simplification and scrubbing of SQL queries for profiling
25 *
26 * @since 1.34
27 * @ingroup Profiler
28 * @ingroup Database
29 */
30class GeneralizedSql {
31	/** @var string */
32	private $rawSql;
33	/** @var string */
34	private $trxId;
35	/** @var string */
36	private $prefix;
37
38	/** @var string|null */
39	private $genericSql;
40
41	/**
42	 * @param string $rawSql
43	 * @param string $trxId
44	 * @param string $prefix
45	 */
46	public function __construct( $rawSql, $trxId, $prefix ) {
47		$this->rawSql = $rawSql;
48		$this->trxId = $trxId;
49		$this->prefix = $prefix;
50	}
51
52	/**
53	 * Removes most variables from an SQL query and replaces them with X or N for numbers.
54	 * It's only slightly flawed. Don't use for anything important.
55	 *
56	 * @param string $sql A SQL Query
57	 *
58	 * @return string
59	 */
60	private static function generalizeSQL( $sql ) {
61		# This does the same as the regexp below would do, but in such a way
62		# as to avoid crashing php on some large strings.
63		# $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
64
65		$sql = str_replace( "\\\\", '', $sql );
66		$sql = str_replace( "\\'", '', $sql );
67		$sql = str_replace( "\\\"", '', $sql );
68		$sql = preg_replace( "/'.*'/s", "'X'", $sql );
69		$sql = preg_replace( '/".*"/s', "'X'", $sql );
70
71		# All newlines, tabs, etc replaced by single space
72		$sql = preg_replace( '/\s+/', ' ', $sql );
73
74		# All numbers => N,
75		# except the ones surrounded by characters, e.g. l10n
76		$sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
77		$sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
78
79		return $sql;
80	}
81
82	/**
83	 * @return string
84	 */
85	public function stringify() {
86		if ( $this->genericSql !== null ) {
87			return $this->genericSql;
88		}
89
90		$this->genericSql = $this->prefix .
91			substr( self::generalizeSQL( $this->rawSql ), 0, 255 ) .
92			( $this->trxId ? " [TRX#{$this->trxId}]" : "" );
93
94		return $this->genericSql;
95	}
96}
97