<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package SQL::Abstract::Test; # see doc at end of file

use strict;
use warnings;
use base qw(Test::Builder::Module Exporter);
use Test::Builder;
use Test::Deep ();
use SQL::Abstract::Tree;

our @EXPORT_OK = qw(
  is_same_sql_bind is_same_sql is_same_bind
  eq_sql_bind eq_sql eq_bind dumper diag_where
  $case_sensitive $sql_differ
);

my $sqlat = SQL::Abstract::Tree-&gt;new;

our $case_sensitive = 0;
our $parenthesis_significant = 0;
our $order_by_asc_significant = 0;

our $sql_differ; # keeps track of differing portion between SQLs
our $tb = __PACKAGE__-&gt;builder;

sub _unpack_arrayrefref {

  my @args;
  for (1,2) {
    my $chunk = shift @_;

    if ( ref $chunk eq 'REF' and ref $$chunk eq 'ARRAY' ) {
      my ($sql, @bind) = @$$chunk;
      push @args, ($sql, \@bind);
    }
    else {
      push @args, $chunk, shift @_;
    }

  }

  # maybe $msg and ... stuff
  push @args, @_;

  @args;
}

sub is_same_sql_bind {
  my ($sql1, $bind_ref1, $sql2, $bind_ref2, $msg) = &amp;_unpack_arrayrefref;

  # compare
  my $same_sql  = eq_sql($sql1, $sql2);
  my $same_bind = eq_bind($bind_ref1, $bind_ref2);

  # call Test::Builder::ok
  my $ret = $tb-&gt;ok($same_sql &amp;&amp; $same_bind, $msg);

  # add debugging info
  if (!$same_sql) {
    _sql_differ_diag($sql1, $sql2);
  }
  if (!$same_bind) {
    _bind_differ_diag($bind_ref1, $bind_ref2);
  }

  # pass ok() result further
  return $ret;
}

sub is_same_sql {
  my ($sql1, $sql2, $msg) = @_;

  # compare
  my $same_sql = eq_sql($sql1, $sql2);

  # call Test::Builder::ok
  my $ret = $tb-&gt;ok($same_sql, $msg);

  # add debugging info
  if (!$same_sql) {
    _sql_differ_diag($sql1, $sql2);
  }

  # pass ok() result further
  return $ret;
}

sub is_same_bind {
  my ($bind_ref1, $bind_ref2, $msg) = @_;

  # compare
  my $same_bind = eq_bind($bind_ref1, $bind_ref2);

  # call Test::Builder::ok
  my $ret = $tb-&gt;ok($same_bind, $msg);

  # add debugging info
  if (!$same_bind) {
    _bind_differ_diag($bind_ref1, $bind_ref2);
  }

  # pass ok() result further
  return $ret;
}

sub dumper {
  # FIXME
  # if we save the instance, we will end up with $VARx references
  # no time to figure out how to avoid this (Deepcopy is *not* an option)
  require Data::Dumper;
  Data::Dumper-&gt;new([])-&gt;Terse(1)-&gt;Indent(1)-&gt;Useqq(1)-&gt;Deparse(1)-&gt;Quotekeys(0)-&gt;Sortkeys(1)-&gt;Maxdepth(0)
    -&gt;Values([@_])-&gt;Dump;
}

sub diag_where{
  $tb-&gt;diag( "Search term:\n" . &amp;dumper );
}

sub _sql_differ_diag {
  my $sql1 = shift || '';
  my $sql2 = shift || '';

  $tb-&gt;${\( $tb-&gt;in_todo ? 'note' : 'diag')} (
       "SQL expressions differ\n"
      ." got: $sql1\n"
      ."want: $sql2\n"
      ."\nmismatch around\n$sql_differ\n"
  );
}

sub _bind_differ_diag {
  my ($bind_ref1, $bind_ref2) = @_;

  $tb-&gt;${\( $tb-&gt;in_todo ? 'note' : 'diag')} (
    "BIND values differ " . dumper({ got =&gt; $bind_ref1, want =&gt; $bind_ref2 })
  );
}

sub eq_sql_bind {
  my ($sql1, $bind_ref1, $sql2, $bind_ref2) = &amp;_unpack_arrayrefref;

  return eq_sql($sql1, $sql2) &amp;&amp; eq_bind($bind_ref1, $bind_ref2);
}


sub eq_bind { goto &amp;Test::Deep::eq_deeply };

sub eq_sql {
  my ($sql1, $sql2) = @_;

  # parse
  my $tree1 = $sqlat-&gt;parse($sql1);
  my $tree2 = $sqlat-&gt;parse($sql2);

  undef $sql_differ;
  return 1 if _eq_sql($tree1, $tree2);
}

