503.6.1
by Daniel Nichter
s/Percona Inc/Percona Ireland Ltd/g |
1 |
# This program is copyright 2008-2011 Percona Ireland Ltd.
|
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
2 |
# Feedback and improvements are welcome.
|
3 |
#
|
|
4 |
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
|
5 |
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
|
6 |
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
7 |
#
|
|
8 |
# This program is free software; you can redistribute it and/or modify it under
|
|
9 |
# the terms of the GNU General Public License as published by the Free Software
|
|
10 |
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
|
|
11 |
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
|
|
12 |
# licenses.
|
|
13 |
#
|
|
14 |
# You should have received a copy of the GNU General Public License along with
|
|
15 |
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
|
16 |
# Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
17 |
# ###########################################################################
|
|
18
by Daniel Nichter
Remove $Revision$ and finish re-branding modules. Rename MaatkitTest.pm to PerconaTest.pm. Put copyrights on one line. |
18 |
# QueryParser package
|
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
19 |
# ###########################################################################
|
9
by Daniel Nichter
Move module docu to work for NaturalDocs. |
20 |
{
|
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
21 |
# Package: QueryParser
|
22 |
# QueryParser extracts parts of SQL statements, like table lists and subqueries.
|
|
23 |
# This package differs from SQLParser because it only extracts from a query
|
|
24 |
# what is needed and only when that can be accomplished rather simply. By
|
|
25 |
# contrast, SQLParser parses the entire SQL statement no matter the complexity.
|
|
26 |
package QueryParser; |
|
27 |
||
28 |
use strict; |
|
29 |
use warnings FATAL => 'all'; |
|
30 |
use English qw(-no_match_vars); |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
31 |
use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
4
by Daniel Nichter
Enclose all packages blocks and add Package devel docu. |
32 |
|
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
33 |
our $tbl_ident = qr/(?:`[^`]+`|\w+)(?:\.(?:`[^`]+`|\w+))?/; |
34 |
# This regex finds things that look like database.table identifiers, based on
|
|
35 |
# their proximity to keywords. (?<!KEY\s) is a workaround for ON DUPLICATE KEY
|
|
36 |
# UPDATE, which is usually followed by a column name.
|
|
37 |
our $tbl_regex = qr{ |
|
38 |
\b(?:FROM|JOIN|(?<!KEY\s)UPDATE|INTO) # Words that precede table names
|
|
39 |
\b\s*
|
|
40 |
\(? # Optional paren around tables
|
|
41 |
# Capture the identifier and any number of comma-join identifiers that
|
|
42 |
# follow it, optionally with aliases with or without the AS keyword
|
|
43 |
($tbl_ident
|
|
44 |
(?: (?:\s+ (?:AS\s+)? \w+)?, \s*$tbl_ident )*
|
|
45 |
)
|
|
46 |
}xio; |
|
47 |
# This regex is meant to match "derived table" queries, of the form
|
|
48 |
# .. from ( select ...
|
|
49 |
# .. join ( select ...
|
|
50 |
# .. bar join foo, ( select ...
|
|
51 |
# Unfortunately it'll also match this:
|
|
52 |
# select a, b, (select ...
|
|
53 |
our $has_derived = qr{ |
|
54 |
\b(?:FROM|JOIN|,)
|
|
55 |
\s*\(\s*SELECT
|
|
56 |
}xi; |
|
57 |
||
58 |
# http://dev.mysql.com/doc/refman/5.1/en/sql-syntax-data-definition.html
|
|
59 |
# We treat TRUNCATE as a dds but really it's a data manipulation statement.
|
|
60 |
our $data_def_stmts = qr/(?:CREATE|ALTER|TRUNCATE|DROP|RENAME)/i; |
|
61 |
||
62 |
# http://dev.mysql.com/doc/refman/5.1/en/sql-syntax-data-manipulation.html
|
|
63 |
# Data manipulation statements.
|
|
64 |
our $data_manip_stmts = qr/(?:INSERT|UPDATE|DELETE|REPLACE)/i; |
|
65 |
||
66 |
sub new { |
|
67 |
my ( $class ) = @_; |
|
68 |
bless {}, $class; |
|
69 |
}
|
|
70 |
||
71 |
# Returns a list of table names found in the query text.
|
|
72 |
sub get_tables { |
|
73 |
my ( $self, $query ) = @_; |
|
74 |
return unless $query; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
75 |
PTDEBUG && _d('Getting tables for', $query); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
76 |
|
77 |
# Handle CREATE, ALTER, TRUNCATE and DROP TABLE.
|
|
78 |
my ( $ddl_stmt ) = $query =~ m/^\s*($data_def_stmts)\b/i; |
|
79 |
if ( $ddl_stmt ) { |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
80 |
PTDEBUG && _d('Special table type:', $ddl_stmt); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
81 |
$query =~ s/IF\s+(?:NOT\s+)?EXISTS//i; |
82 |
if ( $query =~ m/$ddl_stmt DATABASE\b/i ) { |
|
83 |
# Handles CREATE DATABASE, not to be confused with CREATE TABLE.
|
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
84 |
PTDEBUG && _d('Query alters a database, not a table'); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
85 |
return (); |
86 |
}
|
|
87 |
if ( $ddl_stmt =~ m/CREATE/i && $query =~ m/$ddl_stmt\b.+?\bSELECT\b/i ) { |
|
88 |
# Handle CREATE TABLE ... SELECT. In this case, the real tables
|
|
89 |
# come from the SELECT, not the CREATE.
|
|
90 |
my ($select) = $query =~ m/\b(SELECT\b.+)/is; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
91 |
PTDEBUG && _d('CREATE TABLE ... SELECT:', $select); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
92 |
return $self->get_tables($select); |
93 |
}
|
|
94 |
my ($tbl) = $query =~ m/TABLE\s+($tbl_ident)(\s+.*)?/i; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
95 |
PTDEBUG && _d('Matches table:', $tbl); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
96 |
return ($tbl); |
97 |
}
|
|
98 |
||
99 |
# These keywords may appear between UPDATE or SELECT and the table refs.
|
|
100 |
# They need to be removed so that they are not mistaken for tables.
|
|
101 |
$query =~ s/ (?:LOW_PRIORITY|IGNORE|STRAIGHT_JOIN)//ig; |
|
102 |
||
103 |
# Another special case: LOCK TABLES tbl [[AS] alias] READ|WRITE, etc.
|
|
104 |
# We strip the LOCK TABLES stuff and append "FROM" to fake a SELECT
|
|
105 |
# statement and allow $tbl_regex to match below.
|
|
362.4.1
by Brian Fraser
Fix for 1037211: QueryParser fails to distill LOCK TABLES in lowercase |
106 |
if ( $query =~ s/^\s*LOCK TABLES\s+//i ) { |
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
107 |
PTDEBUG && _d('Special table type: LOCK TABLES'); |
362.4.1
by Brian Fraser
Fix for 1037211: QueryParser fails to distill LOCK TABLES in lowercase |
108 |
$query =~ s/\s+(?:READ(?:\s+LOCAL)?|WRITE)\s*//gi; |
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
109 |
PTDEBUG && _d('Locked tables:', $query); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
110 |
$query = "FROM $query"; |
111 |
}
|
|
112 |
||
113 |
$query =~ s/\\["']//g; # quoted strings |
|
114 |
$query =~ s/".*?"/?/sg; # quoted strings |
|
115 |
$query =~ s/'.*?'/?/sg; # quoted strings |
|
116 |
||
117 |
my @tables; |
|
118 |
foreach my $tbls ( $query =~ m/$tbl_regex/gio ) { |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
119 |
PTDEBUG && _d('Match tables:', $tbls); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
120 |
|
121 |
# Some queries coming from certain ORM systems will have superfluous
|
|
122 |
# parens around table names, like SELECT * FROM (`mytable`); We match
|
|
123 |
# these so the table names can be extracted more simply with regexes. But
|
|
124 |
# in case of subqueries, this can cause us to match SELECT as a table
|
|
125 |
# name, for example, in SELECT * FROM (SELECT ....) AS X; It's possible
|
|
126 |
# that SELECT is really a table name, but so unlikely that we just skip
|
|
127 |
# this case.
|
|
128 |
next if $tbls =~ m/\ASELECT\b/i; |
|
129 |
||
130 |
foreach my $tbl ( split(',', $tbls) ) { |
|
131 |
# Remove implicit or explicit (AS) alias.
|
|
132 |
$tbl =~ s/\s*($tbl_ident)(\s+.*)?/$1/gio; |
|
133 |
||
134 |
# Sanity check for cases like when a column is named `from`
|
|
135 |
# and the regex matches junk. Instead of complex regex to
|
|
136 |
# match around these rarities, this simple check will save us.
|
|
137 |
if ( $tbl !~ m/[a-zA-Z]/ ) { |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
138 |
PTDEBUG && _d('Skipping suspicious table name:', $tbl); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
139 |
next; |
140 |
}
|
|
141 |
||
142 |
push @tables, $tbl; |
|
143 |
}
|
|
144 |
}
|
|
145 |
return @tables; |
|
146 |
}
|
|
147 |
||
148 |
# Returns true if it sees what looks like a "derived table", e.g. a subquery in
|
|
149 |
# the FROM clause.
|
|
150 |
sub has_derived_table { |
|
151 |
my ( $self, $query ) = @_; |
|
152 |
# See the $tbl_regex regex above.
|
|
153 |
my $match = $query =~ m/$has_derived/; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
154 |
PTDEBUG && _d($query, 'has ' . ($match ? 'a' : 'no') . ' derived table'); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
155 |
return $match; |
156 |
}
|
|
157 |
||
158 |
# Return a data structure of tables/databases and the name they're aliased to.
|
|
159 |
# Given the following query, SELECT * FROM db.tbl AS foo; the structure is:
|
|
160 |
# { TABLE => { foo => tbl }, DATABASE => { tbl => db } }
|
|
161 |
# If $list is true, then a flat list of tables found in the query is returned
|
|
162 |
# instead. This is used for things that want to know what tables the query
|
|
163 |
# touches, but don't care about aliases.
|
|
164 |
sub get_aliases { |
|
165 |
my ( $self, $query, $list ) = @_; |
|
166 |
||
167 |
# This is the basic result every query must return.
|
|
168 |
my $result = { |
|
169 |
DATABASE => {}, |
|
170 |
TABLE => {}, |
|
171 |
};
|
|
172 |
return $result unless $query; |
|
173 |
||
174 |
# These keywords may appear between UPDATE or SELECT and the table refs.
|
|
175 |
# They need to be removed so that they are not mistaken for tables.
|
|
176 |
$query =~ s/ (?:LOW_PRIORITY|IGNORE|STRAIGHT_JOIN)//ig; |
|
177 |
||
178 |
# These keywords may appear before JOIN. They need to be removed so
|
|
179 |
# that they are not mistaken for implicit aliases of the preceding table.
|
|
180 |
$query =~ s/ (?:INNER|OUTER|CROSS|LEFT|RIGHT|NATURAL)//ig; |
|
181 |
||
182 |
# Get the table references clause and the keyword that starts the clause.
|
|
183 |
# See the comments below for why we need the starting keyword.
|
|
184 |
my @tbl_refs; |
|
185 |
my ($tbl_refs, $from) = $query =~ m{ |
|
186 |
(
|
|
187 |
(FROM|INTO|UPDATE)\b\s* # Keyword before table refs
|
|
188 |
.+? # Table refs
|
|
189 |
)
|
|
190 |
(?:\s+|\z) # If the query does not end with the table
|
|
191 |
# refs then there must be at least 1 space
|
|
192 |
# between the last tbl ref and the next
|
|
193 |
# keyword
|
|
194 |
(?:WHERE|ORDER|LIMIT|HAVING|SET|VALUES|\z) # Keyword after table refs
|
|
195 |
}ix; |
|
196 |
||
197 |
if ( $tbl_refs ) { |
|
198 |
||
199 |
if ( $query =~ m/^(?:INSERT|REPLACE)/i ) { |
|
200 |
# Remove optional columns def from INSERT/REPLACE.
|
|
201 |
$tbl_refs =~ s/\([^\)]+\)\s*//; |
|
202 |
}
|
|
203 |
||
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
204 |
PTDEBUG && _d('tbl refs:', $tbl_refs); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
205 |
|
206 |
# These keywords precede a table ref. They signal the start of a table
|
|
207 |
# ref, but to know where the table ref ends we need the after tbl ref
|
|
208 |
# keywords below.
|
|
209 |
my $before_tbl = qr/(?:,|JOIN|\s|$from)+/i; |
|
210 |
||
211 |
# These keywords signal the end of a table ref and either 1) the start
|
|
212 |
# of another table ref, or 2) the start of an ON|USING part of a JOIN
|
|
213 |
# clause (which we want to skip over), or 3) the end of the string (\z).
|
|
214 |
# We need these after tbl ref keywords so that they are not mistaken
|
|
215 |
# for implicit aliases of the preceding table.
|
|
216 |
my $after_tbl = qr/(?:,|JOIN|ON|USING|\z)/i; |
|
217 |
||
218 |
# This is required for cases like:
|
|
219 |
# FROM t1 JOIN t2 ON t1.col1=t2.col2 JOIN t3 ON t2.col3 = t3.col4
|
|
220 |
# Because spaces may precede a tbl and a tbl may end with \z, then
|
|
221 |
# t3.col4 will match as a table. However, t2.col3=t3.col4 will not match.
|
|
222 |
$tbl_refs =~ s/ = /=/g; |
|
223 |
||
224 |
while ( |
|
225 |
$tbl_refs =~ m{ |
|
226 |
$before_tbl\b\s*
|
|
227 |
( ($tbl_ident) (?:\s+ (?:AS\s+)? (\w+))? )
|
|
228 |
\s*$after_tbl
|
|
229 |
}xgio ) |
|
230 |
{
|
|
231 |
my ( $tbl_ref, $db_tbl, $alias ) = ($1, $2, $3); |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
232 |
PTDEBUG && _d('Match table:', $tbl_ref); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
233 |
push @tbl_refs, $tbl_ref; |
234 |
$alias = $self->trim_identifier($alias); |
|
235 |
||
236 |
# Handle subqueries.
|
|
237 |
if ( $tbl_ref =~ m/^AS\s+\w+/i ) { |
|
238 |
# According to the manual
|
|
239 |
# http://dev.mysql.com/doc/refman/5.0/en/unnamed-views.html:
|
|
240 |
# "The [AS] name clause is mandatory, because every table in a
|
|
241 |
# FROM clause must have a name."
|
|
242 |
# So if the tbl ref begins with 'AS', then we probably have a
|
|
243 |
# subquery.
|
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
244 |
PTDEBUG && _d('Subquery', $tbl_ref); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
245 |
$result->{TABLE}->{$alias} = undef; |
246 |
next; |
|
247 |
}
|
|
248 |
||
249 |
my ( $db, $tbl ) = $db_tbl =~ m/^(?:(.*?)\.)?(.*)/; |
|
250 |
$db = $self->trim_identifier($db); |
|
251 |
$tbl = $self->trim_identifier($tbl); |
|
252 |
$result->{TABLE}->{$alias || $tbl} = $tbl; |
|
253 |
$result->{DATABASE}->{$tbl} = $db if $db; |
|
254 |
}
|
|
255 |
}
|
|
256 |
else { |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
257 |
PTDEBUG && _d("No tables ref in", $query); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
258 |
}
|
259 |
||
260 |
if ( $list ) { |
|
261 |
# Return raw text of the tbls without aliases, instead of identifier
|
|
262 |
# mappings. Include all identifier quotings and such.
|
|
263 |
return \@tbl_refs; |
|
264 |
}
|
|
265 |
else { |
|
266 |
return $result; |
|
267 |
}
|
|
268 |
}
|
|
269 |
||
270 |
# Splits a compound statement and returns an array with each sub-statement.
|
|
271 |
# Example:
|
|
272 |
# INSERT INTO ... SELECT ...
|
|
273 |
# is split into two statements: "INSERT INTO ..." and "SELECT ...".
|
|
274 |
sub split { |
|
275 |
my ( $self, $query ) = @_; |
|
276 |
return unless $query; |
|
277 |
$query = $self->clean_query($query); |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
278 |
PTDEBUG && _d('Splitting', $query); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
279 |
|
280 |
my $verbs = qr{SELECT|INSERT|UPDATE|DELETE|REPLACE|UNION|CREATE}i; |
|
281 |
||
282 |
# This splits a statement on the above verbs which means that the verb
|
|
283 |
# gets chopped out. Capturing the verb (e.g. ($verb)) will retain it,
|
|
284 |
# but then it's disjointed from its statement. Example: for this query,
|
|
285 |
# INSERT INTO ... SELECT ...
|
|
286 |
# split returns ('INSERT', 'INTO ...', 'SELECT', '...'). Therefore,
|
|
287 |
# we must re-attach each verb to its statement; we do this later...
|
|
288 |
my @split_statements = grep { $_ } split(m/\b($verbs\b(?!(?:\s*\()))/io, $query); |
|
289 |
||
290 |
my @statements; |
|
291 |
if ( @split_statements == 1 ) { |
|
292 |
# This happens if the query has no verbs, so it's probably a single
|
|
293 |
# statement.
|
|
294 |
push @statements, $query; |
|
295 |
}
|
|
296 |
else { |
|
297 |
# ...Re-attach verbs to their statements.
|
|
298 |
for ( my $i = 0; $i <= $#split_statements; $i += 2 ) { |
|
299 |
push @statements, $split_statements[$i].$split_statements[$i+1]; |
|
300 |
||
301 |
# Variable-width negative look-behind assertions, (?<!), aren't
|
|
302 |
# fully supported so we split ON DUPLICATE KEY UPDATE. This
|
|
303 |
# puts it back together.
|
|
304 |
if ( $statements[-2] && $statements[-2] =~ m/on duplicate key\s+$/i ) { |
|
305 |
$statements[-2] .= pop @statements; |
|
306 |
}
|
|
307 |
}
|
|
308 |
}
|
|
309 |
||
310 |
# Wrap stmts in <> to make it more clear where each one begins/ends.
|
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
311 |
PTDEBUG && _d('statements:', map { $_ ? "<$_>" : 'none' } @statements); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
312 |
return @statements; |
313 |
}
|
|
314 |
||
315 |
sub clean_query { |
|
316 |
my ( $self, $query ) = @_; |
|
317 |
return unless $query; |
|
318 |
$query =~ s!/\*.*?\*/! !g; # Remove /* comment blocks */ |
|
319 |
$query =~ s/^\s+//; # Remove leading spaces |
|
320 |
$query =~ s/\s+$//; # Remove trailing spaces |
|
321 |
$query =~ s/\s{2,}/ /g; # Remove extra spaces |
|
322 |
return $query; |
|
323 |
}
|
|
324 |
||
325 |
sub split_subquery { |
|
326 |
my ( $self, $query ) = @_; |
|
327 |
return unless $query; |
|
328 |
$query = $self->clean_query($query); |
|
329 |
$query =~ s/;$//; |
|
330 |
||
331 |
my @subqueries; |
|
332 |
my $sqno = 0; # subquery number |
|
333 |
my $pos = 0; |
|
334 |
while ( $query =~ m/(\S+)(?:\s+|\Z)/g ) { |
|
335 |
$pos = pos($query); |
|
336 |
my $word = $1; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
337 |
PTDEBUG && _d($word, $sqno); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
338 |
if ( $word =~ m/^\(?SELECT\b/i ) { |
339 |
my $start_pos = $pos - length($word) - 1; |
|
340 |
if ( $start_pos ) { |
|
341 |
$sqno++; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
342 |
PTDEBUG && _d('Subquery', $sqno, 'starts at', $start_pos); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
343 |
$subqueries[$sqno] = { |
344 |
start_pos => $start_pos, |
|
345 |
end_pos => 0, |
|
346 |
len => 0, |
|
347 |
words => [$word], |
|
348 |
lp => 1, # left parentheses |
|
349 |
rp => 0, # right parentheses |
|
350 |
done => 0, |
|
351 |
};
|
|
352 |
}
|
|
353 |
else { |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
354 |
PTDEBUG && _d('Main SELECT at pos 0'); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
355 |
}
|
356 |
}
|
|
357 |
else { |
|
358 |
next unless $sqno; # next unless we're in a subquery |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
359 |
PTDEBUG && _d('In subquery', $sqno); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
360 |
my $sq = $subqueries[$sqno]; |
361 |
if ( $sq->{done} ) { |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
362 |
PTDEBUG && _d('This subquery is done; SQL is for', |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
363 |
($sqno - 1 ? "subquery $sqno" : "the main SELECT")); |
364 |
next; |
|
365 |
}
|
|
366 |
push @{$sq->{words}}, $word; |
|
367 |
my $lp = ($word =~ tr/\(//) || 0; |
|
368 |
my $rp = ($word =~ tr/\)//) || 0; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
369 |
PTDEBUG && _d('parentheses left', $lp, 'right', $rp); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
370 |
if ( ($sq->{lp} + $lp) - ($sq->{rp} + $rp) == 0 ) { |
371 |
my $end_pos = $pos - 1; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
372 |
PTDEBUG && _d('Subquery', $sqno, 'ends at', $end_pos); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
373 |
$sq->{end_pos} = $end_pos; |
374 |
$sq->{len} = $end_pos - $sq->{start_pos}; |
|
375 |
}
|
|
376 |
}
|
|
377 |
}
|
|
378 |
||
379 |
for my $i ( 1..$#subqueries ) { |
|
380 |
my $sq = $subqueries[$i]; |
|
381 |
next unless $sq; |
|
382 |
$sq->{sql} = join(' ', @{$sq->{words}}); |
|
383 |
substr $query, |
|
384 |
$sq->{start_pos} + 1, # +1 for ( |
|
385 |
$sq->{len} - 1, # -1 for ) |
|
386 |
"__subquery_$i"; |
|
387 |
}
|
|
388 |
||
389 |
return $query, map { $_->{sql} } grep { defined $_ } @subqueries; |
|
390 |
}
|
|
391 |
||
392 |
sub query_type { |
|
393 |
my ( $self, $query, $qr ) = @_; |
|
394 |
my ($type, undef) = $qr->distill_verbs($query); |
|
395 |
my $rw; |
|
396 |
if ( $type =~ m/^SELECT\b/ ) { |
|
397 |
$rw = 'read'; |
|
398 |
}
|
|
399 |
elsif ( $type =~ m/^$data_manip_stmts\b/ |
|
400 |
|| $type =~ m/^$data_def_stmts\b/ ) { |
|
401 |
$rw = 'write' |
|
402 |
}
|
|
403 |
||
404 |
return { |
|
405 |
type => $type, |
|
406 |
rw => $rw, |
|
407 |
}
|
|
408 |
}
|
|
409 |
||
410 |
sub get_columns { |
|
411 |
my ( $self, $query ) = @_; |
|
412 |
my $cols = []; |
|
413 |
return $cols unless $query; |
|
414 |
my $cols_def; |
|
415 |
||
416 |
if ( $query =~ m/^SELECT/i ) { |
|
417 |
$query =~ s/ |
|
418 |
^SELECT\s+
|
|
419 |
(?:ALL
|
|
420 |
|DISTINCT
|
|
421 |
|DISTINCTROW
|
|
422 |
|HIGH_PRIORITY
|
|
423 |
|STRAIGHT_JOIN
|
|
424 |
|SQL_SMALL_RESULT
|
|
425 |
|SQL_BIG_RESULT
|
|
426 |
|SQL_BUFFER_RESULT
|
|
427 |
|SQL_CACHE
|
|
428 |
|SQL_NO_CACHE
|
|
429 |
|SQL_CALC_FOUND_ROWS
|
|
430 |
)\s+
|
|
431 |
/SELECT /xgi; |
|
432 |
($cols_def) = $query =~ m/^SELECT\s+(.+?)\s+FROM/i; |
|
433 |
}
|
|
434 |
elsif ( $query =~ m/^(?:INSERT|REPLACE)/i ) { |
|
435 |
($cols_def) = $query =~ m/\(([^\)]+)\)\s*VALUE/i; |
|
436 |
}
|
|
437 |
||
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
438 |
PTDEBUG && _d('Columns:', $cols_def); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
439 |
if ( $cols_def ) { |
440 |
@$cols = split(',', $cols_def); |
|
441 |
map { |
|
442 |
my $col = $_; |
|
443 |
$col = s/^\s+//g; |
|
444 |
$col = s/\s+$//g; |
|
445 |
$col; |
|
446 |
} @$cols; |
|
447 |
}
|
|
448 |
||
449 |
return $cols; |
|
450 |
}
|
|
451 |
||
452 |
sub parse { |
|
453 |
my ( $self, $query ) = @_; |
|
454 |
return unless $query; |
|
455 |
my $parsed = {}; |
|
456 |
||
457 |
# Flatten and clean query.
|
|
458 |
$query =~ s/\n/ /g; |
|
459 |
$query = $self->clean_query($query); |
|
460 |
||
461 |
$parsed->{query} = $query, |
|
462 |
$parsed->{tables} = $self->get_aliases($query, 1); |
|
463 |
$parsed->{columns} = $self->get_columns($query); |
|
464 |
||
465 |
my ($type) = $query =~ m/^(\w+)/; |
|
466 |
$parsed->{type} = lc $type; |
|
467 |
||
468 |
# my @words = $query =~ m/
|
|
469 |
# [A-Za-z_.]+\(.*?\)+ # Match FUNCTION(...)
|
|
470 |
# |\(.*?\)+ # Match grouped items
|
|
471 |
# |"(?:[^"]|\"|"")*"+ # Match double quotes
|
|
472 |
# |'[^'](?:|\'|'')*'+ # and single quotes
|
|
473 |
# |`(?:[^`]|``)*`+ # and backticks
|
|
474 |
# |[^ ,]+
|
|
475 |
# |,
|
|
476 |
#/gx;
|
|
477 |
||
478 |
$parsed->{sub_queries} = []; |
|
479 |
||
480 |
return $parsed; |
|
481 |
}
|
|
482 |
||
483 |
# Returns an array of arrayrefs like [db,tbl] for each unique db.tbl
|
|
484 |
# in the query and its subqueries. db may be undef.
|
|
485 |
sub extract_tables { |
|
486 |
my ( $self, %args ) = @_; |
|
487 |
my $query = $args{query}; |
|
488 |
my $default_db = $args{default_db}; |
|
489 |
my $q = $self->{Quoter} || $args{Quoter}; |
|
490 |
return unless $query; |
|
134
by Daniel Nichter
Replace MKDEBUG with PTDEBUG in modules. |
491 |
PTDEBUG && _d('Extracting tables'); |
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
492 |
my @tables; |
493 |
my %seen; |
|
494 |
foreach my $db_tbl ( $self->get_tables($query) ) { |
|
495 |
next unless $db_tbl; |
|
496 |
next if $seen{$db_tbl}++; # Unique-ify for issue 337. |
|
497 |
my ( $db, $tbl ) = $q->split_unquote($db_tbl); |
|
498 |
push @tables, [ $db || $default_db, $tbl ]; |
|
499 |
}
|
|
500 |
return @tables; |
|
501 |
}
|
|
502 |
||
503 |
# This is a special trim function that removes whitespace and identifier-quotes
|
|
504 |
# (backticks, in the case of MySQL) from the string.
|
|
505 |
sub trim_identifier { |
|
506 |
my ($self, $str) = @_; |
|
507 |
return unless defined $str; |
|
508 |
$str =~ s/`//g; |
|
509 |
$str =~ s/^\s+//; |
|
510 |
$str =~ s/\s+$//; |
|
511 |
return $str; |
|
512 |
}
|
|
513 |
||
514 |
sub _d { |
|
515 |
my ($package, undef, $line) = caller 0; |
|
516 |
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
|
517 |
map { defined $_ ? $_ : 'undef' } |
|
518 |
@_; |
|
519 |
print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
|
520 |
}
|
|
521 |
||
522 |
1; |
|
4
by Daniel Nichter
Enclose all packages blocks and add Package devel docu. |
523 |
}
|
2
by Daniel Nichter
Add lib/, t/lib/, and sandbox/. All modules are updated and passing on MySQL 5.1. |
524 |
# ###########################################################################
|
525 |
# End QueryParser package
|
|
526 |
# ###########################################################################
|