Do not treat anywhere/nowhere as single operand boolean groups

Change-Id: I2fb964af57436aad94981b3c5af39f3bb6b19bcf
diff --git a/lib/Krawfish/Koral/Corpus.pm b/lib/Krawfish/Koral/Corpus.pm
index e34234a..47aefea 100644
--- a/lib/Krawfish/Koral/Corpus.pm
+++ b/lib/Krawfish/Koral/Corpus.pm
@@ -104,6 +104,9 @@
 
   print_log('kq_corpus', 'Do an "and" on anywhere') if DEBUG;
 
+  # Do not wrap already satisfied queries
+  return $self if $self->is_anywhere || $self->is_nowhere;
+
   return $self->builder->bool_and(
     $self->builder->anywhere,
     $self
diff --git a/lib/Krawfish/Koral/Corpus/FieldGroup.pm b/lib/Krawfish/Koral/Corpus/FieldGroup.pm
index ef5add8..89e0415 100644
--- a/lib/Krawfish/Koral/Corpus/FieldGroup.pm
+++ b/lib/Krawfish/Koral/Corpus/FieldGroup.pm
@@ -21,7 +21,9 @@
 
 # TODO:
 #   In normalization phase make
-#   X geq Y & X leq Y -> X eq Y
+#   - X >= Y & X <= Y  -> X eq Y
+#   - X > 4 & X > 3    -> X > 4
+#   - X < 4 & X < 3    -> X < 3
 
 use constant DEBUG => 0;
 
diff --git a/lib/Krawfish/Koral/Util/Boolean.pm b/lib/Krawfish/Koral/Util/Boolean.pm
index bb661ac..501a0b0 100644
--- a/lib/Krawfish/Koral/Util/Boolean.pm
+++ b/lib/Krawfish/Koral/Util/Boolean.pm
@@ -12,7 +12,7 @@
 # - Koral::Query::TermGroup
 # - Koral::Query::Or
 
-use constant DEBUG => 1;
+use constant DEBUG => 0;
 
 requires qw/bool_and_query
             bool_or_query/;
@@ -137,6 +137,10 @@
 
   $self = $self->_clean_and_flatten;
 
+  unless (Role::Tiny::does_role($self, 'Krawfish::Koral::Util::Boolean')) {
+    return $self->normalize;
+  };
+
   # Recursive normalize
   my @ops = ();
   foreach my $op (@{$self->operands}) {
@@ -154,9 +158,13 @@
   # but an andNot or a leaf after the final step
   #
   # The order is important!
-  return $self
-    ->_clean_and_flatten
-    ->_resolve_idempotence
+  $self = $self->_clean_and_flatten;
+
+  unless (Role::Tiny::does_role($self, 'Krawfish::Koral::Util::Boolean')) {
+    return $self->normalize;
+  };
+
+  return $self->_resolve_idempotence
     ->_resolve_demorgan
     ->_remove_nested_idempotence
     ->_replace_negative;
@@ -404,15 +412,13 @@
     # Check if there is only a single operand
     # (because [1] or [] was removed)
     if (scalar(@$ops) == 1) {
-      if ($op->is_nowhere) {
-        @$ops = ();
-        $self->is_nowhere(1);
-      }
-      elsif ($op->is_anywhere) {
-        @$ops = ();
-        $self->is_anywhere(1);
+
+      # Revert negativity on single operands
+      if ($self->is_negative) {
+        $op = $op->toggle_negative;
       };
-      return $self;
+
+      return $op;
     };
 
     # Remove empty elements
@@ -430,10 +436,7 @@
 
         print_log('kq_bool', 'Group can be simplified to [0]') if DEBUG;
 
-        # Matches nowhere!
-        @$ops = ();
-        $self->is_nowhere(1);
-        last;
+        return $op;
       }
 
       # A | B | [0] -> A | B
@@ -459,9 +462,7 @@
         print_log('kq_bool', 'Group can be simplified to [1]') if DEBUG;
 
         # Matches everywhere
-        @$ops = ();
-        $self->is_anywhere(1);
-        last;
+        return $op;
       }
     }
 
diff --git a/t/compile/segment/group_fields_aggregate_values.t b/t/compile/segment/group_fields_aggregate_values.t
index 39862fb..bf6b500 100644
--- a/t/compile/segment/group_fields_aggregate_values.t
+++ b/t/compile/segment/group_fields_aggregate_values.t
@@ -71,11 +71,11 @@
 
 # TODO:
 #   Simplify [1]&[1]!!!