sub _eq_sql {
  my ($left, $right) = @_;

  # one is defined the other not
  if ( (defined $left) xor (defined $right) ) {
    $sql_differ = sprintf ("[%s] != [%s]\n", map { defined $_ ? $sqlat-&gt;unparse ($_) : 'N/A' } ($left, $right) );
    return 0;
  }

  # one is undefined, then so is the other
  elsif (not defined $left) {
    return 1;
  }

  # both are empty
  elsif (@$left == 0 and @$right == 0) {
    return 1;
  }

  # one is empty
  if (@$left == 0 or @$right == 0) {
    $sql_differ = sprintf ("left: %s\nright: %s\n", map { @$_ ? $sqlat-&gt;unparse ($_) : 'N/A'} ($left, $right) );
    return 0;
  }

  # one is a list, the other is an op with a list
  elsif (ref $left-&gt;[0] xor ref $right-&gt;[0]) {
    $sql_differ = sprintf ("[%s] != [%s]\nleft: %s\nright: %s\n", map
      { ref $_ ? $sqlat-&gt;unparse ($_) : $_ }
      ($left-&gt;[0], $right-&gt;[0], $left, $right)
    );
    return 0;
  }

  # both are lists
  elsif (ref $left-&gt;[0]) {
    for (my $i = 0; $i &lt;= $#$left or $i &lt;= $#$right; $i++ ) {
      if (not _eq_sql ($left-&gt;[$i], $right-&gt;[$i]) ) {
        if (! $sql_differ or $sql_differ !~ /left\:\s .+ right:\s/xs) {
          $sql_differ ||= '';
          $sql_differ .= "\n" unless $sql_differ =~ /\n\z/;
          $sql_differ .= sprintf ("left: %s\nright: %s\n", map { $sqlat-&gt;unparse ($_) } ($left, $right) );
        }
        return 0;
      }
    }
    return 1;
  }

  # both are ops
  else {

    # unroll parenthesis if possible/allowed
    unless ( $parenthesis_significant ) {
      $sqlat-&gt;_parenthesis_unroll($_) for $left, $right;
    }

    # unroll ASC order by's
    unless ($order_by_asc_significant) {
      $sqlat-&gt;_strip_asc_from_order_by($_) for $left, $right;
    }

    if ( $left-&gt;[0] ne $right-&gt;[0] ) {
      $sql_differ = sprintf "OP [$left-&gt;[0]] != [$right-&gt;[0]] in\nleft: %s\nright: %s\n",
        $sqlat-&gt;unparse($left),
        $sqlat-&gt;unparse($right)
      ;
      return 0;
    }

    # literals have a different arg-sig
    elsif ($left-&gt;[0] eq '-LITERAL') {
      (my $l = " $left-&gt;[1][0] " ) =~ s/\s+/ /g;
      (my $r = " $right-&gt;[1][0] ") =~ s/\s+/ /g;
      my $eq = $case_sensitive ? $l eq $r : uc($l) eq uc($r);
      $sql_differ = "[$l] != [$r]\n" if not $eq;
      return $eq;
    }

    # if operators are identical, compare operands
    else {
      my $eq = _eq_sql($left-&gt;[1], $right-&gt;[1]);
      $sql_differ ||= sprintf ("left: %s\nright: %s\n", map { $sqlat-&gt;unparse ($_) } ($left, $right) ) if not $eq;
      return $eq;
    }
  }
}

sub parse { $sqlat-&gt;parse(@_) }
1;


__END__

=head1 NAME

SQL::Abstract::Test - Helper function for testing SQL::Abstract

=head1 SYNOPSIS

  use SQL::Abstract;
  use Test::More;
  use SQL::Abstract::Test import =&gt; [qw/
    is_same_sql_bind is_same_sql is_same_bind
    eq_sql_bind eq_sql eq_bind
  /];

  my ($sql, @bind) = SQL::Abstract-&gt;new-&gt;select(%args);

  is_same_sql_bind($given_sql,    \@given_bind,
                   $expected_sql, \@expected_bind, $test_msg);

  is_same_sql($given_sql, $expected_sql, $test_msg);
  is_same_bind(\@given_bind, \@expected_bind, $test_msg);

  my $is_same = eq_sql_bind($given_sql,    \@given_bind,
                            $expected_sql, \@expected_bind);

  my $sql_same = eq_sql($given_sql, $expected_sql);
  my $bind_same = eq_bind(\@given_bind, \@expected_bind);

=head1 DESCRIPTION

This module is only intended for authors of tests on
L&lt;SQL::Abstract|SQL::Abstract&gt; and related modules;
it exports functions for comparing two SQL statements
and their bound values.

The SQL comparison is performed on I&lt;abstract syntax&gt;,
ignoring differences in spaces or in levels of parentheses.
Therefore the tests will pass as long as the semantics
is preserved, even if the surface syntax has changed.

