--- /dev/null
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use lib 't/lib';
+use TestCommon;
+
+use File::KDBX::Util qw(query search simple_expression_query);
+use Test::Deep;
+use Test::More;
+
+my $list = [
+ {
+ id => 1,
+ name => 'Bob',
+ age => 34,
+ married => 1,
+ notes => 'Enjoys bowling on Thursdays',
+ },
+ {
+ id => 2,
+ name => 'Ken',
+ age => 17,
+ married => 0,
+ notes => 'Eats dessert first',
+ color => '',
+ },
+ {
+ id => 3,
+ name => 'Becky',
+ age => 25,
+ married => 1,
+ notes => 'Listens to rap music on repeat',
+ color => 'orange',
+ },
+ {
+ id => 4,
+ name => 'Bobby',
+ age => 5,
+ notes => 'Loves candy and running around like a crazy person',
+ color => 'blue',
+ },
+];
+
+subtest 'Declarative structure' => sub {
+ my $result = search($list, name => 'Bob');
+ cmp_deeply $result, [shallow($list->[0])], 'Find Bob'
+ or diag explain $result;
+
+ $result = search($list, name => 'Ken');
+ cmp_deeply $result, [$list->[1]], 'Find Ken'
+ or diag explain $result;
+
+ $result = search($list, age => 25);
+ cmp_deeply $result, [$list->[2]], 'Find Becky by age'
+ or diag explain $result;
+
+ $result = search($list, {name => 'Becky', age => 25});
+ cmp_deeply $result, [$list->[2]], 'Find Becky by name AND age'
+ or diag explain $result;
+
+ $result = search($list, {name => 'Becky', age => 99});
+ cmp_deeply $result, [], 'Miss Becky with wrong age'
+ or diag explain $result;
+
+ $result = search($list, [name => 'Becky', age => 17]);
+ cmp_deeply $result, [$list->[1], $list->[2]], 'Find Ken and Becky with different criteria'
+ or diag explain $result;
+
+ $result = search($list, name => 'Becky', age => 17);
+ cmp_deeply $result, [$list->[1], $list->[2]], 'Query list defaults to OR logic'
+ or diag explain $result;
+
+ $result = search($list, age => {'>=', 18});
+ cmp_deeply $result, [$list->[0], $list->[2]], 'Find adults'
+ or diag explain $result;
+
+ $result = search($list, name => {'=~', qr/^Bob/});
+ cmp_deeply $result, [$list->[0], $list->[3]], 'Find both Bobs'
+ or diag explain $result;
+
+ $result = search($list, -and => [name => 'Becky', age => 99]);
+ cmp_deeply $result, [], 'Specify AND logic explicitly'
+ or diag explain $result;
+
+ $result = search($list, {name => 'Becky', age => 99});
+ cmp_deeply $result, [], 'Specify AND logic implicitly'
+ or diag explain $result;
+
+ $result = search($list, '!' => 'married');
+ cmp_deeply $result, [$list->[1], $list->[3]], 'Find unmarried (using normal operator)'
+ or diag explain $result;
+
+ $result = search($list, -false => 'married');
+ cmp_deeply $result, [$list->[1], $list->[3]], 'Find unmarried (using special operator)'
+ or diag explain $result;
+
+ $result = search($list, -true => 'married');
+ cmp_deeply $result, [$list->[0], $list->[2]], 'Find married persons (using special operator)'
+ or diag explain $result;
+
+ $result = search($list, -not => {name => {'=~', qr/^Bob/}});
+ cmp_deeply $result, [$list->[1], $list->[2]], 'What about Bob? Inverse a complex query'
+ or diag explain $result;
+
+ $result = search($list, -nonempty => 'color');
+ cmp_deeply $result, [$list->[2], $list->[3]], 'Find the colorful'
+ or diag explain $result;
+
+ $result = search($list, color => {ne => undef});
+ cmp_deeply $result, [$list->[2], $list->[3]], 'Find the colorful (compare to undef)'
+ or diag explain $result;
+
+ $result = search($list, -empty => 'color');
+ cmp_deeply $result, [$list->[0], $list->[1]], 'Find those without color'
+ or diag explain $result;
+
+ $result = search($list, color => {eq => undef});
+ cmp_deeply $result, [$list->[0], $list->[1]], 'Find those without color (compare to undef)'
+ or diag explain $result;
+
+ $result = search($list, -defined => 'color');
+ cmp_deeply $result, [$list->[1], $list->[2], $list->[3]], 'Find defined colors'
+ or diag explain $result;
+
+ $result = search($list, -undef => 'color');
+ cmp_deeply $result, [$list->[0]], 'Find undefined colors'
+ or diag explain $result;
+
+ $result = search($list,
+ -and => [
+ name => {'=~', qr/^Bob/},
+ -and => {
+ name => {'ne', 'Bob'},
+ },
+ ],
+ -not => {'!' => 'Bobby'},
+ );
+ cmp_deeply $result, [$list->[3]], 'Complex query'
+ or diag explain $result;
+
+ my $query = query(name => 'Ken');
+ $result = search($list, $query);
+ cmp_deeply $result, [$list->[1]], 'Search using a pre-compiled query'
+ or diag explain $result;
+
+ my $custom_query = sub { shift->{name} eq 'Bobby' };
+ $result = search($list, $custom_query);
+ cmp_deeply $result, [$list->[3]], 'Search using a custom query subroutine'
+ or diag explain $result;
+};
+
+##############################################################################
+
+subtest 'Simple expressions' => sub {
+ my $simple_query = simple_expression_query('bob', qw{name notes});
+ my $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[0], $list->[3]], 'Basic one-term expression'
+ or diag explain $result;
+
+ $result = search($list, \'bob', qw{name notes});
+ cmp_deeply $result, [$list->[0], $list->[3]], 'Basic one-term expression on search'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query(' Dessert ', qw{notes});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[1]], 'Whitespace is ignored'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query('to music', qw{notes});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[2]], 'Multiple terms'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query('"to music"', qw{notes});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [], 'One quoted term'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query('candy "CRAZY PERSON" ', qw{notes});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[3]], 'Multiple terms, one quoted term'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query(" bob\tcandy\n\n", qw{name notes});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[3]], 'Multiple terms in different fields'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query('music -repeat', qw{notes});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [], 'Multiple terms, one negative term'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query('-bob', qw{name});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[1], $list->[2]], 'Negative term'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query('bob -bobby', qw{name});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[0]], 'Multiple mixed terms'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query(25, '==', qw{age});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[2]], 'Custom operator'
+ or diag explain $result;
+
+ $simple_query = simple_expression_query('-25', '==', qw{age});
+ $result = search($list, $simple_query);
+ cmp_deeply $result, [$list->[0], $list->[1], $list->[3]], 'Negative term, custom operator'
+ or diag explain $result;
+};
+
+done_testing;