# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. =head1 NAME Lucy::Docs::Tutorial::QueryObjects - Use Query objects instead of query strings. =head1 DESCRIPTION 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: =over =item * L - Turn a query string into a L object. =item * L - Query for a specific term within a specific field. =item * L - "AND" together multiple Query objects to produce an intersected result set. =back =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 = Lucy::Plan::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 source file's name during our C subroutine: my $category = $filename =~ /art/ ? 'article' : $filename =~ /amend/ ? 'amendment' : $filename =~ /preamble/ ? 'preamble' : die "Can't derive category for $filename"; return { title => $title, content => $bodytext, 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| |; if ($cat) { $select =~ s/"$cat"/"$cat" selected/; } return $select; } We'll start off by loading our new modules and extracting our new CGI parameter. use Lucy::Search::QueryParser; use Lucy::Search::TermQuery; use Lucy::Search::ANDQuery; ... my $category = decode( "UTF-8", $cgi->param('category') || '' ); QueryParser's constructor requires a "schema" argument. We can get that from our IndexSearcher: # Create an IndexSearcher and a QueryParser. my $searcher = Lucy::Search::IndexSearcher->new( index => $path_to_index, ); my $qparser = Lucy::Search::QueryParser->new( schema => $searcher->get_schema, ); Previously, we have been handing raw query strings to IndexSearcher. Behind the scenes, IndexSearcher 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 = Lucy::Search::TermQuery->new( field => 'category', term => $category, ); $query = Lucy::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 => $page_size, ); ... 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 Apache Lucy L. ANDQuery has a companion class, L, and a close relative, L.