[open-ils-commits] r15952 - in trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage: . Driver/Pg (miker)

svn at svn.open-ils.org svn at svn.open-ils.org
Wed Mar 24 14:13:56 EDT 2010


Author: miker
Date: 2010-03-24 14:13:52 -0400 (Wed, 24 Mar 2010)
New Revision: 15952

Modified:
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/QueryParser.pm
Log:
split search/fact metadata; add facet syntax

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm	2010-03-24 17:32:37 UTC (rev 15951)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm	2010-03-24 18:13:52 UTC (rev 15952)
@@ -2,6 +2,8 @@
 use OpenILS::Application::Storage::QueryParser;
 use base 'QueryParser';
 use OpenSRF::Utils::JSON;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
 
 sub init {
     my $class = shift;
@@ -44,25 +46,72 @@
     return $self->parse_tree->toSQL;
 }
 
-sub field_id_map {
+sub facet_field_id_map {
     my $self = shift;
     my $map = shift;
 
-    $self->custom_data->{field_id_map} ||= {};
-    $self->custom_data->{field_id_map} = $map if ($map);
-    return $self->custom_data->{field_id_map};
+    $self->custom_data->{facet_field_id_map} ||= {};
+    $self->custom_data->{facet_field_id_map} = $map if ($map);
+    return $self->custom_data->{facet_field_id_map};
 }
 
-sub add_field_id_map {
+sub add_facet_field_id_map {
     my $self = shift;
     my $class = shift;
     my $field = shift;
     my $id = shift;
     my $weight = shift;
 
+    $self->add_facet_field( $class => $field );
+    $self->facet_field_id_map->{by_id}{$id} = { classname => $class, field => $field, weight => $weight };
+    $self->facet_field_id_map->{by_class}{$class}{$field} = $id;
+
+    return {
+        by_id => { $id => { classname => $class, field => $field, weight => $weight } },
+        by_class => { $class => { $field => $id } }
+    };
+}
+
+sub facet_field_class_by_id {
+    my $self = shift;
+    my $id = shift;
+
+    return $self->facet_field_id_map->{by_id}{$id};
+}
+
+sub facet_field_ids_by_class {
+    my $self = shift;
+    my $class = shift;
+    my $field = shift;
+
+    return undef unless ($class);
+
+    if ($field) {
+        return [$self->facet_field_id_map->{by_class}{$class}{$field}];
+    }
+
+    return [values( %{ $self->facet_field_id_map->{by_class}{$class} } )];
+}
+
+sub search_field_id_map {
+    my $self = shift;
+    my $map = shift;
+
+    $self->custom_data->{search_field_id_map} ||= {};
+    $self->custom_data->{search_field_id_map} = $map if ($map);
+    return $self->custom_data->{search_field_id_map};
+}
+
+sub add_search_field_id_map {
+    my $self = shift;
+    my $class = shift;
+    my $field = shift;
+    my $id = shift;
+    my $weight = shift;
+
     $self->add_search_field( $class => $field );
-    $self->field_id_map->{by_id}{$id} = { classname => $class, field => $field, weight => $weight };
-    $self->field_id_map->{by_class}{$class}{$field} = $id;
+    $self->search_field_id_map->{by_id}{$id} = { classname => $class, field => $field, weight => $weight };
+    $self->search_field_id_map->{by_class}{$class}{$field} = $id;
 
     return {
         by_id => { $id => { classname => $class, field => $field, weight => $weight } },
@@ -70,14 +119,14 @@
     };
 }
 
-sub field_class_by_id {
+sub search_field_class_by_id {
     my $self = shift;
     my $id = shift;
 
-    return $self->field_id_map->{by_id}{$id};
+    return $self->search_field_id_map->{by_id}{$id};
 }
 
-sub field_ids_by_class {
+sub search_field_ids_by_class {
     my $self = shift;
     my $class = shift;
     my $field = shift;
@@ -85,10 +134,10 @@
     return undef unless ($class);
 
     if ($field) {
-        return [$self->field_id_map->{by_class}{$class}{$field}];
+        return [$self->search_field_id_map->{by_class}{$class}{$field}];
     }
 
-    return [values( %{ $self->field_id_map->{by_class}{$class} } )];
+    return [values( %{ $self->search_field_id_map->{by_class}{$class} } )];
 }
 
 sub relevance_bumps {
@@ -124,15 +173,16 @@
 }
 
 
-sub initialize_field_id_map {
+sub initialize_search_field_id_map {
     my $self = shift;
     my $cmf_list = shift;
 
     for my $cmf (@$cmf_list) {
-        __PACKAGE__->add_field_id_map( $cmf->field_class, $cmf->name, $cmf->id, $cmf->weight );
+        __PACKAGE__->add_search_field_id_map( $cmf->field_class, $cmf->name, $cmf->id, $cmf->weight ) if ($U->is_true($cmf->search_field));
+        __PACKAGE__->add_facet_field_id_map( $cmf->field_class, $cmf->name, $cmf->id, $cmf->weight ) if ($U->is_true($cmf->facet_field));
     }
 
-    return $self->field_id_map;
+    return $self->search_field_id_map;
 }
 
 sub initialize_relevance_bumps {
@@ -140,7 +190,7 @@
     my $sra_list = shift;
 
     for my $sra (@$sra_list) {
-        my $c = $self->field_class_by_id( $sra->field );
+        my $c = $self->search_field_class_by_id( $sra->field );
         __PACKAGE__->add_relevance_bump( $c->{classname}, $c->{field}, $sra->bump_type, $sra->multiplier );
     }
 
@@ -152,7 +202,7 @@
     my $tree = shift; # open-ils.cstore.direct.config.metabib_field_index_norm_map.search.atomic { "id" : { "!=" : null } }, { "flesh" : 1, "flesh_fields" : { "cmfinm" : ["norm"] }, "order_by" : [{ "class" : "cmfinm", "field" : "pos" }] }
 
     for my $cmfinm ( @$tree ) {
-        my $field_info = $self->field_class_by_id( $cmfinm->field );
+        my $field_info = $self->search_field_class_by_id( $cmfinm->field );
         __PACKAGE__->add_query_normalizer( $field_info->{classname}, $field_info->{field}, $cmfinm->norm->func, OpenSRF::Utils::JSON->JSON2perl($cmfinm->params) );
     }
 }
@@ -168,7 +218,7 @@
 
     return $_complete if ($_complete);
 
-    $self->initialize_field_id_map( $args{config_metabib_field} )
+    $self->initialize_search_field_id_map( $args{config_metabib_field} )
         if ($args{config_metabib_field});
 
     $self->initialize_relevance_bumps( $args{search_relevance_adjustment} )
@@ -188,43 +238,49 @@
 
 sub TEST_SETUP {
     
-    __PACKAGE__->add_field_id_map( series => seriestitle => 1 => 1 );
+    __PACKAGE__->add_search_field_id_map( series => seriestitle => 1 => 1 );
+
+    __PACKAGE__->add_search_field_id_map( series => seriestitle => 1 => 1 );
     __PACKAGE__->add_relevance_bump( series => seriestitle => first_word => 1.5 );
     __PACKAGE__->add_relevance_bump( series => seriestitle => full_match => 20 );
     
-    __PACKAGE__->add_field_id_map( title => abbreviated => 2 => 1 );
+    __PACKAGE__->add_search_field_id_map( title => abbreviated => 2 => 1 );
     __PACKAGE__->add_relevance_bump( title => abbreviated => first_word => 1.5 );
     __PACKAGE__->add_relevance_bump( title => abbreviated => full_match => 20 );
     
-    __PACKAGE__->add_field_id_map( title => translated => 3 => 1 );
+    __PACKAGE__->add_search_field_id_map( title => translated => 3 => 1 );
     __PACKAGE__->add_relevance_bump( title => translated => first_word => 1.5 );
     __PACKAGE__->add_relevance_bump( title => translated => full_match => 20 );
     
-    __PACKAGE__->add_field_id_map( title => proper => 6 => 1 );
+    __PACKAGE__->add_search_field_id_map( title => proper => 6 => 1 );
     __PACKAGE__->add_query_normalizer( title => proper => 'naco_normalize' );
     __PACKAGE__->add_relevance_bump( title => proper => first_word => 1.5 );
     __PACKAGE__->add_relevance_bump( title => proper => full_match => 20 );
     __PACKAGE__->add_relevance_bump( title => proper => word_order => 10 );
     
-    __PACKAGE__->add_field_id_map( author => coporate => 7 => 1 );
+    __PACKAGE__->add_search_field_id_map( author => coporate => 7 => 1 );
     __PACKAGE__->add_relevance_bump( author => coporate => first_word => 1.5 );
     __PACKAGE__->add_relevance_bump( author => coporate => full_match => 20 );
     
-    __PACKAGE__->add_field_id_map( author => personal => 8 => 1 );
+    __PACKAGE__->add_facet_field_id_map( author => personal => 8 => 1 );
+
+    __PACKAGE__->add_search_field_id_map( author => personal => 8 => 1 );
     __PACKAGE__->add_relevance_bump( author => personal => first_word => 1.5 );
     __PACKAGE__->add_relevance_bump( author => personal => full_match => 20 );
     __PACKAGE__->add_query_normalizer( author => personal => 'naco_normalize' );
     __PACKAGE__->add_query_normalizer( author => personal => 'split_date_range' );
     
-    __PACKAGE__->add_field_id_map( subject => topic => 14 => 1 );
+    __PACKAGE__->add_facet_field_id_map( subject => topic => 14 => 1 );
+
+    __PACKAGE__->add_search_field_id_map( subject => topic => 14 => 1 );
     __PACKAGE__->add_relevance_bump( subject => topic => first_word => 1 );
     __PACKAGE__->add_relevance_bump( subject => topic => full_match => 1 );
     
-    __PACKAGE__->add_field_id_map( subject => complete => 16 => 1 );
+    __PACKAGE__->add_search_field_id_map( subject => complete => 16 => 1 );
     __PACKAGE__->add_relevance_bump( subject => complete => first_word => 1 );
     __PACKAGE__->add_relevance_bump( subject => complete => full_match => 1 );
     
-    __PACKAGE__->add_field_id_map( keyword => keyword => 15 => 1 );
+    __PACKAGE__->add_search_field_id_map( keyword => keyword => 15 => 1 );
     __PACKAGE__->add_relevance_bump( keyword => keyword => first_word => 1 );
     __PACKAGE__->add_relevance_bump( keyword => keyword => full_match => 1 );
     
@@ -506,7 +562,7 @@
 
                 my $node_rank = $node->rank . " * ${talias}_weight.weight";
 
-                $from .= "\n\tLEFT JOIN (\n\t\tSELECT *\n\t\t  FROM $table\n\t\t  WHERE index_vector @@ (" .$node->tsquery . ')';
+                $from .= "\n\tLEFT JOIN (\n\t\tSELECT * /* search */\n\t\t  FROM $table\n\t\t  WHERE index_vector @@ (" .$node->tsquery . ')';
 
                 my @bump_fields;
                 if (@{$node->fields} > 0) {
@@ -541,6 +597,26 @@
 
                 push @rank_list, $node_rank;
 
+            } elsif ($node->isa( 'QueryParser::query_plan::facet' )) {
+
+                my $table = $node->table;
+                my $talias = $node->table_alias;
+
+                $from .= "\n\tJOIN (\n\t\tSELECT * /* facet */\n\t\t  FROM $table\n\t\t  WHERE value IN (\$_$$\$" . join("\$_$$\$,\$_$$\$", @{$node->values}) . "\$_$$\$)".
+                         "\n\t\t\tAND field IN (SELECT id FROM config.metabib_field WHERE field_class = \$_$$\$". $node->classname ."\$_$$\$ AND facet_field";
+
+                if (@{$node->fields} > 0) {
+                    $from .= " AND name IN (";
+                    $from .= "\$_$$\$" . join("\$_$$\$,\$_$$\$", @{$node->fields}) . "\$_$$\$)";
+                }
+
+                $from .= ")";
+
+                my $core_limit = $self->QueryParser->core_limit || 25000;
+                $from .= "\n\t\tLIMIT $core_limit\n\t) AS $talias ON (m.source = $talias.source)";
+
+                $where .= 'TRUE';
+
             } else {
                 my $subnode = $node->flatten;
 
@@ -565,6 +641,39 @@
 use base 'QueryParser::query_plan::filter';
 
 #-------------------------------
+package OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan::facet;
+use base 'QueryParser::query_plan::facet';
+
+sub classname {
+    my $self = shift;
+    my ($classname) = split '\|', $self->name;
+    return $classname;
+}
+
+sub table {
+    my $self = shift;
+    return 'metabib.' . $self->classname . '_field_entry';
+}
+
+sub fields {
+    my $self = shift;
+    my ($classname, at fields) = split '\|', $self->name;
+    return \@fields;
+}
+
+sub table_alias {
+    my $self = shift;
+
+    my $table_alias = "$self";
+    $table_alias =~ s/^.*\(0(x[0-9a-fA-F]+)\)$/$1/go;
+    $table_alias .= '_' . $self->name;
+    $table_alias =~ s/\|/_/go;
+
+    return $table_alias;
+}
+
+
+#-------------------------------
 package OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan::modifier;
 use base 'QueryParser::query_plan::modifier';
 

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/QueryParser.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/QueryParser.pm	2010-03-24 17:32:37 UTC (rev 15951)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/QueryParser.pm	2010-03-24 18:13:52 UTC (rev 15952)
@@ -14,6 +14,11 @@
     }
 );
 
+sub facet_class_count {
+    my $self = shift;
+    return @{$self->facet_classes};
+}
+
 sub search_class_count {
     my $self = shift;
     return @{$self->search_classes};
@@ -106,6 +111,19 @@
     return $modifier;
 }
 
+sub add_facet_class {
+    my $pkg = shift;
+    $pkg = ref($pkg) || $pkg;
+    my $class = shift;
+
+    return $class if (grep { $_ eq $class } @{$pkg->facet_classes});
+
+    push @{$pkg->facet_classes}, $class;
+    $pkg->facet_fields->{$class} = [];
+
+    return $class;
+}
+
 sub add_search_class {
     my $pkg = shift;
     $pkg = ref($pkg) || $pkg;
@@ -134,6 +152,16 @@
     return $parser_config{$class}{operators}{$opname};
 }
 
+sub facet_classes {
+    my $class = shift;
+    $class = ref($class) || $class;
+    my $classes = shift;
+
+    $parser_config{$class}{facet_classes} ||= [];
+    $parser_config{$class}{facet_classes} = $classes if (ref($classes) && @$classes);
+    return $parser_config{$class}{facet_classes};
+}
+
 sub search_classes {
     my $class = shift;
     $class = ref($class) || $class;
@@ -188,6 +216,19 @@
     return $QueryParser::parser_config{$pkg}{default_class};
 }
 
+sub remove_facet_class {
+    my $pkg = shift;
+    $pkg = ref($pkg) || $pkg;
+    my $class = shift;
+
+    return $class if (!grep { $_ eq $class } @{$pkg->facet_classes});
+
+    $pkg->facet_classes( [ grep { $_ ne $class } @{$pkg->facet_classes} ] );
+    delete $QueryParser::parser_config{$pkg}{facet_fields}{$class};
+
+    return $class;
+}
+
 sub remove_search_class {
     my $pkg = shift;
     $pkg = ref($pkg) || $pkg;
@@ -201,6 +242,29 @@
     return $class;
 }
 
+sub add_facet_field {
+    my $pkg = shift;
+    $pkg = ref($pkg) || $pkg;
+    my $class = shift;
+    my $field = shift;
+
+    $pkg->add_facet_class( $class );
+
+    return { $class => $field }  if (grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
+
+    push @{$pkg->facet_fields->{$class}}, $field;
+
+    return { $class => $field };
+}
+
+sub facet_fields {
+    my $class = shift;
+    $class = ref($class) || $class;
+
+    $parser_config{$class}{facet_fields} ||= {};
+    return $parser_config{$class}{facet_fields};
+}
+
 sub add_search_field {
     my $pkg = shift;
     $pkg = ref($pkg) || $pkg;
@@ -269,6 +333,19 @@
     return $parser_config{$class}{field_alias_map};
 }
 
+sub remove_facet_field {
+    my $pkg = shift;
+    $pkg = ref($pkg) || $pkg;
+    my $class = shift;
+    my $field = shift;
+
+    return { $class => $field }  if (!$pkg->facet_fields->{$class} || !grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
+
+    $pkg->facet_fields->{$class} = [ grep { $_ ne $field } @{$pkg->facet_fields->{$class}} ];
+
+    return { $class => $field };
+}
+
 sub remove_search_field {
     my $pkg = shift;
     $pkg = ref($pkg) || $pkg;
@@ -372,7 +449,7 @@
         $search_class_re .= '|' unless ($first_class);
         $first_class = 0;
         $search_class_re .= $class . '(?:\|\w+)*';
-        $seeen_class{$class} = 1;
+        $seen_classes{$class} = 1;
     }
 
     for my $class ( keys %{$pkg->search_class_aliases} ) {
@@ -386,8 +463,8 @@
         $search_class_re .= '|' unless ($first_class);
         $first_class = 0;
 
-        $search_class_re .= $class . '(?:\|\w+)*' if (!$seeen_class{$class});
-        $seeen_class{$class} = 1;
+        $search_class_re .= $class . '(?:\|\w+)*' if (!$seen_classes{$class});
+        $seen_classes{$class} = 1;
     }
     $search_class_re .= '):';
 
@@ -412,6 +489,9 @@
 
 
     # Build the filter and modifier uber-regexps
+    my $facet_re = '^\s*((?:' . join( '|', @{$pkg->facet_classes}) . ')(?:\|\w+)*)\[(.+?)\]';
+    warn " Facet RE: $facet_re\n" if $self->debug;
+
     my $filter_re = '^\s*(' . join( '|', @{$pkg->filters}) . ')\(([^()]+)\)';
     my $filter_as_class_re = '^\s*(' . join( '|', @{$pkg->filters}) . '):\s*(\S+)';
 
@@ -494,6 +574,15 @@
             $struct->joiner( '|' );
 
             $last_type = 'OR';
+        } elsif ($self->facet_class_count && /$facet_re/) { # changing current class
+            warn "Encountered facet: $1 => $2\n" if $self->debug;
+
+            my $facet = $1;
+            my $facet_value = [ split '\s*#\s*', $2 ];
+            $struct->new_facet( $facet => $facet_value );
+            $_ = $';
+
+            $last_type = '';
         } elsif ($self->search_class_count && /$search_class_re/) { # changing current class
             warn "Encountered class change: $1\n" if $self->debug;
 
@@ -613,6 +702,18 @@
     return $node;
 }
 
+sub new_facet {
+    my $self = shift;
+    my $pkg = ref($self) || $self;
+    my $name = shift;
+    my $args = shift;
+
+    my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args );
+    $self->add_node( $node );
+
+    return $node;
+}
+
 sub new_filter {
     my $self = shift;
     my $pkg = ref($self) || $self;
@@ -724,6 +825,24 @@
     return $self;
 }
 
+sub facets {
+    my $self = shift;
+    $self->{facets} ||= [];
+    return $self->{facets};
+}
+
+sub add_facet {
+    my $self = shift;
+    my $facet = shift;
+
+    $self->{facets} ||= [];
+    return $self if (grep {$_->name eq $facet->name} @{$self->{facets}});
+
+    push(@{$self->{facets}}, $facet);
+
+    return $self;
+}
+
 sub filters {
     my $self = shift;
     $self->{filters} ||= [];
@@ -907,6 +1026,32 @@
 }
 
 #-------------------------------
+package QueryParser::query_plan::facet;
+
+sub new {
+    my $pkg = shift;
+    $pkg = ref($pkg) || $pkg;
+    my %args = @_;
+
+    return bless \%args => $pkg;
+}
+
+sub plan {
+    my $self = shift;
+    return $self->{plan};
+}
+
+sub name {
+    my $self = shift;
+    return $self->{name};
+}
+
+sub values {
+    my $self = shift;
+    return $self->{'values'};
+}
+
+#-------------------------------
 package QueryParser::query_plan::modifier;
 
 sub new {



More information about the open-ils-commits mailing list