B&lt;Disclaimer&gt; : the semantic equivalence handling is pretty limited.
A lot of effort goes into distinguishing significant from
non-significant parenthesis, including AND/OR operator associativity.
Currently this module does not support commutativity and more
intelligent transformations like L&lt;De Morgan's laws
|http://en.wikipedia.org/wiki/De_Morgan's_laws&gt;, etc.

For a good overview of what this test framework is currently capable of refer
to C&lt;t/10test.t&gt;

=head1 FUNCTIONS

=head2 is_same_sql_bind

  is_same_sql_bind(
    $given_sql, \@given_bind,
    $expected_sql, \@expected_bind,
    $test_msg
  );

  is_same_sql_bind(
    \[$given_sql, @given_bind],
    \[$expected_sql, @expected_bind],
    $test_msg
  );

  is_same_sql_bind(
    $dbic_rs-&gt;as_query
    $expected_sql, \@expected_bind,
    $test_msg
  );

Compares given and expected pairs of C&lt;($sql, \@bind)&gt; by unpacking C&lt;@_&gt;
as shown in the examples above and passing the arguments to L&lt;/eq_sql&gt; and
L&lt;/eq_bind&gt;. Calls L&lt;Test::Builder/ok&gt; with the combined result, with
C&lt;$test_msg&gt; as message.
If the test fails, a detailed diagnostic is printed.

=head2 is_same_sql

  is_same_sql(
    $given_sql,
    $expected_sql,
    $test_msg
  );

Compares given and expected SQL statements via L&lt;/eq_sql&gt;, and calls
L&lt;Test::Builder/ok&gt; on the result, with C&lt;$test_msg&gt; as message.
If the test fails, a detailed diagnostic is printed.

=head2 is_same_bind

  is_same_bind(
    \@given_bind,
    \@expected_bind,
    $test_msg
  );

Compares given and expected bind values via L&lt;/eq_bind&gt;, and calls
L&lt;Test::Builder/ok&gt; on the result, with C&lt;$test_msg&gt; as message.
If the test fails, a detailed diagnostic is printed.

=head2 eq_sql_bind

  my $is_same = eq_sql_bind(
    $given_sql, \@given_bind,
    $expected_sql, \@expected_bind,
  );

  my $is_same = eq_sql_bind(
    \[$given_sql, @given_bind],
    \[$expected_sql, @expected_bind],
  );

  my $is_same = eq_sql_bind(
    $dbic_rs-&gt;as_query
    $expected_sql, \@expected_bind,
  );

Unpacks C&lt;@_&gt; depending on the given arguments and calls L&lt;/eq_sql&gt; and
L&lt;/eq_bind&gt;, returning their combined result.

=head2 eq_sql

  my $is_same = eq_sql($given_sql, $expected_sql);

Compares the abstract syntax of two SQL statements. Similar to L&lt;/is_same_sql&gt;,
but it just returns a boolean value and does not print diagnostics or talk to
L&lt;Test::Builder&gt;. If the result is false, the global variable L&lt;/$sql_differ&gt;
will contain the SQL portion where a difference was encountered; this is useful
for printing diagnostics.

=head2 eq_bind

  my $is_same = eq_sql(\@given_bind, \@expected_bind);

Compares two lists of bind values, taking into account the fact that some of
the values may be arrayrefs (see L&lt;SQL::Abstract/bindtype&gt;). Similar to
L&lt;/is_same_bind&gt;, but it just returns a boolean value and does not print
diagnostics or talk to L&lt;Test::Builder&gt;.

=head1 GLOBAL VARIABLES

=head2 $case_sensitive

If true, SQL comparisons will be case-sensitive. Default is false;

=head2 $parenthesis_significant

If true, SQL comparison will preserve and report difference in nested
parenthesis. Useful while testing C&lt;IN (( x ))&gt; vs C&lt;IN ( x )&gt;.
Defaults to false;

=head2 $order_by_asc_significant

If true SQL comparison will consider C&lt;ORDER BY foo ASC&gt; and
C&lt;ORDER BY foo&gt; to be different. Default is false;

=head2 $sql_differ

When L&lt;/eq_sql&gt; returns false, the global variable
C&lt;$sql_differ&gt; contains the SQL portion
where a difference was encountered.

=head1 SEE ALSO

L&lt;SQL::Abstract&gt;, L&lt;Test::More&gt;, L&lt;Test::Builder&gt;.

=head1 AUTHORS

Laurent Dami &lt;laurent.dami AT etat  geneve  ch&gt;

Norbert Buchmuller &lt;norbi@nix.hu&gt;

Peter Rabbitson &lt;ribasushi@cpan.org&gt;

=head1 COPYRIGHT AND LICENSE

Copyright 2008 by Laurent Dami.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
</pre></body></html>