=head1 NAME

KinoSearch::Docs::Tutorial::QueryObjects - Use Query objects instead of query


Until now, our search app has had only a single search box.  In this tutorial
chapter, we'll move towards an "advanced search" interface, by adding a
"category" drop-down menu.  Three new classes will be required:


=item *

L<QueryParser|KinoSearch::QueryParser> - Turn a query string into a
L<Query|KinoSearch::Search::Query> object.

=item *

L<TermQuery|KinoSearch::Search::TermQuery> - Query for a specific term within
a specific field.

=item *

L<ANDQuery|KinoSearch::Search::ANDQuery> - "AND" together multiple Query
objects to produce an intersected result set.


=head2 Adaptations to indexer.pl

Our new "category" field will be a StringType field rather than a FullTextType
field, because we will only be looking for exact matches.  It needs to be
indexed, but since we won't display its value, it doesn't need to be stored.

    my $cat_type = KinoSearch::FieldType::StringType->new( stored => 0 );
    $schema->spec_field( name => 'category', type => $cat_type );

There will be three possible values: "article", "amendment", and "preamble",
which we'll hack out of the HTML source file's name during our C<parse_file>

    my $category
        = $filename =~ /art/      ? 'article'
        : $filename =~ /amend/    ? 'amendment'
        : $filename =~ /preamble/ ? 'preamble'
        :                           die "Can't derive category for $filename";
    return {
        title    => $title_node->as_trimmed_text,
        content  => $bodytext_node->as_trimmed_text,
        url      => "/us_constitution/$filename",
        category => $category,

=head2 Adaptations to search.cgi

The "category" constraint will be added to our search interface using an HTML
"select" element:

    # Build up the HTML "select" object for the "category" field.
    sub generate_category_select {
        my $cat = shift;
        my $select = qq|
          <select name="category">
            <option value="">All Sections</option>
            <option value="article">Articles</option>
            <option value="amendment">Amendments</option>
        if ($cat) {
            $select =~ s/"$cat"/"$cat" selected/;
        return $select;

We'll start off by loading our new modules and extracting our new CGI

    use KinoSearch::QueryParser;
    use KinoSearch::Search::TermQuery;
    use KinoSearch::Search::ANDQuery;
    my $category = $cgi->param('category') || '';

QueryParser's constructor requires a "schema" argument.  We can get that from
our Searcher:

    # Create a Searcher and a QueryParser.
    my $searcher = KinoSearch::Searcher->new( index => $path_to_index );
    my $qparser  = KinoSearch::QueryParser->new( 
        schema => $searcher->get_schema,

Previously, we have been handing raw query strings to Searcher.  Behind the
scenes, Searcher has been using a QueryParser to turn those query strings into
Query objects.  Now, we will bring QueryParser into the foreground and parse
the strings explicitly.

    my $query = $qparser->parse($q);

If the user has specified a category, we'll use an ANDQuery to join our parsed
query together with a TermQuery representing the category.

    if ($category) {
        my $category_query = KinoSearch::Search::TermQuery->new(
            field => 'category', 
            term  => $category,
        $query = KinoSearch::Search::ANDQuery->new(
            children => [ $query, $category_query ]

Now when we execute the query...

    # Execute the Query and get a Hits object.
    my $hits = $searcher->hits(
        query      => $query,
        offset     => $offset,
        num_wanted => $hits_per_page,

... we'll get a result set which is the intersection of the parsed query and
the category query.

=head1 Congratulations!

You've made it to the end of the tutorial.

=head1 SEE ALSO

For additional thematic documentation, see the KinoSearch

ANDQuery has a companion class, L<ORQuery|KinoSearch::Search::ORQuery>, and a
close relative,


Copyright 2008-2009 Marvin Humphrey


See L<KinoSearch> version 0.30.