Ignore:
Timestamp:
02/28/10 22:57:46 (2 years ago)
Author:
karpet
Message:

add docs and prelim tests

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Search-Query-Dialect-KSx/trunk/lib/Search/Query/Dialect/KSx.pm

    r2552 r2556  
    66use Data::Dump qw( dump ); 
    77use Search::Query::Field::KSx; 
     8use KinoSearch::Search::ANDQuery; 
     9use KinoSearch::Search::NoMatchQuery; 
     10use KinoSearch::Search::NOTQuery; 
     11use KinoSearch::Search::ORQuery; 
     12use KinoSearch::Search::PhraseQuery; 
     13use KinoSearch::Search::RangeQuery; 
     14use KinoSearch::Search::TermQuery; 
     15use Search::Query::Dialect::KSx::NOTWildcardQuery; 
     16use Search::Query::Dialect::KSx::WildcardQuery; 
    817 
    918our $VERSION = '0.01'; 
     
    2433 my $query = Search::Query->parser( dialect => 'KSx' )->parse('foo'); 
    2534 print $query; 
     35 my $ks_query = $query->as_ks_query(); 
     36 my $hits = $ks_searcher->hits( query => $ks_query ); 
    2637 
    2738=head1 DESCRIPTION 
    2839 
    29 Search::Query::Dialect::KSx supports the KinoSearch::QueryParser syntax. 
     40Search::Query::Dialect::KSx extends the KinoSearch::QueryParser syntax 
     41to support wildcards, proximity and ranges, in addition to the standard 
     42Search::Query features. 
    3043 
    3144=head1 METHODS 
     
    149162        = $clause->{field} 
    150163        ? ( $clause->{field} ) 
    151         : ( @{ $default_field ? @$default_field : [] } ); 
     164        : ( defined $default_field ? @$default_field : () ); 
    152165 
    153166    # what value 
     
    256269} 
    257270 
     271=head2 as_ks_query 
     272 
     273Returns the Dialect object as a KinoSearch::Search::Query-based object. 
     274The Dialect object is walked and converted to a  
     275KinoSearch::Searcher-compatible tree. 
     276 
     277=cut 
     278 
     279sub as_ks_query { 
     280    my $self = shift; 
     281    my $tree = shift || $self; 
     282 
     283    my @q; 
     284    foreach my $prefix ( '+', '', '-' ) { 
     285        my @clauses; 
     286        my $joiner = $op_map{$prefix}; 
     287        next unless exists $tree->{$prefix}; 
     288        for my $clause ( @{ $tree->{$prefix} } ) { 
     289            push( @clauses, $self->_ks_clause( $clause, $prefix ) ); 
     290        } 
     291        next if !@clauses; 
     292 
     293        my $ks_class = 'KinoSearch::Search::' . $joiner . 'Query'; 
     294 
     295        push @q, @clauses == 1 
     296            ? $clauses[0] 
     297            : $ks_class->new( children => [ grep {defined} @clauses ] ); 
     298    } 
     299 
     300    return @q == 1 
     301        ? $q[0] 
     302        : KinoSearch::Search::ANDQuery->new( children => \@q ); 
     303} 
     304 
     305sub _ks_clause { 
     306    my $self   = shift; 
     307    my $clause = shift; 
     308    my $prefix = shift; 
     309 
     310    #warn dump $clause; 
     311    #warn "prefix = '$prefix'"; 
     312 
     313    if ( $clause->{op} eq '()' ) { 
     314        return $self->as_ks_query( $clause->{value} ); 
     315    } 
     316 
     317    # make sure we have a field 
     318    my $default_field = $self->default_field || $self->parser->default_field; 
     319    my @fields 
     320        = $clause->{field} 
     321        ? ( $clause->{field} ) 
     322        : ( defined $default_field ? @$default_field : () ); 
     323 
     324    # what value 
     325    my $value 
     326        = ref $clause->{value} 
     327        ? $clause->{value} 
     328        : $self->_doctor_value($clause); 
     329 
     330    # if we have no fields, we can't proceed, because KS 
     331    # requires a field for every term. 
     332    if ( !@fields ) { 
     333        croak 
     334            "No field specified for term '$value' -- set a default_field in Parser or Dialect"; 
     335    } 
     336 
     337    my $wildcard = $self->wildcard; 
     338 
     339    # normalize operator 
     340    my $op = $clause->{op} || ":"; 
     341    if ( $op eq '=' ) { 
     342        $op = ':'; 
     343    } 
     344    if ( $prefix eq '-' ) { 
     345        $op = '!' . $op; 
     346    } 
     347    if ( $value =~ m/\%/ ) { 
     348        $op = $prefix eq '-' ? '!~' : '~'; 
     349    } 
     350 
     351    my $quote = $clause->quote || ''; 
     352 
     353    my @buf; 
     354NAME: for my $name (@fields) { 
     355        my $field = $self->_get_field($name); 
     356 
     357        if ( defined $field->callback ) { 
     358            push( @buf, $field->callback->( $field, $op, $value ) ); 
     359            next NAME; 
     360        } 
     361 
     362        #warn dump [ $name, $op, $quote, $value ]; 
     363 
     364        # invert fuzzy 
     365        if ( $op eq '!~' || ( $op eq '!:' and $value =~ m/[$wildcard\*\?]/ ) ) 
     366        { 
     367            $value .= $wildcard unless $value =~ m/\Q$wildcard/; 
     368 
     369            push( 
     370                @buf, 
     371                Search::Query::Dialect::KSx::NOTWildcardQuery->new( 
     372                    field => $name, 
     373                    term  => $value, 
     374                ) 
     375            ); 
     376        } 
     377 
     378        # fuzzy 
     379        elsif ( $op eq '~' 
     380            || ( $op eq ':' and $value =~ m/[$wildcard\*\?]/ ) ) 
     381        { 
     382            $value .= $wildcard unless $value =~ m/\Q$wildcard/; 
     383 
     384            push( 
     385                @buf, 
     386                Search::Query::Dialect::KSx::WildcardQuery->new( 
     387                    field => $name, 
     388                    term  => $value, 
     389                ) 
     390            ); 
     391        } 
     392 
     393        # invert 
     394        elsif ( $op eq '!:' ) { 
     395            push( 
     396                @buf, 
     397                KinoSearch::Search::NOTQuery->new( 
     398                    field => $name, 
     399                    term  => $value, 
     400                ) 
     401            ); 
     402        } 
     403 
     404        # range 
     405        elsif ( $op eq '..' ) { 
     406            if ( ref $value ne 'ARRAY' or @$value != 2 ) { 
     407                croak "range of values must be a 2-element ARRAY"; 
     408            } 
     409 
     410            my $range_query = KinoSearch::Search::RangeQuery->new( 
     411                field         => $name, 
     412                lower_term    => $value->[0], 
     413                upper_term    => $value->[1], 
     414                include_lower => 1, 
     415                include_upper => 1, 
     416            ); 
     417 
     418            push( @buf, $range_query ); 
     419 
     420        } 
     421 
     422        # invert range 
     423        elsif ( $op eq '!..' ) { 
     424            if ( ref $value ne 'ARRAY' or @$value != 2 ) { 
     425                croak "range of values must be a 2-element ARRAY"; 
     426            } 
     427 
     428            croak "NOT Range query not yet supported"; 
     429        } 
     430 
     431        # standard 
     432        else { 
     433            push( 
     434                @buf, 
     435                KinoSearch::Search::TermQuery->new( 
     436                    field => $name, 
     437                    term  => $value, 
     438                ) 
     439            ); 
     440        } 
     441    } 
     442    if ( @buf == 1 ) { 
     443        return $buf[0]; 
     444    } 
     445    my $joiner = $prefix eq '-' ? 'AND' : 'OR'; 
     446    my $ks_class = 'KinoSearch::Search::' . $joiner . 'Query'; 
     447    return $ks_class->new( children => \@buf ); 
     448} 
     449 
    258450=head2 field_class 
    259451 
Note: See TracChangeset for help on using the changeset viewer.