#!/usr/bin/env perl
# Test the Parse::YAPP parser used by the Lister commands
use strict;
use warnings;
use File::Basename;
use lib File::Basename::dirname(__FILE__)."/../../../lib";
use lib File::Basename::dirname(__FILE__)."/../..";
use URT;
use Test::More tests => 710;
class URT::RelatedItem {
id_by => 'ritem_id',
has => [
ritem_property => { is => 'String' },
ritem_number => { is => 'Number' },
],
};
class URT::Item {
id_by => [qw/name group/],
has => [
name => { is => "String" },
parent => { is => "URT::Item", is_optional => 1, id_by => ['parent_name','parent_group'] },
foo => { is => "String", is_optional => 1 },
fh => { is => "IO::Handle", is_optional => 1 },
score => { is => 'Integer' },
ritem => { is => 'URT::RelatedItem', id_by => 'ritem_id' },
desc => { is => 'String' },
]
};
foreach my $test (
{ string => 'name = bob',
values => { name => 'bob' },
operators => { name => '=' },
},
{ string => 'name=bob',
values => { name => 'bob' },
operators => { name => '=' },
},
{ string => 'name=>bob',
values => { name => 'bob' },
operators => { name => '=' },
},
{ string => 'name=a-longer-string',
values => { name => 'a-longer-string' },
operators => { name => '=' },
},
{ string => 'name=2012-jan-12',
values => { name => '2012-jan-12' },
operators => { name => '=' },
#stop => 1,
},
{ string => 'name=some.thing',
values => { name => 'some.thing' },
operators => { name => '='},
},
{ string => 'name=/some/file.path.ext',
values => { name => '/some/file.path.ext' },
operators => { name => '='},
},
{ string => 'name=Some::Class::Name',
values => { name => 'Some::Class::Name' },
operators => { name => '='},
},
{ string => 'name:Some::Class/Other::Class/Third::Class,score =2',
values => { name => ['Other::Class','Some::Class','Third::Class'], score => 2 },
operators => { name => 'in', score => '='},
},
{ string => 'name in [Some::Class, Other::Class, Third::Class] and score = 2',
values => { name => ['Other::Class','Some::Class','Third::Class'], score => 2 },
operators => { name => 'in', score => '='},
},
{ string => 'name=fred and score>2',
values => { name => 'fred', score => 2 },
operators => { name => '=', score => '>'},
},
{ string => 'name=",",score=2',
values => { name => ',', score => 2 },
operators => { name => '=', score => '=' },
},
{ string => 'name=and and score=2' ,
values => { name => 'and', score => 2 },
operators => { name => '=', score => '=' },
},
{ string => 'name in [bob,fred] and score<-2',
values => { name => ['bob','fred'], score => -2 },
operators => { name => 'in', score => '<' }
},
{ string => 'score = -12.2' ,
values => { score => -12.2 },
operators => { score => '=' },
},
{ string => 'score = .2' ,
values => { score => .2 },
operators => { score => '=' },
},
{ string => 'score = -.2' ,
values => { score => -0.2 },
operators => { score => '=' },
},
{ string => 'name=fred and score>2,foo=bar',
values => { name => 'fred', score => 2, foo => 'bar' },
operators => { name => '=', score => '>', foo => '='}
},
{ string => 'name=fred and score>=2',
operators => { name => '=', score => '>=' },
values => { name => 'fred', score => 2},
},
{ string => 'name=fred and score<=2',
operators => { name => '=', score => '<=' },
values => { name => 'fred', score => 2},
},
{ string => 'score!:-100--10.2',
values => { score => [-100, -10.2] },
operators => { score => 'not between' },
#stop => 1,
},
{ string => 'name~%yoyo,score:10-100',
values => { name => '%yoyo', score => [10,100] },
operators => { name => 'like', score => 'between' }
},
{ string => 'name like yoyo',
values => { name => '%yoyo%' },
operators => { name => 'like' }
},
{ string => 'name like something-with-dashes1795%',
values => { name => 'something-with-dashes1795%' },
operators => { name => 'like' },
},
{ string => 'name like H_%-MPaS3387-1795-lib2',
values => { name => 'H_%-MPaS3387-1795-lib2' },
operators => { name => 'like' },
},
{ string => 'name like %some/file/path-name.ext',
values => { name => '%some/file/path-name.ext' },
operators => { name => 'like' },
},
{ string => 'name like 1234% and desc not like %bar%',
values => { name => '1234%', desc => '%bar%' },
operators => { name => 'like', desc => 'not like' },
},
{ string => 'foo:one/two/three',
values => { foo => ['one','three','two'] }, # They get sorted internally
operators => { foo => 'in' },
},
{ string => 'foo!:one/two/three',
values => { foo => ['one','three','two'] }, # They get sorted internally
operators => { foo => 'not in' },
},
{ string => 'name=/a/path/name',
values => { name => '/a/path/name' },
operators => { name => '=' },
},
{ string => 'name:a/path/name',
values => { name => ['a','name','path'] },
operators => { name => 'in' },
},
{ string => 'name in ["/a/path/name","/other/path/","relative/path/name"]',
values => { name => ['/a/path/name','/other/path/','relative/path/name'] },
operators => {name => 'in' },
},
{ string => 'score in [1,2,3]',
values => { score => [1,2,3] },
operators => { score => 'in' },
},
{ string => 'score not in [1,2,3]',
values => { score => [1,2,3] },
operators => { score => 'not in' },
},
{ string => 'foo:one/two/three,score:10-100', # These both use :
values => { foo => ['one','three','two'], score => [10,100] },
operators => { foo => 'in', score => 'between' },
},
{ string => 'foo!:one/two/three,score:10-100', # These both use :
values => { foo => ['one','three','two'], score => [10,100] },
operators => { foo => 'not in', score => 'between' },
},
{ string => q(name="bob is cool",foo:'one "two"'/three),
values => { name => 'bob is cool', foo => ['one "two"','three'] },
operators => { name => '=', foo => 'in' },
},
{ string => 'name not like %joe',
values => { name => '%joe' },
operators => { name => 'not like' },
},
{ string => 'name ! like %joe',
values => { name => '%joe' },
operators => { name => 'not like' },
},
{ string => 'name !~%joe',
values => { name => '%joe' },
operators => { name => 'not like' },
},
{ string => 'name not like %joe and score!:10-100 and foo!:one/two/three',
values => { name => '%joe', score => [10,100], foo => ['one', 'three', 'two'] },
operators => { name => 'not like', score => 'not between', foo => 'not in' }
},
{ string => 'name=foo and ritem.ritem_property=bar',
values => { name => 'foo', 'ritem.ritem_property' => 'bar' },
operators => { name => '=', 'ritem.ritem_property' => '=' },
},
{ string => 'name=foo,ritem.ritem_property=bar,ritem.ritem_number=.2',
values => { name => 'foo', 'ritem.ritem_property' => 'bar','ritem.ritem_number' => 0.2 },
operators => { name => '=', 'ritem.ritem_property' => '=', 'ritem.ritem_number' => '=' },
},
{ string => 'name=foo and foo=bar and score=2',
values => { name => 'foo', foo => 'bar', score => 2 },
operators => { name => '=', foo => '=', score => '=' },
},
{ string => 'name=foo and ( foo=bar and score=2 )',
values => { name => 'foo', foo => 'bar', score => 2 },
operators => { name => '=', foo => '=', score => '=' },
},
{ string => 'name=foo limit 10',
values => { name => 'foo' },
operators => {name => '='},
limit => 10,
},
{ string => 'name=foo offset 10',
values => { name => 'foo' },
operators => {name => '='},
offset => 10,
},
{ string => 'name=foo limit 10 offset 20',
values => { name => 'foo' },
operators => {name => '='},
limit => 10,
offset => 20,
},
{ string => 'name=foo and score=2 limit 10 offset 20',
values => { name => 'foo', score => 2 },
operators => {name => '=', score => '='},
limit => 10,
offset => 20,
},
{ string => 'name=foo order by score' ,
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['score'],
},
{ string => 'name=foo order by score asc' ,
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['score'],
},
{ string => 'name=foo order by -score' ,
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score'],
},
{ string => 'name=foo order by score desc' ,
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score'],
},
{ string => 'name=foo order by score,foo',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['score','foo'],
},
{ string => 'name=foo order by score asc,foo',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['score','foo'],
},
{ string => 'name=foo order by score asc,foo asc',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['score','foo'],
},
{ string => 'name=foo order by score,-foo',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['score','-foo'],
},
{ string => 'name=foo order by score,foo desc',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['score','-foo'],
},
{ string => 'name=foo order by -score,foo',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','foo'],
},
{ string => 'name=foo order by score desc,foo',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','foo'],
},
{ string => 'name=foo order by score desc,foo asc',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','foo'],
},
{ string => 'name=foo order by -score,-foo',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','-foo'],
},
{ string => 'name=foo order by score desc,foo desc',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','-foo'],
},
{ string => 'name=foo order by -score,-foo group by ritem_id',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','-foo'],
group_by => ['ritem_id'],
},
{ string => 'name=foo order by score desc,foo desc group by ritem_id',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','-foo'],
group_by => ['ritem_id'],
},
{ string => 'name=foo order by -score,-foo group by ritem_id, parent_name',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','-foo'],
group_by => ['ritem_id','parent_name'],
},
{ string => 'name=foo order by -score,-foo group by ritem_id, parent_name limit 10 offset 20',
values => { name => 'foo' },
operators => { name => '=' },
order_by => ['-score','-foo'],
group_by => ['ritem_id','parent_name'],
limit => 10,
offset => 20,
},
{ string => '',
values => {},
operators => {},
},
{ string => 'order by score',
values => {},
operators => {},
order_by => ['score'],
},
{ string => 'name = a string and score=2',
values => { name => 'a string', score => 2},
operators => { name => '=', score => '=' },
},
{ string => 'name=a string with some more words and score = 2',
values => { name => 'a string with some more words', score => 2},
operators => { name => '=', score => '=' },
},
{ string => 'name=a string with spaces in between the words and score =2',
values => { name => 'a string with spaces in between the words', score => 2 },
operators => { name => '=', score => '=' },
},
{ string => 'name=a string with multiple spaces and score = 2',
values => { name => 'a string with multiple spaces', score => 2},
operators => { name => '=', score => '=' },
},
{ string => 'name true',
operators => { name => 'true' },
values => { name => 1 },
},
{ string => 'name false',
operators => { name => 'false' },
values => { name => 1 },
},
{ string => 'name true and score=2',
operators => { name => 'true', score => '=' },
values => { name => 1, score => 2 },
},
{ string => 'name is null',
operators => { name => '=' },
values => { name => undef },
},
{ string => 'name is not null',
operators => { name => '!=' },
values => { name => undef },
},
{ string => 'name is undef',
operators => { name => '=' },
values => { name => undef },
},
{ string => 'name is not undef',
operators => { name => '!=' },
values => { name => undef },
},
{ string => 'name not is undef',
operators => { name => '!=' },
values => { name => undef },
},
{ string => 'name not is null',
operators => { name => '!=' },
values => { name => undef },
},
{ string => 'name is not undef and score=2',
operators => { name => '!=', score => '=' },
values => { name => undef, score => 2 },
},
{ string => 'name=this that + the other thing',
operators => { name => '=' },
values => { name => 'this that + the other thing' },
},
) {
my $string = $test->{'string'};
my $values = $test->{'values'};
my $value_count = scalar(values %$values);
my @properties = keys %$values;
my $operators = $test->{'operators'};
my $r = UR::BoolExpr->resolve_for_string(
'URT::Item',
$test->{'string'});
ok($r, "Created rule from string \"$string\"");
my @got_values = $r->values();
is(scalar(@got_values), $value_count, 'Rule has the right number of values');
foreach my $property (@properties) {
is_deeply($r->value_for($property), $values->{$property}, "Value for $property is correct");
is($r->operator_for($property), $operators->{$property}, "Operator for $property is correct");
}
foreach my $meta ( 'order_by', 'group_by', 'limit', 'offset' ) {
if ($test->{$meta}) {
my $got = $r->template->$meta;
is_deeply($got, $test->{$meta}, "$meta is correct");
}
}
exit if ($test->{'stop'});
# print Data::Dumper::Dumper($r);
}
#exit;
# or-type rules need to be checked differently
foreach my $test (
{ string => 'name=bob or foo=bar',
rules => [
{ values => { name => 'bob' },
operators => { name => '=' },
},
{ values => { foo => 'bar' },
operators => { foo => '=' },
}
],
},
{ string => 'name=bob and score=2 or name =fred and foo=bar',
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'fred', foo => 'bar' },
operators => { name => '=', foo => '=' },
}
],
},
{ string => 'name=bob or name=foo or foo=bar',
rules => [
{ values => { name => 'bob' },
operators => { name => '=' },
},
{ values => { name => 'foo' },
operators => { name => '=' },
},
{ values => { foo => 'bar' },
operators => { foo => '=' },
},
],
},
{ string => 'name=bob and (score=2 or foo=bar)',
rules => [
{ values => { name => 'bob', score => 2, },
operators => { name => '=', score => '=' },
},
{ values => { name => 'bob', foo => 'bar' },
operators => { name => '=', foo => '=' },
},
],
},
{ string => '(name=bob or name=joe) and (score = 2 or score = 4)',
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'bob', score => 4 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'joe', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'joe', score => 4 },
operators => { name => '=', score => '=' },
},
],
},
{ string => 'name = bob and (score=2 or foo=bar and (name in ["bob","fred","joe"] and score > -10.16))',
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => ['bob','fred','joe'], foo => 'bar', score => -10.16 },
operators => { name => 'in', foo => '=', score => '>' },
# calling values() will return 4 things (since name is in there twice), but value_for('name') returns the list
override_value_count => 4,
},
],
},
{ string => q(name=bob and (score = 2 or (foo:"bar "/baz/' quux "quux" ' and (score!:-100.321--.123 or score<4321)))),
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => [-100.321, -0.123]},
operators => { name => '=', foo => 'in', score => 'not between' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => 4321 },
operators => { name => '=', foo => 'in', score => '<' },
},
],
},
{ string => 'name = bob and (score=2 or foo=bar and (name in ["bob","fred","joe"] and score > -10.16))',
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => ['bob','fred','joe'], foo => 'bar', score => -10.16 },
operators => { name => 'in', foo => '=', score => '>' },
# calling values() will return 4 things (since name is in there twice), but value_for('name') returns the list
override_value_count => 4,
},
],
},
{ string => q(name=bob and (score = 2 or (foo:"bar "/baz/' quux "quux" ' and (score!:-100.321--.123 or score<4321)))),
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => [-100.321, -0.123]},
operators => { name => '=', foo => 'in', score => 'not between' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => 4321 },
operators => { name => '=', foo => 'in', score => '<' },
},
],
},
{ string => 'name = bob and (score=2 or foo=bar and (name in ["bob","fred","joe"] and score > -10.16))',
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => ['bob','fred','joe'], foo => 'bar', score => -10.16 },
operators => { name => 'in', foo => '=', score => '>' },
# calling values() will return 4 things (since name is in there twice), but value_for('name') returns the list
override_value_count => 4,
},
],
},
{ string => q(name=bob and (score = 2 or (foo:"bar "/baz/' quux "quux" ' and (score!:-100.321--.123 or score<4321)))),
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => [-100.321, -0.123]},
operators => { name => '=', foo => 'in', score => 'not between' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => 4321 },
operators => { name => '=', foo => 'in', score => '<' },
},
],
},
{ string => 'name = bob and (score=2 or foo=bar and (name in ["bob","fred","joe"] and score > -10.16))',
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => ['bob','fred','joe'], foo => 'bar', score => -10.16 },
operators => { name => 'in', foo => '=', score => '>' },
# calling values() will return 4 things (since name is in there twice), but value_for('name') returns the list
override_value_count => 4,
},
],
},
{ string => q(name=bob and (score = 2 or (foo:"bar "/baz/' quux "quux" ' and (score!:-100.321--.123 or score<4321)))),
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => [-100.321, -0.123]},
operators => { name => '=', foo => 'in', score => 'not between' },
},
{ values => { name => 'bob', foo => [' quux "quux" ', 'bar ','baz'], score => 4321 },
operators => { name => '=', foo => 'in', score => '<' },
},
],
},
{ string => q( name=bob and (score = 2 or ( foo = bar and (parent_name=joe or ((group=cool or ritem.ritem_number<0.123) and (ritem_id = 123 or ritem.ritem_property=mojo)))))),
rules => [
{ values => { name => 'bob', score => 2 },
operators => { name => '=', score => '=' },
},
{ values => { name => 'bob', foo => 'bar', parent_name => 'joe' },
operators => { name => '=', foo => '=', parent_name => '=' },
},
{ values => { name => 'bob', foo => 'bar', group => 'cool', ritem_id => 123 },
operators => { name => '=', foo => '=', group => '=', ritem_id => '=' },
},
{ values => { name => 'bob', foo => 'bar', group => 'cool', 'ritem.ritem_property' => 'mojo' },
operators => { name => '=', foo => '=', group => '=', 'ritem.ritem_property' => '=' },
},
{ values => { name => 'bob', foo => 'bar', 'ritem.ritem_number' => 0.123, ritem_id => 123 },
operators => { name => '=', foo => '=', 'ritem.ritem_number' => '<', ritem_id => '=' },
},
{ values => { name => 'bob', foo => 'bar', 'ritem.ritem_number' => 0.123, 'ritem.ritem_property' => 'mojo' },
operators => { name => '=', foo => '=', 'ritem.ritem_number' => '<', 'ritem.ritem_property' => '=' },
},
],
},
) {
$DB::single=1 if ($test->{'stop'});
my $string = $test->{'string'};
my $composite_rule = UR::BoolExpr->resolve_for_string('URT::Item',$string);
ok($composite_rule, "Created rule from string \"$string\"");
isa_ok($composite_rule->template, 'UR::BoolExpr::Template::Or');
#print Data::Dumper::Dumper($composite_rule);
my @r = $composite_rule->underlying_rules();
is(scalar(@r), scalar(@{$test->{'rules'}}), 'Underlying rules count is correct');
for (my $i = 0; $i< @{ $test->{'rules'}}; $i++) {
my $r = $r[$i];
my $test_rule = $test->{'rules'}->[$i];
my $values = $test_rule->{'values'};
my $value_count = $test_rule->{'override_value_count'} || scalar(values %$values);
my @properties = keys %$values;
my $operators = $test_rule->{'operators'};
my @got_values = $r->values();
is(scalar(@got_values), $value_count, "Composite rule $i has the right number of values");
foreach my $property (@properties) {
is_deeply($r->value_for($property), $values->{$property}, "Value for $property is correct");
is($r->operator_for($property), $operators->{$property}, "Operator for $property is correct");
}
}
exit if ($test->{'stop'});
}
foreach my $test (
{ string => 'name in bob/fred and score<-2',
exception => qr{Syntax error near token WORD 'bob/fred'.*Expected one of: LEFT_BRACKET}s,
},
{ string => 'name:[bob,fred] and score<-2',
exception => qr{Syntax error near token LEFT_BRACKET '\['},
},
{ string => 'name:/a/path/name',
exception => qr{Syntax error near token IN_DIVIDER '/'},
},
{ string => 'score=[1,2,3]',
exception => qr{Syntax error near token LEFT_BRACKET '\['},
},
{ string => 'score!=[1,2,3]',
exception => qr{Syntax error near token LEFT_BRACKET '\['},
},
{ string => 'name=foo order by -score desc',
exception => qr{Syntax error near token DESC_WORD 'desc'},
},
{ string => 'name=foo order by -score asc',
exception => qr{Syntax error near token ASC_WORD 'asc'},
},
{ string => 'name=foo order by score desc asc',
exception => qr{Syntax error near token ASC_WORD 'asc'},
},
) {
my $string = $test->{'string'};
my $exception_re = $test->{'exception'};
my $r;
eval {
$r = UR::BoolExpr->resolve_for_string(
'URT::Item',
$test->{'string'});
};
ok(!$r, "Correctly did not create rule from string \"$string\"");
like($@, $exception_re, 'exception looks right');
}
1;