The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

# Test the different ways that SQL's handling of NULL might differ
# with the way Perl and UR convert NULL to undef and the various
# numeric and string conversions when doing comparisions.  We want UR's
# object cache to return the same results that a query against the database
# would

use File::Basename;
use lib File::Basename::dirname(__FILE__)."/../../../lib";
use lib File::Basename::dirname(__FILE__)."/../..";
use URT;

use Test::More tests => 227;
use URT::DataSource::SomeSQLite;


my $dbh = URT::DataSource::SomeSQLite->get_default_handle;

ok($dbh, 'Got DB handle');

ok( $dbh->do("create table things (thing_id integer, value integer)"),
   'Created things table');

$dbh->do("insert into things (thing_id, value) values (1, NULL)");
$dbh->do("Insert into things (thing_id, value) values (2, NULL)");
ok($dbh->commit(), 'DB commit');
           
UR::Object::Type->define(
    class_name => 'URT::Thing',
    id_by => [
        thing_id => { is => 'Integer' },
    ],
    has_optional => [
        value => { is => 'Integer' },
    ],
    data_source => 'URT::DataSource::SomeSQLite',
    table_name => 'things',
);


my @result;

# For the equality operator, "value => undef" is converted to SQL as
# "value IS NULL", not "value = NULL, so it should return the items

foreach my $test (
        [ 'undef' => undef ],
        [ "''" => '' ],
) {
    # undef and the empty string both mean NULL
    my($value_as_string, $value) = @$test;

    @result = URT::Thing->get(value => $value);
    is(scalar(@result), 2, "value => $value_as_string loaded 2 items");

    @result = URT::Thing->get(value => $value);
    is(scalar(@result), 2, "value => $value_as_string returned all 2 items");

    URT::Thing->unload();  # clear object and query cache
}

# For other values using the equality operator, it should return nothing
foreach my $value ( 0, 1, -1) {
    operator_returns_object_count('', $value,0);
}


## != for non-null values should return both things
foreach my $value ( 0, 1, -1) {
    my @result = URT::Thing->get(value => { operator => '!=', value => $value});
    is(scalar(@result), 2, "value != $value (old syntax) loaded 2 items");

    @result = URT::Thing->get(value => { operator => '!=', value => $value});
    is(scalar(@result), 2, "value != $value (old syntax) returned 2 items");

    URT::Thing->unload();  # clear object and query cache

    @result = URT::Thing->get('value !=' => $value);
    is(scalar(@result), 2, "value != $value (new syntax) loaded 2 items");

    @result = URT::Thing->get('value !=' => $value);
    is(scalar(@result), 2, "value != $value (new syntax) returned 2 items");

    URT::Thing->unload();  # clear object and query cache
}

# the 'false' operator should return both things, since NULL is false
{
    my @result = URT::Thing->get(value => { operator => 'false', value => '' });
    is(scalar(@result), 2, "value is false (old syntax) loaded 2 items");

    @result = URT::Thing->get(value => { operator => 'false', value => ''});
    is(scalar(@result), 2, "value is false (old syntax) returned 2 items");

    URT::Thing->unload();  # clear object and query cache

    @result = URT::Thing->get('value false' => 1);
    is(scalar(@result), 2, "value is false (new syntax) loaded 2 items");

    @result = URT::Thing->get('value false' => 1);
    is(scalar(@result), 2, "value is false (new syntax) returned 2 items");

    URT::Thing->unload();  # clear object and query cache
}    


foreach my $operator ( qw( < <= > >= true ) ) {
    foreach my $value ( undef, 0, "", 1, -1) {

        operator_returns_object_count($operator,$value,0);

        last if ($operator eq 'true' or $operator eq 'false'); # true and false don't use the 'value' anyway
    }
}

# FIXME - uninitialized warnings here
foreach my $operator ( 'like', 'not like' ) {
    foreach my $value ( undef, '%', '%1', '%1%' ) {

        operator_returns_object_count($operator, $value, 0)
    }
}

# Supress messages about null in-clauses.
URT::DataSource::SomeSQLite->warning_messages_callback(
    sub {
        my ($self,$msg) = @_;
        if ($msg =~ m/Null in-clause passed/) {
            $_[1] = undef;
        }
    }
);

# 'in' operator
# value => [undef] does SQL to include NULL items
operator_returns_object_count('in', [undef], 2);

operator_returns_object_count('not in', [undef], 0);

foreach my $operator ( '', 'in', 'not in' ) {
    foreach my $value ( [], [1] ) {
        operator_returns_object_count($operator, $value, 0);
    }
}

# 'between' operator
foreach my $value ( [undef, undef], [1,1], [0,1], [-1,0], [-1,-1],
                    [undef, 1], [undef, 0], [undef, -1],
                    [1, undef], [0, undef], [-1, undef] )
{
    operator_returns_object_count('between', $value, 0);

}
 
sub operator_returns_object_count {
    my($operator,$value,$expected_count) = @_;

    if (ref($value) eq 'ARRAY' and !$operator) {
        $operator = 'in';
    }

    my $print_operator = $operator || '=>';

    my $print_value;
    if (! defined $value) {
        $print_value = '(undef)';
    } elsif (length($value) == 0 ) {
        $print_value = '""';
    } elsif (ref($value) eq 'ARRAY') {
        $print_value = '[' . join(",", map { defined($_) ? "'$_'" : '(undef)' } @$value) . ']';
    } else {
        $print_value = $value;
    }

    # Original non-eq-operator syntax
    @result = URT::Thing->get(value => { operator => $operator, value => $value });
    is(scalar(@result), $expected_count, "value $print_operator $print_value (old syntax) loads $expected_count item(s)");
    URT::Thing->unload();  # clear object and query cache
    URT::Thing->get(1);    # Get an object into the cache

    @result = URT::Thing->get(value => { operator => $operator, value => $value });
    is(scalar(@result), $expected_count, "value $print_operator $print_value (old syntax) returns $expected_count item(s)");
    URT::Thing->unload();


    # New syntax
    my $property_string = "value $operator";
    @result = URT::Thing->get($property_string => $value);
    is(scalar(@result), $expected_count, "value $print_operator $print_value (new syntax) loads $expected_count item(s)");

    URT::Thing->unload();  # clear object and query cache
    URT::Thing->get(1);    # Get an object into the cache

    @result = URT::Thing->get($property_string => $value);
    is(scalar(@result), $expected_count, "value $print_operator $print_value (new syntax) returns $expected_count item(s)");
    URT::Thing->unload();
}