#!perl use strict; use warnings; use Test::More; use Test::Deep qw(cmp_details deep_diag bag); use Data::Dump qw(pp); use Test::Exception; use ElasticSearch::SearchBuilder; my $a = ElasticSearch::SearchBuilder->new; test_filters( 'SCALAR', 'V', 'v', { term => { _all => 'v' } }, '\\V', \'v', 'v', ); test_filters( 'KEY-VALUE PAIRS', 'K: V', { k => 'v' }, { term => { k => 'v' } }, 'K: UNDEF', { k => undef }, { missing => { field => 'k' } }, 'K: \\V', { k => \'v' }, { k => 'v' }, 'K: []', { k => [] }, { missing => { field => 'k' } }, 'K: [V]', { k => ['v'] }, { term => { k => 'v' } }, 'K: [V,V]', { k => [ 'v', 'v' ] }, { terms => { k => [ 'v', 'v' ] } }, 'K: [UNDEF]', { k => [undef] }, { missing => { field => 'k' } }, 'K: [V,UNDEF]', { k => [ 'v', undef ] }, { or => [ { term => { k => 'v' } }, { missing => { field => 'k' } }, ] }, 'K: [-and,V,UNDEF]', { k => [ '-and', 'v', undef ] }, { and => bag( { missing => { field => 'k' } }, { term => { k => 'v' } }, ) }, ); for my $op (qw(= term terms)) { test_filters( "FIELD OPERATOR: $op", "K: $op V", { k => { $op => 'v' } }, { term => { k => 'v' } }, "K: $op UNDEF", { k => { $op => undef } }, { missing => { field => 'k' } }, "K: $op [V]", { k => { $op => ['v'] } }, { term => { k => 'v' } }, "K: $op [V,V]", { k => { $op => [ 'v', 'v' ] } }, { terms => { k => [ 'v', 'v' ] } }, "K: $op [UNDEF]", { k => { $op => [undef] } }, { missing => { field => 'k' } }, "K: $op [V,UNDEF]", { k => { $op => [ 'v', undef ] } }, { or => [ { term => { k => 'v' } }, { missing => { field => 'k' } }, ] }, 'K: = [-and,V,UNDEF]', { k => { $op => [ '-and', 'v', undef ] } }, { or => [ { term => { k => '-and' } }, { term => { k => 'v' } }, { missing => { field => 'k' } }, ] }, 'K: {VV,ex}', { k => { $op => { value => [ 1, 2 ], execution => 'bool' } } }, { terms => { k => [ 1, 2 ], execution => 'bool' } }, 'K: {V,ex}', { k => { $op => { value => [1], execution => 'bool' } } }, { term => { k => 1 } }, ); } for my $op (qw(!= <> not_term not_terms)) { test_filters( "FIELD OPERATOR: $op", "K: $op V", { k => { $op => 'v' } }, { not => { filter => { term => { k => 'v' } } } }, "K: $op UNDEF", { k => { $op => undef } }, { not => { filter => { missing => { field => 'k' } } } }, "K: $op [V]", { k => { $op => ['v'] } }, { not => { filter => { term => { k => 'v' } } } }, "K: $op [V,V]", { k => { $op => [ 'v', 'v' ] } }, { not => { filter => { terms => { k => [ 'v', 'v' ] } } } }, "K: $op [UNDEF]", { k => { $op => [undef] } }, { not => { filter => { missing => { field => 'k' } } } }, "K: $op [V,UNDEF]", { k => { $op => [ 'v', undef ] } }, { not => { filter => { or => [ { term => { k => 'v' } }, { missing => { field => 'k' } }, ] } } }, 'K: = [-and,V,UNDEF]', { k => { $op => [ '-and', 'v', undef ] } }, { not => { filter => { or => [ { term => { k => '-and' } }, { term => { k => 'v' } }, { missing => { field => 'k' } }, ] } } }, ); } for my $op (qw(^ prefix)) { test_filters( "FIELD OPERATOR: $op", "K: $op V", { k => { $op => 'v' } }, { prefix => { k => 'v' } }, "K: $op UNDEF", { k => { $op => undef } }, qr/ARRAYREF, SCALAR/, "K: $op [V]", { k => { $op => ['v'] } }, { prefix => { k => 'v' } }, "K: $op [V,V]", { k => { $op => [ 'v', 'v' ] } }, { or => [ { prefix => { k => 'v' } }, { prefix => { k => 'v' } } ] }, "K: $op [UNDEF]", { k => { $op => [undef] } }, qr/ARRAYREF, SCALAR/, "K: $op [V,UNDEF]", { k => { $op => [ 'v', undef ] } }, qr/ARRAYREF, SCALAR/, 'K: = [-and,V,UNDEF]', { k => { $op => [ '-and', 'v', undef ] } }, qr/ARRAYREF, SCALAR/, ); } test_filters( "FIELD OPERATOR: not_prefix", "K: not_prefix V", { k => { not_prefix => 'v' } }, { not => { filter => { prefix => { k => 'v' } } } }, "K: not_prefix UNDEF", { k => { not_prefix => undef } }, qr/ARRAYREF, SCALAR/, "K: not_prefix [V]", { k => { not_prefix => ['v'] } }, { not => { filter => { prefix => { k => 'v' } } } }, "K: not_prefix [V,V]", { k => { not_prefix => [ 'v', 'v' ] } }, { not => { filter => { or => [ { prefix => { k => 'v' } }, { prefix => { k => 'v' } } ] } } }, "K: not_prefix [UNDEF]", { k => { not_prefix => [undef] } }, qr/ARRAYREF, SCALAR/, "K: not_prefix [V,UNDEF]", { k => { not_prefix => [ 'v', undef ] } }, qr/ARRAYREF, SCALAR/, 'K: = [-and,V,UNDEF]', { k => { not_prefix => [ '-and', 'v', undef ] } }, qr/ARRAYREF, SCALAR/, ); my %range_map = ( '<' => 'lt', '<=' => 'lte', '>' => 'gt', '>=' => 'gte' ); for my $op (qw(< <= >= > gt gte lt lte)) { my ( $type, $es_op ); if ( $es_op = $range_map{$op} ) { $type = 'numeric_range'; } else { $type = 'range'; $es_op = $op; } test_filters( "FIELD OPERATOR: $op", "K: $op V", { k => { $op => 'v' } }, { $type => { k => { $es_op => 'v' } } }, "K: $op UNDEF", { $type => { $op => undef } }, qr/SCALAR/, "K: $op [V]", { k => { $op => ['v'] } }, qr/SCALAR/, "K: $op [V,V]", { k => { $op => [ 'v', 'v' ] } }, qr/SCALAR/, "K: $op [UNDEF]", { k => { $op => [undef] } }, qr/SCALAR/, "K: $op [V,UNDEF]", { k => { $op => [ 'v', undef ] } }, qr/SCALAR/, 'K: = [-and,V,UNDEF]', { k => { $op => [ '-and', 'v', undef ] } }, qr/SCALAR/, 'K[$op 5],K[$op 10]', { k => [ -and => { '>' => 5 }, { '>' => 10 } ] }, qr/Duplicate/, ); } test_filters( "COMBINED RANGE OPERATORS", "K: gt gte lt lte < <= > >= V", { k => { gt => 'v', gte => 'v', lt => 'v', lte => 'v', '>' => 'V', '>=' => 'V', '<' => 'V', '<=' => 'V' } }, { and => bag( { numeric_range => { k => { gt => 'V', gte => 'V', lt => 'V', lte => 'V' } } }, { range => { k => { gt => 'v', gte => 'v', lt => 'v', lte => 'v' } } }, ) }, "K: [gt gte lt lte < <= > >=] V", { k => [ { gt => 'v' }, { gte => 'v' }, { lt => 'v' }, { lte => 'v' }, { '>' => 'V' }, { '>=' => 'V' }, { '<' => 'V' }, { '<=' => 'V' } ] }, { or => [ { range => { k => { gt => "v" } } }, { range => { k => { gte => "v" } } }, { range => { k => { lt => "v" } } }, { range => { k => { lte => "v" } } }, { numeric_range => { k => { gt => "V" } } }, { numeric_range => { k => { gte => "V" } } }, { numeric_range => { k => { lt => "V" } } }, { numeric_range => { k => { lte => "V" } } }, ], }, ); test_filters( "FIELD OPERATORS: missing/exists", "K: exists 1", { k => { exists => 1 } }, { exists => { field => 'k' } }, "K: exists 0", { k => { exists => 0 } }, { missing => { field => 'k' } }, "K: exists UNDEF", { k => { exists => undef } }, { missing => { field => 'k' } }, "K: missing 1", { k => { missing => 1 } }, { missing => { field => 'k' } }, "K: missing 0", { k => { missing => 0 } }, { exists => { field => 'k' } }, "K: missing UNDEF", { k => { missing => undef } }, { exists => { field => 'k' } }, "K: not_missing HASH", { k => { missing => { null_value => 1, existence => 1 } } }, { missing => { field => 'k', null_value => 1, existence => 1 } }, "K: not_exists 1", { k => { not_exists => 1 } }, { missing => { field => 'k' } }, "K: not_exists 0", { k => { not_exists => 0 } }, { exists => { field => 'k' } }, "K: not_exists UNDEF", { k => { not_exists => undef } }, { exists => { field => 'k' } }, "K: not_missing 1", { k => { not_missing => 1 } }, { not => { filter => { missing => { field => 'k' } } } }, "K: not_missing 0", { k => { not_missing => 0 } }, { not => { filter => { exists => { field => 'k' } } } }, "K: not_missing UNDEF", { k => { not_missing => undef } }, { not => { filter => { exists => { field => 'k' } } } }, "K: not_missing HASH", { k => { not_missing => { null_value => 1, existence => 1 } } }, { not => { filter => { missing => { field => 'k', null_value => 1, existence => 1 } } } }, ); test_filters( "FIELD OPERATORS: geo_distance, geo_distance_range, " . "geo_bounding_box, geo_polygon", 'K: geo_distance %V', { k => { geo_distance => { location => 'LAT,LON', distance => '10km', normalize => 0, optimize_bbox => 'indexed', } } }, { geo_distance => { k => 'LAT,LON', distance => '10km', normalize => 0, optimize_bbox => 'indexed' } }, 'K: geo_distance FOO', { k => { geo_distance => 'FOO' } }, qr/hashref/, 'K: geo_distance_range %V', { k => { geo_distance_range => { location => 'LAT,LON', 'gt' => '10km', 'lt' => '10km', normalize => 0, optimize_bbox => 'indexed', }, } }, { geo_distance_range => { k => 'LAT,LON', gt => '10km', lt => '10km', normalize => 0, optimize_bbox => 'indexed', } }, 'K: geo_distance_range FOO', { k => { geo_distance => 'FOO' } }, qr/hashref/, 'K: geo_bbox %V', { k => { geo_bbox => { top_left => 'LAT,LON', bottom_right => 'LAT,LON', normalize => 0, type => 'indexed', }, } }, { geo_bounding_box => { k => { bottom_right => 'LAT,LON', top_left => 'LAT,LON', normalize => 0, type => 'indexed', } } }, 'K: geo_bbox FOO', { k => { geo_bbox => 'FOO' } }, qr/hashref/, 'K: geo_bounding_box %V', { k => { geo_bounding_box => { top_left => 'LAT,LON', bottom_right => 'LAT,LON', normalize => 0, type => 'indexed', }, } }, { geo_bounding_box => { k => { bottom_right => 'LAT,LON', top_left => 'LAT,LON', normalize => 0, type => 'indexed', } } }, 'K: geo_bounding_box FOO', { k => { geo_bounding_box => 'FOO' } }, qr/hashref/, 'K: geo_polygon @V', { k => { geo_polygon => [ 'LAT,LON', 'LAT,LON' ] } }, { geo_polygon => { k => { points => [ 'LAT,LON', 'LAT,LON' ] } } }, 'K: geo_polygon {}', { k => { geo_polygon => { points => [ 'LAT,LON', 'LAT,LON' ], normalize => 0 } } }, { geo_polygon => { k => { points => [ 'LAT,LON', 'LAT,LON' ], normalize => 0 } } }, 'K: geo_polygon FOO', { k => { geo_polygon => 'FOO' } }, qr/ARRAYREF/, ); test_filters( "FIELD OPERATORS: not_geo_distance, not_geo_distance_range, " . "not_geo_bounding_box, not_geo_polygon", 'K: not_geo_distance %V', { k => { not_geo_distance => { location => 'LAT,LON', distance => '10km', normalize => 0, optimize_bbox => 'indexed', } } }, { not => { filter => { geo_distance => { k => 'LAT,LON', distance => '10km', normalize => 0, optimize_bbox => 'indexed' } } } }, 'K: not_geo_distance_range %V', { k => { not_geo_distance_range => { location => 'LAT,LON', 'gt' => '10km', 'lt' => '10km', normalize => 0, optimize_bbox => 'indexed' }, } }, { not => { filter => { geo_distance_range => { k => 'LAT,LON', gt => '10km', lt => '10km', normalize => 0, optimize_bbox => 'indexed' } } } }, 'K: not_geo_bounding_box %V', { k => { not_geo_bounding_box => { top_left => 'LAT,LON', bottom_right => 'LAT,LON', normalize => 0, type => 'indexed', }, } }, { not => { filter => { geo_bounding_box => { k => { bottom_right => 'LAT,LON', top_left => 'LAT,LON', normalize => 0, type => 'indexed', } } } } }, 'K: not_geo_polygon @V', { k => { not_geo_polygon => [ 'LAT,LON', 'LAT,LON' ] } }, { not => { filter => { geo_polygon => { k => { points => [ 'LAT,LON', 'LAT,LON' ] } } } } }, 'K: not_geo_polygon {}', { k => { not_geo_polygon => { points => [ 'LAT,LON', 'LAT,LON' ], normalize => 0 } } }, { not => { filter => { geo_polygon => { k => { points => [ 'LAT,LON', 'LAT,LON' ], normalize => 0 } } } } }, ); done_testing(); #=================================== sub test_filters { #=================================== note "\n" . shift(); while (@_) { my $name = shift; my $in = shift; my $out = shift; if ( ref $out eq 'Regexp' ) { throws_ok { $a->filter($in) } $out, $name; next; } my $got = $a->filter($in); my $expect = { filter => $out }; my ( $ok, $stack ) = cmp_details( $got, $expect ); if ($ok) { pass $name; next; } fail($name); note("Got:"); note( pp($got) ); note("Expected:"); note( pp($expect) ); diag( deep_diag($stack) ); } }