-is($query->to_string, "gaggr(values:['age']:gFields('author':[1]&[1]))", 'string');
+is($query->to_string, "gaggr(values:['age']:gFields('author':[1]))", 'string');
 
 ok($query = $query->identify($index->dict)->optimize($index->segment), 'Optimize');
 
-is($query->to_string(1), 'gFields(#3;groupAggr([values:#1]):and([1],[1]))', 'Optimized query');
+is($query->to_string(1), 'gFields(#3;groupAggr([values:#1]):[1])', 'Optimized query');
 
 diag 'Implement Group::Aggregate!!';
 
diff --git a/t/corpus/and.t b/t/corpus/and.t
index 29a8706..3d2889d 100644
--- a/t/corpus/and.t
+++ b/t/corpus/and.t
@@ -123,32 +123,6 @@
 ok(!$plan->next, 'No more next');
 
 
-ok($query = $cb->bool_and(
-  $cb->anywhere,
-  $cb->anywhere
-), "And with everywhere");
-
-is($query->to_string, '[1]&[1]', 'Stringification');
-ok($plan = $query->normalize, 'Normalization');
-is($plan->to_string, "", 'Stringification');
-ok($plan->is_anywhere, 'Query is anywhere');
-# ok($plan = $plan->finalize, 'Planning');
-# is($plan->to_string, "[1]", 'Stringification');
-
-ok($query = $cb->bool_and(
-  $cb->nowhere,
-  $cb->nowhere
-), "And with nowhere");
-
-is($query->to_string, '[0]&[0]', 'Stringification');
-ok($plan = $query->normalize, 'Normalization');
-is($plan->to_string, "", 'Stringification');
-ok($plan->is_nowhere, 'Is nowhere');
-
-
-# TODO:
-#   Check [1]|[1]
-
 done_testing;
 __END__
 
diff --git a/t/corpus/or.t b/t/corpus/or.t
index c1e8535..3a7c9d6 100644
--- a/t/corpus/or.t
+++ b/t/corpus/or.t
@@ -61,27 +61,6 @@
 
 # matches($plan, [qw/[0] [1] [2] [3] [4]/], 'Matches');
 
-ok($query = $cb->bool_or(
-  $cb->anywhere,
-  $cb->anywhere
-), "Or with everywhere");
-
-is($query->to_string, '[1]|[1]', 'Stringification');
-ok($plan = $query->normalize, 'Normalization');
-is($plan->to_string, "", 'Stringification');
-ok($plan->is_anywhere, 'Is anywhere');
-
-
-ok($query = $cb->bool_or(
-  $cb->nowhere,
-  $cb->nowhere
-), "Or with nowhere");
-
-is($query->to_string, '[0]|[0]', 'Stringification');
-ok($plan = $query->normalize, 'Normalization');
-is($plan->to_string, "", 'Stringification');
-ok($plan->is_nowhere, 'Is nowhere');
-
 
 
 TODO: {
diff --git a/t/koral/corpus/boolean.t b/t/koral/corpus/boolean.t
index 4733de7..42889f9 100644
--- a/t/koral/corpus/boolean.t
+++ b/t/koral/corpus/boolean.t
@@ -7,20 +7,21 @@
 
 ok(my $cb = Krawfish::Koral::Corpus::Builder->new, 'Create CorpusBuilder');
 
+my $tree;
+
 # Get tree
-my $tree = $cb->bool_and(
+$tree = $cb->bool_and(
   $cb->string('age')->eq('4'),
   $cb->string('author')->eq('Peter'),
   undef,
   $cb->string('age')->eq('4')
 );
 
+
 # Remove empty elements
 ok($tree = $tree->normalize, 'Query normalization');
 is($tree->to_string, 'age=4&author=Peter', 'Resolve idempotence');
 
-
-
 # Solve grouping
 $tree = $cb->bool_and(
   $cb->string('a')->eq('1'),
@@ -180,9 +181,9 @@
 );
 
 is($tree->to_string, '[0]&x=1&z=1', 'Plain groups');
-$tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 ok($tree->is_nowhere, 'Nowhere');
-is($tree->to_string, '', 'Nowhere');
+is($tree->to_string, '[0]', 'Nowhere');
 
 
 # Check flattening with ANY
@@ -194,10 +195,10 @@
 );
 
 is($tree->to_string, '[1]|x=1|z=1', 'Plain groups');
-$tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 ok(!$tree->is_nowhere, 'No Nowhere');
 ok($tree->is_anywhere, 'Anything');
-is($tree->to_string, '', 'no string');
+is($tree->to_string, '[1]', 'no string');
 
 
 # Check flattening with ANY
@@ -209,7 +210,7 @@
 );
 
 is($tree->to_string, '[1]&x=1&z=1', 'Plain groups');
