1# Copyright (c) 2008, 2021, Oracle and/or its affiliates. 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, version 2.0, 5# as published by the Free Software Foundation. 6# 7# This program is also distributed with certain software (including 8# but not limited to OpenSSL) that is licensed under separate terms, 9# as designated in a particular file or component or in included license 10# documentation. The authors of MySQL hereby grant you an additional 11# permission to link the program and your derivative works with the 12# separately licensed software that they have included with MySQL. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License, version 2.0, for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program; if not, write to the Free Software 21# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 23################################################################################ 24# outer_join.yy 25# Purpose: Random Query Generator grammar for testing larger (6 - 10 tables) JOINs 26# Tuning: Please tweak the rule table_ref ratio of table:join for larger joins 27# NOTE: be aware that larger (15-20 tables) queries can take far too 28# long to run to be of much interest for fast, automated testing 29# 30# Notes: This grammar is designed to be used with gendata=conf/outer_join.zz 31# It can be altered, but one will likely need field names 32# Additionally, it is not recommended to use the standard RQG-produced 33# tables as they way we pick tables can result in the use of 34# several large tables that will bog down a generated query 35# 36# Please rely largely on the _portable variant of this grammar if 37# doing 3-way comparisons as it has altered code that will produce 38# more standards-compliant queries for use with other DBMS's 39# 40# We keep the grammar here as it is in order to also test certain 41# MySQL-specific syntax variants. 42################################################################################ 43 44query: 45 { @nonaggregates = (); @aggregates = (); $tables = 0; $ext = ""; $fields = 0; @outer_tables = (); $stack->push(); "" } 46 query_expr order_by_clause 47 { $stack->pop(undef) } 48 ; 49 50 51############################################### 52## Ref: ISO 9075, Chap 6.3 <table reference> ## 53############################################### 54################################################################################ 55# recommend 4 tables : 1 joins for smaller queries, 3:1 for larger ones 56################################################################################ 57table_ref: 58 table | table | table | table 59 | joined_table 60#| derived_table # See note where 'derived_table:' is defined. 61 ; 62 63## 64# There are two problems with subqry as derived_tables which are the reason for not 65# allowing them in this grammar: 66# 67# 1. Name resolving of outer referrence columns seems to be broken. 68# 2. Create correct references to columns from the derived tables are complicated within RQG. 69# It might be doable with logic similar to 'lookahead_for_table_alias' in order, 70# but I want invest time in this right now, and probably never. 71# 72# Testcoverage of the different flavours of subqueries are assumed to be sufficient 73# throught scalar_subqry and IN/EXISTS 74## 75derived_table_unused: 76 { $stack->push(); undef } 77 table_subquery 78 { my $x = " AS table".++$tables.$ext; my @s=($x); $stack->pop(\@s); $x } 79 ; 80 81 82############################################## 83## Ref: ISO 9075, Chap 6.x <value_expr_xxx> ## 84############################################## 85 86value_expr: 87 int_value_expr 88 | char_value_expr 89 ; 90 91int_value_expr: 92 _digit 93 | int_column 94#| int_aggregate 95 | int_value_expr + int_value_expr 96 | int_value_expr - int_value_expr 97 | CHAR_LENGTH(char_value_expr) 98 | (int_value_expr) 99#| int_scalar_subquery 100 ; 101 102char_value_expr: 103 char_column 104 | {"'".$prng->string(4)."'"} 105#| {"'"}_letter{"'"} 106#| char_aggregate 107#| UPPER(char_value_expr) 108#| LOWER(char_value_expr) 109#| TRIM(char_value_expr) 110 | (char_value_expr) 111#| char_scalar_subquery 112 ; 113 114char_string_literal: 115 {"'".$prng->string(4)."'"} 116 ; 117 118int_column: 119 table_alias.int_field_name 120 ; 121 122char_column: 123 table_alias.char_field_name 124 ; 125 126####################################################### 127## Ref: ISO 9075, Chap 7.2 <table value constructor> ## 128####################################################### 129table_value_constr: 130 VALUES table_value_list 131; 132 133table_value_list: 134 (1, 2, 3) 135 ; 136 137################################################ 138## Ref: ISO 9075, Chap 7.3 <table expression> ## 139################################################ 140table_expr: 141 from_clause where_clause group_by_clause having_clause 142 ; 143 144########################################### 145## Ref: ISO 9075, Chap 7.4 <from clause> ## 146########################################### 147from_clause: 148 FROM table_list 149 { scalar(@tables) == $table_refs or die "Table lookahead predicted ".scalar(@tables)." tables, found: ".$tables."\n"; undef } 150 ; 151 152#### 153# <table referrence> has been inlined into <table list>, and decomposed 154# in order to increase frequency of: 155# - <table>, <table>, <table>... 156# - single <joined tables> 157# - single <table subquery> 158# Particularly we want to avoid to many <joined table>, <joined table> .... 159# being produced, they are allowed though. 160# Tweak the rule 'table_ref_list' to adjust this. 161#### 162table_list: 163 table_ref_list 164#| table_only_list 165 ; 166 167outer_join_pattern_unused: 168 { $stack->push() } 169 table_ref { $stack->set("left",$stack->get("result")); } 170 outer_join_type JOIN table_ref join_spec 171 { my $left = $stack->get("left"); my $right = $stack->get("result"); my @n = (); push(@n,@$right); push(@n,@$left); $stack->pop(\@n); return undef } 172 ; 173 174table_only_list: 175 table 176 | table, table_only_list 177 178# { $stack->push() } 179# table, { $stack->set("left",$stack->get("result")); } 180# table_only_list 181 ; 182 183table_ref_list: 184 table 185 | composite_table_ref 186 | composite_table_ref 187 | composite_table_ref 188 | composite_table_ref 189 | composite_table_ref 190#| composite_table_ref, table_list 191 ; 192 193composite_table_ref: 194 joined_table 195#| derived_table # See note where 'derived_table:' is defined. 196 ; 197 198table: 199 # We use the "AS table" bit here so we can have unique aliases if we use the same table many times 200 { $stack->push(); my $x = $prng->arrayElement($executors->[0]->tables())." AS table".++$tables.$ext; my @s=($x); $stack->pop(\@s); $x } 201 ; 202 203lookahead_for_table_alias: 204 { @table_refs = (); my $tmp = join('', @sentence); my $lookahead = substr($tmp,index($tmp,$_)+length($_)); while ($lookahead =~ s/[(](?!SELECT)([^()]*)[)]/$1/g or $lookahead =~ s/[(]SELECT[^()]*[)]/<subqry>/g or $lookahead =~ s/(^[^(()]*)[)].*/$1/) {}; my @tab=(1..($lookahead =~ s/AS table//g)); foreach $id (@tab) { push (@table_refs,"table".$id.$ext) }; "" } 205 ; 206 207# NOTE can't make use of 'existing_table_item' as <table_expr> has not been produced yet 208table_alias: 209 # If there are 'outer tables': choose either inner or outer table_ref 210 { $prng->arrayElement(\@table_refs) } 211 | { scalar(@outer_tables) > 0 ? $prng->arrayElement(\@outer_tables) : $prng->arrayElement(\@table_refs) } 212 ; 213 214outer_table_unused: 215 { $prng->arrayElement(\@outer_tables) } 216 ; 217 218 219############################################ 220## Ref: ISO 9075, Chap 7.5 <joined_table> ## 221############################################ 222joined_table: 223 qualified_join 224 | qualified_join 225 | qualified_join 226 | qualified_join 227 | qualified_join 228 | qualified_join 229#| cross_join 230 | (joined_table) 231 ; 232 233qualified_join: 234 inner_join 235 | outer_join 236 ; 237 238cross_join: 239 table_ref CROSS JOIN table_ref 240 # Stupid MySQL extension where missing 'cross' join is determined based on no join_condition 241 | table_ref INNER JOIN table_ref 242 | table_ref JOIN table_ref 243 ; 244 245# MySQL has the (stupid) cross_join extension (above) where it actually is the presence of the join_cond which 246# determines if the join is a cross- or inner-join. This creates ambigous parser precedence rules which requires 247# us to enclose right side argument in paranthesis - Else the 'outer_join:' rule is more in accordance 248# with the ISO 9075 standards. 249inner_join: 250 { $stack->push() } 251 table_ref { $stack->set("left",$stack->get("result")); } 252 inner_join_type JOIN (table_ref) join_spec 253 { my $left = $stack->get("left"); my $right = $stack->get("result"); my @n = (); push(@n,@$right); push(@n,@$left); $stack->pop(\@n); return undef } 254 ; 255 256outer_join: 257 { $stack->push() } 258 table_ref { $stack->set("left",$stack->get("result")); } 259 outer_join_type JOIN table_ref join_spec 260 { my $left = $stack->get("left"); my $right = $stack->get("result"); my @n = (); push(@n,@$right); push(@n,@$left); $stack->pop(\@n); return undef } 261 ; 262 263join_spec: 264 ON join_condition 265#| USING (pk) # Hard to handle, need to refer a avail common column from left / right 266 ; 267 268inner_join_type: 269 # Unspecified -> <join_type> implies 'INNER' 270 | INNER 271 ; 272 273outer_join_type: 274 LEFT outer 275 | RIGHT outer 276 ; 277 278outer: 279 | OUTER 280 ; 281 282# Prefer int_condition as char_condition will hardly ever match. 283join_condition: 284 int_condition 285 | int_condition 286 | int_condition 287 | int_condition 288 | int_condition 289 | int_condition 290 | char_condition 291 | other_condition 292 ; 293 294int_condition: 295 existing_left_table.int_field_name = existing_right_table.int_field_name # General rule 296 | existing_left_table.int_indexed = existing_right_table.int_indexed # Want more joins on indexed field 297 | int_multi_conditions 298 ; 299 300# Most of these join conditions crafted to match specific unique indexes 301int_multi_conditions: 302 # ix1(col_int,col_int_unique) 303 existing_left_table.col_int = existing_right_table.col_int AND 304 existing_left_table.col_int_unique = existing_right_table.col_int_unique 305 | 306 # ix2((col_int_key,col_int_unique)) 307 existing_left_table.col_int_key = existing_right_table.col_int_key AND 308 existing_left_table.col_int_unique = existing_right_table.col_int_unique 309 | 310 # Variant of ix2() above 311 existing_left_table.col_int_key = existing_right_table.int_field_name AND 312 existing_left_table.col_int_unique = existing_right_table.int_field_name 313 | 314 # ix3(col_int,col_int_key,col_int_unique) 315 existing_left_table.col_int = existing_right_table.col_int AND 316 existing_left_table.col_int_key = existing_right_table.col_int_key AND 317 existing_left_table.col_int_unique = existing_right_table.col_int_unique 318#| 319# int_condition AND int_condition 320 ; 321 322char_condition: 323 existing_left_table.char_field_name = existing_right_table.char_field_name # General rule 324 | existing_left_table.char_indexed = existing_right_table.char_indexed # Want more joins on indexed field 325 | char_multi_conditions 326 ; 327 328char_multi_conditions: 329 # ix1(col_char_16,col_char_16_unique) 330 existing_left_table.col_char_16 = existing_right_table.col_char_16 AND 331 existing_left_table.col_char_16_unique = existing_right_table.col_char_16_unique 332 | 333 # ix2(col_varchar_256,col_char_16_unique) 334 existing_left_table.col_varchar_256 = existing_right_table.col_varchar_256 AND 335 existing_left_table.col_varchar_10_unique = existing_right_table.col_varchar_10_unique 336#| 337# char_condition AND char_condition 338 ; 339 340other_condition: 341 existing_left_table.col_int comparison_operator existing_right_table.col_int 342 | existing_left_table.col_int IS not NULL 343 | existing_right_table.col_int IS not NULL 344 | existing_left_table.col_int not IN (number_list) 345 | existing_right_table.col_int not IN (number_list) 346 | join_condition and_or join_condition 347 | not (join_condition) is_truth_value 348 ; 349 350existing_left_table: 351 { my $left = $stack->get("left"); my %s=map{$_=>1} @$left; my @r=(keys %s); my $table_string = $prng->arrayElement(\@r); my @table_array = split(/AS/, $table_string); $table_array[1] } 352 ; 353 354existing_right_table: 355 { my $right = $stack->get("result"); my %s=map{$_=>1} @$right; my @r=(keys %s); my $table_string = $prng->arrayElement(\@r); my @table_array = split(/AS/, $table_string); $table_array[1] } 356 ; 357 358 359############################################ 360## Ref: ISO 9075, Chap 7.6 <where clause> ## 361############################################ 362where_clause: 363 # <where_clause> is optional 364 | WHERE search_condition 365 ; 366 367################################################################################ 368# We ensure that a GROUP BY statement includes all nonaggregates. # 369# This helps to ensure the query is more useful in detecting real errors / # 370# that the query doesn't lend itself to variable result sets # 371################################################################################ 372############################################### 373## Ref: ISO 9075, Chap 7.7 <group by clause> ## 374############################################### 375group_by_clause: 376 group_by_iff_aggregate 377 | group_by_iff_aggregate 378 | group_by_iff_aggregate 379 | group_by_iff_aggregate 380 | group_by_iff_aggregate 381 | group_by_iff_aggregate 382 | group_by 383 ; 384 385## Conditionaly specify 'GROUP BY' if table expression is a grouped table 386group_by_iff_aggregate: 387 { scalar(@aggregates) > 0 and scalar(@nonaggregates) > 0 ? " GROUP BY ".join (', ' , @nonaggregates ) : "" } 388 ; 389 390group_by: 391 { scalar(@nonaggregates) > 0 ? " GROUP BY ".join (', ' , @nonaggregates ) : "" } 392 ; 393 394############################################# 395## Ref: ISO 9075, Chap 7.8 <having clause> ## 396############################################# 397having_clause: 398 HAVING having_list 399 ||||| 400 ; 401 402having_list: 403 having_item 404 | having_item 405 | (having_list and_or having_item) 406 ; 407 408having_item: 409 existing_select_item comparison_operator _digit 410 ; 411 412and_or: 413 AND 414 | AND 415 | OR 416 ; 417 418################################################### 419## Ref: ISO 9075, Chap 7.9 <query specification> ## 420################################################### 421query_spec: 422 SELECT lookahead_for_table_alias 423 distinct straight_join select_option select_list table_expr 424 ; 425 426distinct: |||| DISTINCT ; 427 428select_option: 429 | SQL_SMALL_RESULT 430#| SQL_BIG_RESULT # Need fix for Bug#53534 431 ||||||| 432 ; 433 434straight_join: ||||||||||| STRAIGHT_JOIN ; 435 436# Tuned to prefer short <select list>'s 437select_list: 438 select_item 439 | select_item, select_item 440 | select_item, select_item 441 | select_item, select_list 442 ; 443 444select_item: 445 int_select_item 446 | char_select_item 447 ; 448 449int_select_item: 450 int_value | int_value | int_value | int_value | int_value 451#| int_aggregate # Need fix for bug#53485 452 ; 453 454char_select_item: 455 char_value | char_value | char_value | char_value | char_value 456#| char_aggregate # Need fix for bug#53485 457 ; 458 459int_value: 460 int_value_expr AS nonaggregate_field 461 | int_column AS nonaggregate_field 462 | int_column AS nonaggregate_field 463 | int_column AS nonaggregate_field 464 | int_column AS nonaggregate_field 465 | int_column AS nonaggregate_field 466 | int_column AS nonaggregate_field 467 | int_column AS nonaggregate_field 468 | int_column AS nonaggregate_field 469 | int_column AS nonaggregate_field 470 ; 471 472char_value: 473 char_value_expr AS nonaggregate_field 474 | char_column AS nonaggregate_field 475 | char_column AS nonaggregate_field 476 | char_column AS nonaggregate_field 477 | char_column AS nonaggregate_field 478 | char_column AS nonaggregate_field 479 | char_column AS nonaggregate_field 480 | char_column AS nonaggregate_field 481 | char_column AS nonaggregate_field 482 | char_column AS nonaggregate_field 483 ; 484 485int_aggregate: 486 COUNT(*) AS aggregate_field 487 | COUNT(distinct value_expr) AS aggregate_field 488 | SUM(distinct int_value_expr) AS aggregate_field 489 | MIN(distinct int_value_expr) AS aggregate_field 490 | MAX(distinct int_value_expr) AS aggregate_field 491 ; 492 493char_aggregate: 494 MIN(distinct char_value_expr) AS aggregate_field 495 | MAX(distinct char_value_expr) AS aggregate_field 496 ; 497 498nonaggregate_field: 499 { my $f = "field".++$fields; push @nonaggregates, $f ; $f } 500 ; 501 502aggregate_field: 503 { my $f = "field".++$fields; push @aggregates, $f ; $f } 504 ; 505 506################################################# 507## Ref: ISO 9075, Chap 7.10 <query expression> ## 508################################################# 509query_expr: 510 non_join_query_expr 511#| joined_table # MySql unsupported 512 ; 513 514non_join_query_expr: 515 non_join_query_term 516#| query_expr UNION query_term 517#| query_expr EXCEPT query_term 518 ; 519 520query_term: 521 non_join_query_term 522#| joined_table # MySql unsupported 523 ; 524 525non_join_query_term: 526 non_join_query_primary 527#| query_term INTERSECT query_primary 528 ; 529 530query_primary: 531 non_join_query_primary 532#| joined_table # MySql unsupported 533 ; 534 535non_join_query_primary: 536 simple_table 537 | simple_table 538 | simple_table 539 | (non_join_query_expr) 540 ; 541 542simple_table: 543 query_spec 544#| table_value_constr # MySql unsupported 545#| explicit_table # MySql unsupported 546 ; 547 548explicit_table: 549 TABLE table 550 ; 551 552corresponding_spec: 553 CORRESPONDING 554 ; 555 556######################################### 557## Ref: ISO 9075, Chap 7.11 <subquery> ## 558######################################### 559 560int_scalar_subquery: 561 int_subquery_1row 562 ; 563 564char_scalar_subquery: 565 char_subquery_1row 566 ; 567 568int_table_subquery: 569 int_subquery_1row 570 ; 571 572char_table_subquery: 573 char_subquery_1row 574 ; 575 576int_subquery_1row: 577 subqry_enter 578 (SELECT lookahead_for_table_alias 579 distinct straight_join select_option int_select_item table_expr) 580 subqry_leave 581 ; 582 583char_subquery_1row: 584 subqry_enter 585 (SELECT lookahead_for_table_alias 586 distinct straight_join select_option char_select_item table_expr) 587 subqry_leave 588 ; 589 590table_subquery: 591 subquery 592 ; 593 594subquery: 595 subqry_enter 596 (query_expr) 597 subqry_leave 598 ; 599 600subqry_enter: 601 { $stack->push(); undef } 602 { $stack->set("tables",$tables); $tables = 0; $ext = $ext."s"; undef} 603 { my @tmp = @outer_tables; $stack->set("outer_tables",\@tmp); @outer_tables = @table_refs; undef} 604 { my @tmp = @table_refs; $stack->set("table_refs",\@tmp); @table_refs = (); undef} 605 { my @tmp = @nonaggregates; $stack->set("nonaggregates",\@tmp); @nonaggregates = (); undef} 606 { my @tmp = @aggregates; $stack->set("aggregates",\@tmp); @aggregates = (); undef} 607 ; 608 609subqry_leave: 610 { $tables = $stack->get("tables"); chop $ext; undef } 611 { @outer_tables = @{$stack->get("outer_tables")}; undef } 612 { @table_refs = @{$stack->get("table_refs")}; undef } 613 { @aggregates = @{$stack->get("aggregates")}; undef } 614 { @nonaggregates = @{$stack->get("nonaggregates")}; undef} 615 { $stack->pop(); undef} 616 ; 617 618correlate_iff_subquery_unused: 619 { scalar(@outer_tables) > 0 ? $prng->arrayElement(\@table_refs).".pk "."= ".$prng->arrayElement(\@outer_tables).".pk " : "" } 620 ; 621 622 623################################################# 624## Ref: ISO 9075, Chap 8.12 <search condition> ## 625################################################# 626search_condition: 627 boolean_factor 628 | boolean_factor 629 | boolean_factor 630 | boolean_factor 631 | boolean_factor 632 | search_condition and_or boolean_factor 633 ; 634 635boolean_factor: 636 not boolean_primary is_truth_value 637 ; 638 639is_truth_value: 640 IS truth_value 641 |||||||||||||||| 642 ; 643 644truth_value: 645 TRUE | FALSE | UNKNOWN 646 ; 647 648boolean_primary: 649 predicate 650 | predicate 651 | predicate 652 | predicate 653 | (search_condition) 654 ; 655 656predicate: 657 common_int_predicate 658 | common_int_predicate 659 | common_int_predicate 660 | common_int_predicate 661 | common_int_predicate 662 | common_char_predicate 663 | other_predicate 664 ; 665 666common_int_predicate: 667 int_column comparison_operator _digit 668 | int_column comparison_operator _digit 669 | int_column comparison_operator _digit 670 | int_column comparison_operator int_column 671 | int_column not BETWEEN _digit[invariant] AND _digit[invariant]+_digit 672 ; 673 674common_char_predicate: 675 char_column comparison_operator char_column 676 ; 677 678other_predicate: 679 int_value_expr comparison_operator int_value_expr 680 | char_value_expr comparison_operator char_value_expr 681 | value_expr IS not NULL 682 | int_value_expr not BETWEEN int_value_expr AND int_value_expr 683 | char_value_expr not LIKE {"'%".$prng->string(3)."%'"} 684 | EXISTS table_subquery 685 | int_value_expr not IN (number_list) 686 | int_value_expr not IN int_table_subquery 687 | char_value_expr not IN char_table_subquery 688 | int_value_expr comparison_operator quantifier int_table_subquery 689 | char_value_expr comparison_operator quantifier char_table_subquery 690#| UNIQUE table_subquery # MySql unsupported 691 ; 692 693number_list: 694 _digit | number_list, _digit 695 ; 696 697quantifier: 698 ALL | ANY 699 ; 700 701################################################################################ 702# We use the total_order_by rule when using the LIMIT operator to ensure that # 703# we have a consistent result set - server1 and server2 should not differ # 704################################################################################ 705 706order_by_clause: 707 ORDER BY total_order_by desc limit 708 | ORDER BY order_by_list 709 ||||| 710 ; 711 712total_order_by: 713 { my @tmp = (@nonaggregates, @aggregates); $prng->shuffleArray(\@tmp); join(',', @tmp) } 714 ; 715 716order_by_list: 717 order_by_item 718 | order_by_item, order_by_list 719 ; 720 721order_by_item: 722 existing_select_item desc 723 ; 724 725desc: 726 ASC 727 | DESC 728 ||||| 729 ; 730 731################################################################################ 732# We mix digit and _digit here. We want to alter the possible values of LIMIT # 733# To ensure we hit varying EXPLAIN plans, but the OFFSET can be smaller # 734################################################################################ 735 736limit: 737 LIMIT limit_size 738 | LIMIT limit_size OFFSET _digit 739 ||| 740 ; 741 742field_name: 743 int_field_name 744 | char_field_name 745 | datetime_field_name 746 ; 747 748int_field_name: 749 pk | col_int_key | col_int_unique | col_int 750 ; 751 752int_indexed: 753 pk | col_int_key | col_int_unique 754 ; 755 756################################################################# 757# Charset and collation should be specified when starting mysqld 758# or creating the database. 759# In order to get a deterministic resultset for 'ORDER BY ... LIMIT' 760# ensure that the collation is case sensitive (or binary). 761################################################################# 762char_field_name: 763 col_char_16 764 | col_char_16_key 765 | col_char_16_unique 766 | col_varchar_10 767 | col_varchar_10_key 768 | col_varchar_10_unique 769 | col_varchar_256 770 | col_varchar_256_key 771 | col_varchar_256_unique 772 ; 773 774char_indexed: 775 col_char_16_key 776 | col_char_16_key 777 | col_varchar_10_key 778 | col_varchar_10_unique 779 | col_varchar_256_key 780 | col_varchar_256_unique 781 ; 782 783datetime_field_name: 784 col_datetime 785 | col_datetime_key 786 | col_datetime_unique 787 ; 788 789datetime_indexed: 790 col_datetime_key 791 | col_datetime_unique 792 ; 793 794existing_table_item: 795 { "table".$prng->int(1,$tables) } 796 ; 797 798existing_select_item: 799 { my @tmp = (@nonaggregates, @aggregates); $prng->arrayElement(\@tmp) } 800 ; 801 802_digit: 803 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 804 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | _tinyint_unsigned 805 ; 806 807comparison_operator: 808 = | = | = | = | = 809 | > | < | != | <> | <= | >= 810 ; 811 812not: 813 NOT |||||||||||| 814; 815 816################################################################################ 817# We define LIMIT_rows in this fashion as LIMIT values can differ depending on # 818# how large the LIMIT is - LIMIT 2 = LIMIT 9 != LIMIT 19 # 819################################################################################ 820 821limit_size: 822 1 | 2 | 10 | 100 | 1000; 823 824