~percona-toolkit-dev/percona-toolkit/release-2.2.3

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
# ###########################################################################