-$tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 ok(!$tree->is_nowhere, 'No Nowhere');
 ok(!$tree->is_anywhere, 'No Anything');
 is($tree->to_string, 'x=1&z=1', 'no string');
@@ -224,7 +225,7 @@
 );
 
 is($tree->to_string, 'x!=1|y!=1|z!=1', 'Plain groups');
-$tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 is($tree->to_string, '!(x=1&y=1&z=1)', 'no string');
 
 
@@ -237,7 +238,7 @@
 );
 
 is($tree->to_string, 'x!=1&y!=1&z!=1', 'Plain groups');
-$tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 is($tree->to_string, '!(x=1|y=1|z=1)', 'no string');
 
 
@@ -252,7 +253,7 @@
 );
 
 is($tree->to_string, 'a!=1|b=1|c!=1|d=1|e!=1|f=1', 'Plain groups');
-$tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 is($tree->to_string, '([1]&!(a=1&c=1&e=1))' . '|b=1|d=1|f=1', 'no string');
 
 # DeMorgan grouping with AND
@@ -266,7 +267,7 @@
 );
 
 is($tree->to_string, 'a!=1&b=1&c!=1&d=1&e!=1&f=1', 'Plain groups');
-$tree = $tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 
 # TODO: This may require a direct andNot() serialization with the all-query
 is($tree->to_string, '((b=1&d=1&f=1)&!(a=1|c=1|e=1))', 'no string');
@@ -279,7 +280,7 @@
 )->toggle_negative;
 
 is($tree->to_string, '!(a!=1)', 'Plain groups');
-$tree = $tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 is($tree->to_string, 'a=1', 'simple string');
 
 
@@ -290,7 +291,7 @@
 )->toggle_negative;
 
 is($tree->to_string, '!(a!=1&b!=1)', 'Plain groups');
-$tree = $tree->normalize;
+ok($tree = $tree->normalize, 'Normalize');
 is($tree->to_string, 'a=1|b=1', 'simple string');
 
 
@@ -325,6 +326,60 @@
 is($tree->to_string, '([1]&!(([1]&!b=1)|c=1|d=1))', 'simple string');
 
 
+# Check [1/0]&/|[1/0]
+ok($tree = $cb->bool_and(
+  $cb->anywhere,
+  $cb->anywhere
+), "And with everywhere");
+
+is($tree->to_string, '[1]&[1]', 'Stringification');
+ok($tree = $tree->normalize, 'Normalization');
+is($tree->to_string, '[1]', 'Stringification');
+ok($tree->is_anywhere, 'Query is anywhere');
+ok($tree = $tree->finalize, 'Planning');
+is($tree->to_string, "[1]", 'Stringification');
+
+ok($tree = $cb->bool_and(
+  $cb->nowhere,
+  $cb->nowhere
+), "And with nowhere");
+
+is($tree->to_string, '[0]&[0]', 'Stringification');
+ok($tree = $tree->normalize, 'Normalization');
+is($tree->to_string, '[0]', 'Stringification');
+ok($tree->is_nowhere, 'Is nowhere');
+ok($tree = $tree->finalize, 'Planning');
+is($tree->to_string, "[0]", 'Stringification');
+
+
+ok($tree = $cb->bool_or(
+  $cb->anywhere,
+  $cb->anywhere
+), "Or with everywhere");
+
+is($tree->to_string, '[1]|[1]', 'Stringification');
+ok($tree = $tree->normalize, 'Normalization');
+is($tree->to_string, '[1]', 'Stringification');
+ok($tree->is_anywhere, 'Is anywhere');
+ok($tree = $tree->finalize, 'Planning');
+is($tree->to_string, "[1]", 'Stringification');
+
+
+
+ok($tree = $cb->bool_or(
+  $cb->nowhere,
+  $cb->nowhere
+), "Or with nowhere");
+
+is($tree->to_string, '[0]|[0]', 'Stringification');
+ok($tree = $tree->normalize, 'Normalization');
+is($tree->to_string, '[0]', 'Stringification');
+ok($tree->is_nowhere, 'Is nowhere');
+ok($tree = $tree->finalize, 'Planning');
+is($tree->to_string, "[0]", 'Stringification');
+
+
+
 
 TODO: {
   local $TODO = 'Check with negativity for >=, <=, exists etc.'
@@ -383,4 +438,9 @@
 $tree->remove_empty->resolve_idempotence;
 is($tree->to_string, 'age!=4&author!=Peter', 'Resolve idempotence');
 
+
+
+
+
+
 done_testing;