From: Norbert Buchmuller Date: Mon, 15 Dec 2008 21:26:39 +0000 (+0000) Subject: Merged changes from the '1.50_RC-extraparens' branch. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=25823711a9e82eba7d569ec08a5cc0c40658def2;p=scpubgit%2FQ-Branch.git Merged changes from the '1.50_RC-extraparens' branch. --- diff --git a/lib/SQL/Abstract/Test.pm b/lib/SQL/Abstract/Test.pm index 4153add..6c47bcf 100644 --- a/lib/SQL/Abstract/Test.pm +++ b/lib/SQL/Abstract/Test.pm @@ -16,13 +16,63 @@ our $case_sensitive = 0; our $sql_differ; # keeps track of differing portion between SQLs our $tb = __PACKAGE__->builder; +# Parser states for _recurse_parse() +use constant { + PARSE_TOP_LEVEL => 0, + PARSE_IN_EXPR => 1, + PARSE_IN_PARENS => 2, +}; + +# These SQL keywords always signal end of the current expression (except inside +# of a parenthesized subexpression). +# Format: A list of strings that will be compiled to extended syntax (ie. +# /.../x) regexes, without capturing parentheses. They will be automatically +# anchored to word boundaries to match the whole token). +my @expression_terminator_sql_keywords = ( + 'FROM', + '(?: + (?: + (?: \b (?: LEFT | RIGHT | FULL ) \s+ )? + (?: \b (?: CROSS | INNER | OUTER ) \s+ )? + )? + JOIN + )', + 'ON', + 'WHERE', + 'GROUP \s+ BY', + 'HAVING', + 'ORDER \s+ BY', + 'LIMIT', + 'OFFSET', + 'FOR', + 'UNION', + 'INTERSECT', + 'EXCEPT', +); + +my $tokenizer_re_str = join('|', + map { '\b' . $_ . '\b' } + @expression_terminator_sql_keywords, 'AND', 'OR' +); + +my $tokenizer_re = qr/ + \s* + ( + \( + | + \) + | + $tokenizer_re_str + ) + \s* +/xi; + + sub is_same_sql_bind { my ($sql1, $bind_ref1, $sql2, $bind_ref2, $msg) = @_; # compare - my $tree1 = parse($sql1); - my $tree2 = parse($sql2); - my $same_sql = eq_sql($tree1, $tree2); + my $same_sql = eq_sql($sql1, $sql2); my $same_bind = eq_bind($bind_ref1, $bind_ref2); # call Test::Builder::ok @@ -51,6 +101,16 @@ sub eq_bind { } sub eq_sql { + my ($sql1, $sql2) = @_; + + # parse + my $tree1 = parse($sql1); + my $tree2 = parse($sql2); + + return _eq_sql($tree1, $tree2); +} + +sub _eq_sql { my ($left, $right) = @_; # ignore top-level parentheses @@ -74,8 +134,8 @@ sub eq_sql { return $eq; } else { # binary operator - return eq_sql($left->[1][0], $right->[1][0]) # left operand - && eq_sql($left->[1][1], $right->[1][1]); # right operand + return _eq_sql($left->[1][0], $right->[1][0]) # left operand + && _eq_sql($left->[1][1], $right->[1][1]); # right operand } } } @@ -84,27 +144,37 @@ sub eq_sql { sub parse { my $s = shift; - # tokenize string - my $tokens = [grep {!/^\s*$/} split /\s*(\(|\)|\bAND\b|\bOR\b)\s*/, $s]; + # tokenize string, and remove all optional whitespace + my $tokens = []; + foreach my $token (split $tokenizer_re, $s) { + $token =~ s/\s+/ /g; + $token =~ s/\s+([^\w\s])/$1/g; + $token =~ s/([^\w\s])\s+/$1/g; + push @$tokens, $token if $token !~ /^$/; + } - my $tree = _recurse_parse($tokens); + my $tree = _recurse_parse($tokens, PARSE_TOP_LEVEL); return $tree; } sub _recurse_parse { - my $tokens = shift; + my ($tokens, $state) = @_; my $left; while (1) { # left-associative parsing my $lookahead = $tokens->[0]; - return $left if !defined($lookahead) || $lookahead eq ')'; + return $left if !defined($lookahead) + || ($state == PARSE_IN_PARENS && $lookahead eq ')') + || ($state == PARSE_IN_EXPR && grep { $lookahead =~ /^$_$/xi } + '\)', @expression_terminator_sql_keywords + ); my $token = shift @$tokens; # nested expression in () if ($token eq '(') { - my $right = _recurse_parse($tokens); + my $right = _recurse_parse($tokens, PARSE_IN_PARENS); $token = shift @$tokens or croak "missing ')'"; $token eq ')' or croak "unexpected token : $token"; $left = $left ? [CONCAT => [$left, [PAREN => $right]]] @@ -112,9 +182,15 @@ sub _recurse_parse { } # AND/OR elsif ($token eq 'AND' || $token eq 'OR') { - my $right = _recurse_parse($tokens); + my $right = _recurse_parse($tokens, PARSE_IN_EXPR); $left = [$token => [$left, $right]]; } + # expression terminator keywords (as they start a new expression) + elsif (grep { $token =~ /^$_$/xi } @expression_terminator_sql_keywords) { + my $right = _recurse_parse($tokens, PARSE_IN_EXPR); + $left = $left ? [CONCAT => [$left, [CONCAT => [[EXPR => $token], [PAREN => $right]]]]] + : [CONCAT => [[EXPR => $token], [PAREN => $right]]]; + } # leaf expression else { $left = $left ? [CONCAT => [$left, [EXPR => $token]]] @@ -220,10 +296,12 @@ where a difference was encountered. L, L, L. -=head1 AUTHOR +=head1 AUTHORS Laurent Dami, Elaurent.dami AT etat geneve chE +Norbert Buchmuller + =head1 COPYRIGHT AND LICENSE Copyright 2008 by Laurent Dami. diff --git a/t/10test.t b/t/10test.t index af6404d..f114d51 100644 --- a/t/10test.t +++ b/t/10test.t @@ -3,11 +3,455 @@ use strict; use warnings; use List::Util qw(sum); -use Data::Dumper; use Test::More; +my @sql_tests = ( + # WHERE condition - equal + { + equal => 1, + statements => [ + q/SELECT foo FROM bar WHERE a = 1/, + q/SELECT foo FROM bar WHERE a=1/, + q/SELECT foo FROM bar WHERE (a = 1)/, + q/SELECT foo FROM bar WHERE (a=1)/, + q/SELECT foo FROM bar WHERE ( a = 1 )/, + q/ + SELECT + foo + FROM + bar + WHERE + a = 1 + /, + q/ + SELECT + foo + FROM + bar + WHERE + (a = 1) + /, + q/ + SELECT + foo + FROM + bar + WHERE + ( a = 1 ) + /, + q/SELECT foo FROM bar WHERE ((a = 1))/, + q/SELECT foo FROM bar WHERE ( (a = 1) )/, + q/SELECT foo FROM bar WHERE ( ( a = 1 ) )/, + ] + }, + { + equal => 1, + statements => [ + q/SELECT foo FROM bar WHERE a = 1 AND b = 1/, + q/SELECT foo FROM bar WHERE (a = 1) AND (b = 1)/, + q/SELECT foo FROM bar WHERE ((a = 1) AND (b = 1))/, + q/SELECT foo FROM bar WHERE (a = 1 AND b = 1)/, + q/SELECT foo FROM bar WHERE ((a = 1 AND b = 1))/, + q/SELECT foo FROM bar WHERE (((a = 1) AND (b = 1)))/, + q/ + SELECT + foo + FROM + bar + WHERE + a = 1 + AND + b = 1 + /, + q/ + SELECT + foo + FROM + bar + WHERE + (a = 1 + AND + b = 1) + /, + q/ + SELECT + foo + FROM + bar + WHERE + (a = 1) + AND + (b = 1) + /, + q/ + SELECT + foo + FROM + bar + WHERE + ((a = 1) + AND + (b = 1)) + /, + ] + }, + + # WHERE condition - different + { + equal => 0, + statements => [ + q/SELECT foo FROM bar WHERE a = 1/, + q/SELECT quux FROM bar WHERE a = 1/, + q/SELECT foo FROM quux WHERE a = 1/, + q/FOOBAR foo FROM bar WHERE a = 1/, + + q/SELECT foo FROM bar WHERE a = 2/, + q/SELECT foo FROM bar WHERE a < 1/, + q/SELECT foo FROM bar WHERE b = 1/, + q/SELECT foo FROM bar WHERE (c = 1)/, + q/SELECT foo FROM bar WHERE (d = 1)/, + + q/SELECT foo FROM bar WHERE a = 1 AND quux/, + q/SELECT foo FROM bar WHERE a = 1 GROUP BY foo/, + q/SELECT foo FROM bar WHERE a = 1 ORDER BY foo/, + q/SELECT foo FROM bar WHERE a = 1 LIMIT 1/, + q/SELECT foo FROM bar WHERE a = 1 OFFSET 1/, + q/SELECT foo FROM bar JOIN quux WHERE a = 1/, + q/SELECT foo FROM bar JOIN quux ON a = 1 WHERE a = 1/, + ] + }, + { + equal => 0, + statements => [ + q/SELECT foo FROM bar WHERE a = 1 AND b = 1/, + q/SELECT quux FROM bar WHERE a = 1 AND b = 1/, + q/SELECT foo FROM quux WHERE a = 1 AND b = 1/, + q/FOOBAR foo FROM bar WHERE a = 1 AND b = 1/, + + q/SELECT foo FROM bar WHERE a = 2 AND b = 1/, + q/SELECT foo FROM bar WHERE a = 3 AND (b = 1)/, + q/SELECT foo FROM bar WHERE (a = 4) AND b = 1/, + q/SELECT foo FROM bar WHERE (a = 5) AND (b = 1)/, + q/SELECT foo FROM bar WHERE ((a = 6) AND (b = 1))/, + q/SELECT foo FROM bar WHERE ((a = 7) AND (b = 1))/, + + q/SELECT foo FROM bar WHERE a = 1 AND b = 2/, + q/SELECT foo FROM bar WHERE a = 1 AND (b = 3)/, + q/SELECT foo FROM bar WHERE (a = 1) AND b = 4/, + q/SELECT foo FROM bar WHERE (a = 1) AND (b = 5)/, + q/SELECT foo FROM bar WHERE ((a = 1) AND (b = 6))/, + q/SELECT foo FROM bar WHERE ((a = 1) AND (b = 7))/, + + q/SELECT foo FROM bar WHERE a < 1 AND b = 1/, + q/SELECT foo FROM bar WHERE b = 1 AND b = 1/, + q/SELECT foo FROM bar WHERE (c = 1) AND b = 1/, + q/SELECT foo FROM bar WHERE (d = 1) AND b = 1/, + + q/SELECT foo FROM bar WHERE a = 1 AND b = 1 AND quux/, + q/SELECT foo FROM bar WHERE a = 1 AND b = 1 GROUP BY foo/, + q/SELECT foo FROM bar WHERE a = 1 AND b = 1 ORDER BY foo/, + q/SELECT foo FROM bar WHERE a = 1 AND b = 1 LIMIT 1/, + q/SELECT foo FROM bar WHERE a = 1 AND b = 1 OFFSET 1/, + q/SELECT foo FROM bar JOIN quux WHERE a = 1 AND b = 1/, + q/SELECT foo FROM bar JOIN quux ON a = 1 WHERE a = 1 AND b = 1/, + ] + }, + + # JOIN condition - equal + { + equal => 1, + statements => [ + q/SELECT foo FROM bar JOIN baz ON a = 1 WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON a=1 WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON (a = 1) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON (a=1) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON ( a = 1 ) WHERE x = 1/, + q/ + SELECT + foo + FROM + bar + JOIN + baz + ON + a = 1 + WHERE + x = 1 + /, + q/ + SELECT + foo + FROM + bar + JOIN + baz + ON + (a = 1) + WHERE + x = 1 + /, + q/ + SELECT + foo + FROM + bar + JOIN + baz + ON + ( a = 1 ) + WHERE + x = 1 + /, + q/SELECT foo FROM bar JOIN baz ON ((a = 1)) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON ( (a = 1) ) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON ( ( a = 1 ) ) WHERE x = 1/, + ] + }, + { + equal => 1, + statements => [ + q/SELECT foo FROM bar JOIN baz ON a = 1 AND b = 1 WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON (a = 1) AND (b = 1) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON ((a = 1) AND (b = 1)) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON (a = 1 AND b = 1) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON ((a = 1 AND b = 1)) WHERE x = 1/, + q/SELECT foo FROM bar JOIN baz ON (((a = 1) AND (b = 1))) WHERE x = 1/, + q/ + SELECT + foo + FROM + bar + JOIN + baz + ON + a = 1 + AND + b = 1 + WHERE + x = 1 + /, + q/ + SELECT + foo + FROM + bar + JOIN + baz + ON + (a = 1 + AND + b = 1) + WHERE + x = 1 + /, + q/ + SELECT + foo + FROM + bar + JOIN + baz + ON + (a = 1) + AND + (b = 1) + WHERE + x = 1 + /, + q/ + SELECT + foo + FROM + bar + JOIN + baz + ON + ((a = 1) + AND + (b = 1)) + WHERE + x = 1 + /, + ] + }, + + # JOIN condition - different + { + equal => 0, + statements => [ + q/SELECT foo FROM bar JOIN quux ON a = 1 WHERE quuux/, + q/SELECT quux FROM bar JOIN quux ON a = 1 WHERE quuux/, + q/SELECT foo FROM quux JOIN quux ON a = 1 WHERE quuux/, + q/FOOBAR foo FROM bar JOIN quux ON a = 1 WHERE quuux/, + + q/SELECT foo FROM bar JOIN quux ON a = 2 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON a < 1 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON b = 1 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (c = 1) WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (d = 1) WHERE quuux/, + + q/SELECT foo FROM bar JOIN quux ON a = 1 AND quuux/, + q/SELECT foo FROM bar JOIN quux ON a = 1 GROUP BY foo/, + q/SELECT foo FROM bar JOIN quux ON a = 1 ORDER BY foo/, + q/SELECT foo FROM bar JOIN quux ON a = 1 LIMIT 1/, + q/SELECT foo FROM bar JOIN quux ON a = 1 OFFSET 1/, + q/SELECT foo FROM bar JOIN quux ON a = 1 JOIN quuux/, + q/SELECT foo FROM bar JOIN quux ON a = 1 JOIN quuux ON a = 1/, + ] + }, + { + equal => 0, + statements => [ + q/SELECT foo FROM bar JOIN quux ON a = 1 AND b = 1 WHERE quuux/, + q/SELECT quux FROM bar JOIN quux ON a = 1 AND b = 1 WHERE quuux/, + q/SELECT foo FROM quux JOIN quux ON a = 1 AND b = 1 WHERE quuux/, + q/FOOBAR foo FROM bar JOIN quux ON a = 1 AND b = 1 WHERE quuux/, + + q/SELECT foo FROM bar JOIN quux ON a = 2 AND b = 1 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON a = 3 AND (b = 1) WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (a = 4) AND b = 1 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (a = 5) AND (b = 1) WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON ((a = 6) AND (b = 1)) WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON ((a = 7) AND (b = 1)) WHERE quuux/, + + q/SELECT foo FROM bar JOIN quux ON a = 1 AND b = 2 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON a = 1 AND (b = 3) WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (a = 1) AND b = 4 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (a = 1) AND (b = 5) WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON ((a = 1) AND (b = 6)) WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON ((a = 1) AND (b = 7)) WHERE quuux/, + + q/SELECT foo FROM bar JOIN quux ON a < 1 AND b = 1 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON b = 1 AND b = 1 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (c = 1) AND b = 1 WHERE quuux/, + q/SELECT foo FROM bar JOIN quux ON (d = 1) AND b = 1 WHERE quuux/, + + q/SELECT foo FROM bar JOIN quux ON a = 1 AND b = 1 AND quuux/, + q/SELECT foo FROM bar JOIN quux ON a = 1 AND b = 1 GROUP BY foo/, + q/SELECT foo FROM bar JOIN quux ON a = 1 AND b = 1 ORDER BY foo/, + q/SELECT foo FROM bar JOIN quux ON a = 1 AND b = 1 LIMIT 1/, + q/SELECT foo FROM bar JOIN quux ON a = 1 AND b = 1 OFFSET 1/, + q/SELECT foo FROM bar JOIN quux JOIN quuux ON a = 1 AND b = 1/, + q/SELECT foo FROM bar JOIN quux ON a = 1 JOIN quuux ON a = 1 AND b = 1/, + ] + }, + + # DISTINCT ON (...) not confused with JOIN ON (...) + { + equal => 1, + statements => [ + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE a = 1/, + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE a=1/, + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE (a = 1)/, + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE (a=1)/, + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE ( a = 1 )/, + q/ + SELECT DISTINCT ON (foo, quux) + foo, + quux + FROM + bar + WHERE + a = 1 + /, + q/ + SELECT DISTINCT ON (foo, quux) + foo, + quux + FROM + bar + WHERE + (a = 1) + /, + q/ + SELECT DISTINCT ON (foo, quux) + foo, + quux + FROM + bar + WHERE + ( a = 1 ) + /, + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE ((a = 1))/, + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE ( (a = 1) )/, + q/SELECT DISTINCT ON (foo, quux) foo, quux FROM bar WHERE ( ( a = 1 ) )/, + ] + }, + + # subselects - equal + { + equal => 1, + statements => [ + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1)) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1)) AS foo WHERE (a = 1)/, + ] + }, + { + equal => 1, + statements => [ + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND c = 1) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND (c = 1)) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND c = 1) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND (c = 1)) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE ((b = 1) AND (c = 1))) AS foo WHERE a = 1/, + + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND c = 1) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND (c = 1)) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND c = 1) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND (c = 1)) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE ((b = 1) AND (c = 1))) AS foo WHERE (a = 1)/, + ] + }, + + # subselects - different + { + equal => 0, + statements => [ + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1) AS foo WHERE a = 2/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1) AS foo WHERE (a = 3)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1)) AS foo WHERE a = 4/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1)) AS foo WHERE (a = 5)/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 2) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 3) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 4)) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 5)) AS foo WHERE (a = 1)/, + ] + }, + { + equal => 0, + statements => [ + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND c = 1) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND c = 2) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND (c = 3)) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND (c = 4)) AS foo WHERE a = 1/, + q/SELECT * FROM (SELECT * FROM bar WHERE ((b = 1) AND (c = 5))) AS foo WHERE a = 1/, + + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND c = 6) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND c = 7) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND (c = 8)) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND (c = 9)) AS foo WHERE (a = 1)/, + q/SELECT * FROM (SELECT * FROM bar WHERE ((b = 1) AND (c = 10))) AS foo WHERE (a = 1)/, + + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND c = 1) AS foo WHERE a = 2/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND c = 2) AS foo WHERE a = 2/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND (c = 3)) AS foo WHERE a = 2/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND (c = 4)) AS foo WHERE a = 2/, + q/SELECT * FROM (SELECT * FROM bar WHERE ((b = 1) AND (c = 5))) AS foo WHERE a = 2/, + + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND c = 6) AS foo WHERE (a = 2)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND c = 7) AS foo WHERE (a = 2)/, + q/SELECT * FROM (SELECT * FROM bar WHERE b = 1 AND (c = 8)) AS foo WHERE (a = 2)/, + q/SELECT * FROM (SELECT * FROM bar WHERE (b = 1) AND (c = 9)) AS foo WHERE (a = 2)/, + q/SELECT * FROM (SELECT * FROM bar WHERE ((b = 1) AND (c = 10))) AS foo WHERE (a = 2)/, + ] + }, +); + my @bind_tests = ( # scalar - equal { @@ -200,14 +644,40 @@ my @bind_tests = ( }, ); - -plan tests => 1 + sum - map { $_ * ($_ - 1) / 2 } - map { scalar @{$_->{bindvals}} } - @bind_tests; +plan tests => 1 + + sum( + map { $_ * ($_ - 1) / 2 } + map { scalar @{$_->{statements}} } + @sql_tests + ) + + sum( + map { $_ * ($_ - 1) / 2 } + map { scalar @{$_->{bindvals}} } + @bind_tests + ); use_ok('SQL::Abstract::Test', import => [qw(eq_sql eq_bind is_same_sql_bind)]); +for my $test (@sql_tests) { + my $statements = $test->{statements}; + while (@$statements) { + my $sql1 = shift @$statements; + foreach my $sql2 (@$statements) { + my $equal = eq_sql($sql1, $sql2); + if ($test->{equal}) { + ok($equal, "equal SQL expressions considered equal"); + } else { + ok(!$equal, "different SQL expressions considered not equal"); + } + + if ($equal ^ $test->{equal}) { + diag("sql1: $sql1"); + diag("sql2: $sql2"); + } + } + } +} + for my $test (@bind_tests) { my $bindvals = $test->{bindvals}; while (@$bindvals) {