LP1968754: Better handling for blank and wildcard course searches
authorJane Sandberg <js7389@princeton.edu>
Fri, 20 May 2022 19:26:57 +0000 (13:26 -0600)
committerJane Sandberg <js7389@princeton.edu>
Sat, 13 May 2023 15:48:05 +0000 (08:48 -0700)
Signed-off-by: Jane Sandberg <sandbergja@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm
Open-ILS/src/perlmods/t/26-course-opac-search.t [new file with mode: 0644]

index f0e8ab0..866eb7e 100644 (file)
@@ -298,27 +298,32 @@ sub _create_where_clause {
     for my $query_obj (@{$queries}) {
         my $type = $query_obj->{'qtype'};
         my $query = $query_obj->{'value'};
-        $query =~ s/\*//g;
+
+        # Remove punctuation except for the the * wildcard
+        $query =~ s/\w\s\*//;
+
+        # Do we have a blank query, or just wildcards and/or spaces?
+        next if $query =~ /^[\s\*]*$/;
         my $bool = $query_obj->{'bool'};
         my $contains = $query_obj->{'contains'};
         my $operator = ($contains eq 'nocontains') ? '!~*' : '~*';
         my $search_query;
         if ($type eq 'instructor') {
+            $query =~ s/\*$/:*/; # postgres prefix matching syntax ends with :* (e.g. string:*, not string*)
             my $in = ($contains eq 'nocontains') ? "not in" : "in";
             $search_query = {'id' => {$in => {
-                'from' => 'acmcu',
+                'from' => {'acmcu' => 'acmr'},
                 'select' => {'acmcu' => ['course']},
-                'where' => {'usr' => {'in' => {
-                    'from' => 'au',
-                    'select' => {'au' => ['id']},
-                    'where' => {
-                        'name_kw_tsvector' => {
-                            '@@' => {'value' => [ 'plainto_tsquery', $query ] }
-                        }
-                    }
-                }}}
+                'where' => [
+                    {"+acmr"=>"is_public"},
+                    {"usr"=>{"in"=>
+                        {"select"=>{"au"=>["id"]},
+                        "where"=>{"name_kw_tsvector"=>
+                        {"@@"=>{"value"=>["to_tsquery",$query]}}},
+                    "from"=>"au"}}}]
             }}};
         } else {
+            $query =~ s/\*/.*/;
             $search_query = ($contains eq 'nocontains') ?
               {'+acmc' => { $type => {$operator => $query}}} :
               {$type => {$operator => $query}};
diff --git a/Open-ILS/src/perlmods/t/26-course-opac-search.t b/Open-ILS/src/perlmods/t/26-course-opac-search.t
new file mode 100644 (file)
index 0000000..9d2da15
--- /dev/null
@@ -0,0 +1,83 @@
+#!perl
+use strict; use warnings;
+use Test::More tests => 9;
+use OpenILS::WWW::EGCatLoader;
+
+use_ok('OpenILS::WWW::EGCatLoader');
+can_ok( 'OpenILS::WWW::EGCatLoader', '_create_where_clause' );
+
+my $orgs = (1..9);
+
+my @course_name_query = {'qtype' => 'name','contains' => 'contains','bool' => 'and','value' => 'zebra'};
+my $course_name_expected = {"-and" => [
+                               {"owning_lib"=> $orgs},
+                               {"-not"=>{"+acmc"=>"is_archived"}},
+                               {"name"=>{"~*"=>"zebra"}}]};
+is_query_parsed_correctly ( \@course_name_query, $course_name_expected, 'Create a valid course name search json query' );
+
+my @course_name_wildcard_query = {'qtype' => 'name','contains' => 'contains','bool' => 'and','value' => '*ebra'};
+my $course_name_wildcard_expected = {"-and" => [
+                               {"owning_lib"=> $orgs},
+                               {"-not"=>{"+acmc"=>"is_archived"}},
+                               {"name"=>{"~*"=>".*ebra"}}]};
+is_query_parsed_correctly ( \@course_name_wildcard_query, $course_name_wildcard_expected, 'Create a valid course name search json query' );
+
+my @empty_number_query = {'qtype' => 'course_number','contains' => 'contains','bool' => 'and','value' => ''};
+my $blank_query_expected = {"-and" => [
+                               {"owning_lib"=> $orgs},
+                               {"-not"=>{"+acmc"=>"is_archived"}}]};
+is_query_parsed_correctly ( \@empty_number_query, $blank_query_expected,
+                            'Create a valid course number search json query for blank search' );
+
+my @instructor_wildcard_query = {'qtype' => 'instructor','contains' => 'contains','bool' => 'and','value' => '*'};
+is_query_parsed_correctly( \@instructor_wildcard_query, $blank_query_expected,
+                           'Create a valid instructor search json query for wildcard search' );
+
+my @instructor_blank_query = {'qtype' => 'instructor','contains' => 'contains','bool' => 'and','value' => ''};
+is_query_parsed_correctly( \@instructor_blank_query, $blank_query_expected,
+                           'Create a valid instructor search json query for blank search' );
+
+my @instructor_query = {'qtype' => 'instructor','contains' => 'contains','bool' => 'and','value' => 'leonard'};
+my $instructor_query_expected = {"-and" => [
+                                    {"owning_lib"=> $orgs},
+                                    {"-not"=>{"+acmc"=>"is_archived"}},
+                                    {"id"=>{"in"=>{
+                                        "where"=>[
+                                            {"+acmr"=>"is_public"},
+                                            {"usr"=>{"in"=>
+                                                {"select"=>{"au"=>["id"]},
+                                                "where"=>{"name_kw_tsvector"=>
+                                                {"@@"=>{"value"=>["to_tsquery","leonard"]}}},
+                                            "from"=>"au"}}}],                                        
+                                        "select"=>{"acmcu"=>["course"]},
+                                        "from"=>{"acmcu" => "acmr"}}}}]};
+is_query_parsed_correctly( \@instructor_query, $instructor_query_expected,
+                           'Create a valid instructor search json query');
+
+my @instructor_prefix_wildcard_query = {'qtype' => 'instructor','contains' => 'contains','bool' => 'and','value' => 'd*'};
+my $instructor_prefix_wildcard_query_expected = {"-and" => [
+                                                    {"owning_lib"=> $orgs},
+                                                    {"-not"=>{"+acmc"=>"is_archived"}},
+                                                    {"id"=>{"in"=>{
+                                                        "where"=>[
+                                                            {"+acmr"=>"is_public"},
+                                                            {"usr"=>{"in"=>
+                                                                {"select"=>{"au"=>["id"]},
+                                                                "where"=>{"name_kw_tsvector"=>
+                                                                {"@@"=>{"value"=>["to_tsquery","d:*"]}}},
+                                                            "from"=>"au"}}}],                                        
+                                                        "select"=>{"acmcu"=>["course"]},
+                                                        "from"=>{"acmcu" => "acmr"}}}}]};
+is_query_parsed_correctly( \@instructor_prefix_wildcard_query, $instructor_prefix_wildcard_query_expected,
+                           'Create a valid instructor prefix tsquery json query');
+
+
+
+sub is_query_parsed_correctly {
+    my ($query_hash, $expected, $description) = @_;
+    my $ctx = {'processed_search_query' => '*'};
+    my $query = OpenILS::WWW::EGCatLoader::_create_where_clause($ctx, $query_hash, $orgs);
+    return is_deeply($query, $expected, $description);
+}
+
+1;