1 package OpenILS::Application::Storage::Publisher::metabib;
2 use base qw/OpenILS::Application::Storage::Publisher/;
4 use OpenSRF::EX qw/:try/;
5 use OpenILS::Application::Storage::FTS;
6 use OpenILS::Utils::Fieldmapper;
7 use OpenSRF::Utils::Logger qw/:level/;
8 use OpenSRF::Utils::Cache;
9 use OpenSRF::Utils::JSON;
11 use Digest::MD5 qw/md5_hex/;
14 my $log = 'OpenSRF::Utils::Logger';
18 sub ordered_records_from_metarecord {
26 my (@types,@forms,@blvl);
29 my ($t, $f, $b) = split '-', $formats;
30 @types = split '', $t;
31 @forms = split '', $f;
37 "actor.org_unit_descendants($org, $depth)" :
38 "actor.org_unit_descendants($org)" ;
41 my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.opac_visible IS TRUE AND cl.opac_visible IS TRUE';
42 $copies_visible = '' if ($self->api_name =~ /staff/o);
44 my $sm_table = metabib::metarecord_source_map->table;
45 my $rd_table = metabib::record_descriptor->table;
46 my $fr_table = metabib::full_rec->table;
47 my $cn_table = asset::call_number->table;
48 my $cl_table = asset::copy_location->table;
49 my $cp_table = asset::copy->table;
50 my $cs_table = config::copy_status->table;
51 my $src_table = config::bib_source->table;
52 my $out_table = actor::org_unit_type->table;
53 my $br_table = biblio::record_entry->table;
60 FIRST(COALESCE(LTRIM(SUBSTR( value, COALESCE(SUBSTRING(ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'zzzzzzzz')) AS title
72 if ($copies_visible) {
78 WHERE rd.record = sm.source
79 AND fr.record = sm.source
82 AND (EXISTS ((SELECT 1
84 JOIN $cn_table cn ON (cp.call_number = cn.id)
85 JOIN $cs_table cs ON (cp.status = cs.id)
86 JOIN $cl_table cl ON (cp.location = cl.id)
87 JOIN $descendants d ON (cp.circ_lib = d.id)
88 WHERE cn.record = sm.source
94 WHERE src.id = br.source
95 AND src.transcendant IS TRUE))
102 JOIN $br_table br ON (sm.source = br.id)
103 JOIN $fr_table fr ON (fr.record = br.id)
104 JOIN $rd_table rd ON (rd.record = br.id)
105 WHERE sm.metarecord = ?
111 WHERE cn.record = br.id
112 AND cn.deleted = FALSE
113 AND cp.deleted = FALSE
114 AND cp.circ_lib = d.id
115 AND cn.id = cp.call_number
121 WHERE cn.record = br.id
122 AND cn.deleted = FALSE
123 AND cp.deleted = FALSE
124 AND cn.id = cp.call_number
130 WHERE src.id = br.source
131 AND src.transcendant IS TRUE))
137 $sql .= ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
141 $sql .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
145 $sql .= ' AND rd.bib_level IN ('.join(',',map{'?'}@blvl).')';
155 GROUP BY record, item_type, item_form, quality
158 WHEN item_type IS NULL -- default
160 WHEN item_type = '' -- default
162 WHEN item_type IN ('a','t') -- books
164 WHEN item_type = 'g' -- movies
166 WHEN item_type IN ('i','j') -- sound recordings
168 WHEN item_type = 'm' -- software
170 WHEN item_type = 'k' -- images
172 WHEN item_type IN ('e','f') -- maps
174 WHEN item_type IN ('o','p') -- mixed
176 WHEN item_type IN ('c','d') -- music
178 WHEN item_type = 'r' -- 3d
185 my $ids = metabib::metarecord_source_map->db_Main->selectcol_arrayref($sql, {}, "$mr", @types, @forms, @blvl);
186 return $ids if ($self->api_name =~ /atomic$/o);
188 $client->respond( $_ ) for ( @$ids );
192 __PACKAGE__->register_method(
193 api_name => 'open-ils.storage.ordered.metabib.metarecord.records',
194 method => 'ordered_records_from_metarecord',
198 __PACKAGE__->register_method(
199 api_name => 'open-ils.storage.ordered.metabib.metarecord.records.staff',
200 method => 'ordered_records_from_metarecord',
205 __PACKAGE__->register_method(
206 api_name => 'open-ils.storage.ordered.metabib.metarecord.records.atomic',
207 method => 'ordered_records_from_metarecord',
211 __PACKAGE__->register_method(
212 api_name => 'open-ils.storage.ordered.metabib.metarecord.records.staff.atomic',
213 method => 'ordered_records_from_metarecord',
218 # XXX: this subroutine and its two registered methods are marked for
219 # deprecation, as they do not work properly in 2.x (these tags are no longer
220 # normalized in mfr) and are not in known use
224 my $isxn = lc(shift());
228 $isxn =~ s/-//o if ($self->api_name =~ /isbn/o);
230 my $tag = ($self->api_name =~ /isbn/o) ? "'020' OR f.tag = '024'" : "'022'";
232 my $fr_table = metabib::full_rec->table;
233 my $bib_table = biblio::record_entry->table;
236 SELECT DISTINCT f.record
238 JOIN $bib_table b ON (b.id = f.record)
241 AND b.deleted IS FALSE
244 my $list = metabib::full_rec->db_Main->selectcol_arrayref($sql, {}, "$isxn%");
245 $client->respond($_) for (@$list);
248 __PACKAGE__->register_method(
249 api_name => 'open-ils.storage.id_list.biblio.record_entry.search.isbn',
250 method => 'isxn_search',
254 __PACKAGE__->register_method(
255 api_name => 'open-ils.storage.id_list.biblio.record_entry.search.issn',
256 method => 'isxn_search',
261 sub metarecord_copy_count {
267 my $sm_table = metabib::metarecord_source_map->table;
268 my $rd_table = metabib::record_descriptor->table;
269 my $cn_table = asset::call_number->table;
270 my $cp_table = asset::copy->table;
271 my $br_table = biblio::record_entry->table;
272 my $src_table = config::bib_source->table;
273 my $cl_table = asset::copy_location->table;
274 my $cs_table = config::copy_status->table;
275 my $out_table = actor::org_unit_type->table;
277 my $descendants = "actor.org_unit_descendants(u.id)";
278 my $ancestors = "actor.org_unit_ancestors(?) u JOIN $out_table t ON (u.ou_type = t.id)";
280 if ($args{org_unit} < 0) {
281 $args{org_unit} *= -1;
282 $ancestors = "(select org_unit as id from actor.org_lasso_map where lasso = ?) u CROSS JOIN (SELECT -1 AS depth) t";
285 my $copies_visible = 'AND a.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.opac_visible IS TRUE AND cl.opac_visible IS TRUE';
286 $copies_visible = '' if ($self->api_name =~ /staff/o);
288 my (@types,@forms,@blvl);
289 my ($t_filter, $f_filter, $b_filter) = ('','','');
292 my ($t, $f, $b) = split '-', $args{format};
293 @types = split '', $t;
294 @forms = split '', $f;
295 @blvl = split '', $b;
298 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
302 $f_filter .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
306 $b_filter .= ' AND rd.bib_level IN ('.join(',',map{'?'}@blvl).')';
316 JOIN $cn_table cn ON (cn.record = r.source)
317 JOIN $rd_table rd ON (cn.record = rd.record)
318 JOIN $cp_table cp ON (cn.id = cp.call_number)
319 JOIN $cs_table cs ON (cp.status = cs.id)
320 JOIN $cl_table cl ON (cp.location = cl.id)
321 JOIN $descendants a ON (cp.circ_lib = a.id)
322 WHERE r.metarecord = ?
323 AND cn.deleted IS FALSE
324 AND cp.deleted IS FALSE
334 JOIN $cn_table cn ON (cn.record = r.source)
335 JOIN $rd_table rd ON (cn.record = rd.record)
336 JOIN $cp_table cp ON (cn.id = cp.call_number)
337 JOIN $cs_table cs ON (cp.status = cs.id)
338 JOIN $cl_table cl ON (cp.location = cl.id)
339 JOIN $descendants a ON (cp.circ_lib = a.id)
340 WHERE r.metarecord = ?
341 AND cp.status IN (0,7,12)
342 AND cn.deleted IS FALSE
343 AND cp.deleted IS FALSE
353 JOIN $cn_table cn ON (cn.record = r.source)
354 JOIN $rd_table rd ON (cn.record = rd.record)
355 JOIN $cp_table cp ON (cn.id = cp.call_number)
356 JOIN $cs_table cs ON (cp.status = cs.id)
357 JOIN $cl_table cl ON (cp.location = cl.id)
358 WHERE r.metarecord = ?
359 AND cn.deleted IS FALSE
360 AND cp.deleted IS FALSE
361 AND cp.opac_visible IS TRUE
362 AND cs.opac_visible IS TRUE
363 AND cl.opac_visible IS TRUE
372 JOIN $br_table br ON (br.id = r.source)
373 JOIN $src_table src ON (src.id = br.source)
374 WHERE r.metarecord = ?
375 AND src.transcendant IS TRUE
383 my $sth = metabib::metarecord_source_map->db_Main->prepare_cached($sql);
384 $sth->execute( ''.$args{metarecord},
388 ''.$args{metarecord},
392 ''.$args{metarecord},
396 ''.$args{metarecord},
400 while ( my $row = $sth->fetchrow_hashref ) {
401 $client->respond( $row );
405 __PACKAGE__->register_method(
406 api_name => 'open-ils.storage.metabib.metarecord.copy_count',
407 method => 'metarecord_copy_count',
412 __PACKAGE__->register_method(
413 api_name => 'open-ils.storage.metabib.metarecord.copy_count.staff',
414 method => 'metarecord_copy_count',
420 sub biblio_multi_search_full_rec {
425 my $class_join = $args{class_join} || 'AND';
426 my $limit = $args{limit} || 100;
427 my $offset = $args{offset} || 0;
428 my $sort = $args{'sort'};
429 my $sort_dir = $args{sort_dir} || 'DESC';
434 for my $arg (@{ $args{searches} }) {
435 my $term = $$arg{term};
436 my $limiters = $$arg{restrict};
438 my ($index_col) = metabib::full_rec->columns('FTS');
439 $index_col ||= 'value';
440 my $search_table = metabib::full_rec->table;
442 my $fts = OpenILS::Application::Storage::FTS->compile('default' => $term, 'value',"$index_col");
444 my $fts_where = $fts->sql_where_clause();
445 my @fts_ranks = $fts->fts_rank;
447 my $rank = join(' + ', @fts_ranks);
450 for my $limit (@$limiters) {
451 if ($$limit{tag} =~ /^\d+$/ and $$limit{tag} < 10) {
452 # MARC control field; mfr.subfield is NULL
453 push @wheres, "( tag = ? AND $fts_where )";
454 push @binds, $$limit{tag};
455 $log->debug("Limiting query using { tag => $$limit{tag} }", DEBUG);
457 push @wheres, "( tag = ? AND subfield LIKE ? AND $fts_where )";
458 push @binds, $$limit{tag}, $$limit{subfield};
459 $log->debug("Limiting query using { tag => $$limit{tag}, subfield => $$limit{subfield} }", DEBUG);
462 my $where = join(' OR ', @wheres);
464 push @selects, "SELECT record, AVG($rank) as sum FROM $search_table WHERE $where GROUP BY record";
468 my $descendants = defined($args{depth}) ?
469 "actor.org_unit_descendants($args{org_unit}, $args{depth})" :
470 "actor.org_unit_descendants($args{org_unit})" ;
473 my $metabib_record_descriptor = metabib::record_descriptor->table;
474 my $metabib_full_rec = metabib::full_rec->table;
475 my $asset_call_number_table = asset::call_number->table;
476 my $asset_copy_table = asset::copy->table;
477 my $cs_table = config::copy_status->table;
478 my $cl_table = asset::copy_location->table;
479 my $br_table = biblio::record_entry->table;
481 my $cj = 'HAVING COUNT(x.record) = ' . scalar(@selects) if ($class_join eq 'AND');
483 '(SELECT x.record, sum(x.sum) FROM (('.
484 join(') UNION ALL (', @selects).
485 ")) x GROUP BY 1 $cj ORDER BY 2 DESC )";
487 my $has_vols = 'AND cn.owning_lib = d.id';
488 my $has_copies = 'AND cp.call_number = cn.id';
489 my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.opac_visible IS TRUE AND cl.opac_visible IS TRUE';
491 if ($self->api_name =~ /staff/o) {
492 $copies_visible = '';
493 $has_copies = '' if ($ou_type == 0);
494 $has_vols = '' if ($ou_type == 0);
497 my ($t_filter, $f_filter) = ('','');
498 my ($a_filter, $l_filter, $lf_filter) = ('','','');
500 if (my $a = $args{audience}) {
501 $a = [$a] if (!ref($a));
504 $a_filter = ' AND rd.audience IN ('.join(',',map{'?'}@aud).')';
508 if (my $l = $args{language}) {
509 $l = [$l] if (!ref($l));
512 $l_filter = ' AND rd.item_lang IN ('.join(',',map{'?'}@lang).')';
516 if (my $f = $args{lit_form}) {
517 $f = [$f] if (!ref($f));
520 $lf_filter = ' AND rd.lit_form IN ('.join(',',map{'?'}@lit_form).')';
521 push @binds, @lit_form;
524 if (my $f = $args{item_form}) {
525 $f = [$f] if (!ref($f));
528 $f_filter = ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
532 if (my $t = $args{item_type}) {
533 $t = [$t] if (!ref($t));
536 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
542 my ($t, $f) = split '-', $args{format};
543 my @types = split '', $t;
544 my @forms = split '', $f;
546 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
550 $f_filter .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
552 push @binds, @types, @forms;
555 my $relevance = 'sum(f.sum)';
556 $relevance = 1 if (!$copies_visible);
558 my $rank = $relevance;
559 if (lc($sort) eq 'pubdate') {
562 SELECT COALESCE(SUBSTRING(frp.value FROM E'\\\\d+'),'9999')::INT
563 FROM $metabib_full_rec frp
564 WHERE frp.record = f.record
566 AND frp.subfield = 'c'
570 } elsif (lc($sort) eq 'create_date') {
572 ( FIRST (( SELECT create_date FROM $br_table rbr WHERE rbr.id = f.record)) )
574 } elsif (lc($sort) eq 'edit_date') {
576 ( FIRST (( SELECT edit_date FROM $br_table rbr WHERE rbr.id = f.record)) )
578 } elsif (lc($sort) eq 'title') {
581 SELECT COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'zzzzzzzz')
582 FROM $metabib_full_rec frt
583 WHERE frt.record = f.record
585 AND frt.subfield = 'a'
589 } elsif (lc($sort) eq 'author') {
592 SELECT COALESCE(LTRIM(fra.value),'zzzzzzzz')
593 FROM $metabib_full_rec fra
594 WHERE fra.record = f.record
595 AND fra.tag LIKE '1%'
596 AND fra.subfield = 'a'
597 ORDER BY fra.tag::text::int
606 if ($copies_visible) {
608 SELECT f.record, $relevance, count(DISTINCT cp.id), $rank
609 FROM $search_table f,
610 $asset_call_number_table cn,
611 $asset_copy_table cp,
615 $metabib_record_descriptor rd,
617 WHERE br.id = f.record
618 AND cn.record = f.record
619 AND rd.record = f.record
620 AND cp.status = cs.id
621 AND cp.location = cl.id
622 AND br.deleted IS FALSE
623 AND cn.deleted IS FALSE
624 AND cp.deleted IS FALSE
633 GROUP BY f.record HAVING count(DISTINCT cp.id) > 0
634 ORDER BY 4 $sort_dir,3 DESC
638 SELECT f.record, 1, 1, $rank
639 FROM $search_table f,
641 $metabib_record_descriptor rd
642 WHERE br.id = f.record
643 AND rd.record = f.record
644 AND br.deleted IS FALSE
656 $log->debug("Search SQL :: [$select]",DEBUG);
658 my $recs = metabib::full_rec->db_Main->selectall_arrayref("$select;", {}, @binds);
659 $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
662 $max = 1 if (!@$recs);
664 $max = $$_[1] if ($$_[1] > $max);
668 for my $rec (@$recs[$offset .. $offset + $limit - 1]) {
669 next unless ($$rec[0]);
670 my ($rid,$rank,$junk,$skip) = @$rec;
671 $client->respond( [$rid, sprintf('%0.3f',$rank/$max), $count] );
675 __PACKAGE__->register_method(
676 api_name => 'open-ils.storage.biblio.full_rec.multi_search',
677 method => 'biblio_multi_search_full_rec',
682 __PACKAGE__->register_method(
683 api_name => 'open-ils.storage.biblio.full_rec.multi_search.staff',
684 method => 'biblio_multi_search_full_rec',
690 sub search_full_rec {
696 my $term = $args{term};
697 my $limiters = $args{restrict};
699 my ($index_col) = metabib::full_rec->columns('FTS');
700 $index_col ||= 'value';
701 my $search_table = metabib::full_rec->table;
703 my $fts = OpenILS::Application::Storage::FTS->compile('default' => $term, 'value',"$index_col");
705 my $fts_where = $fts->sql_where_clause();
706 my @fts_ranks = $fts->fts_rank;
708 my $rank = join(' + ', @fts_ranks);
712 for my $limit (@$limiters) {
713 if ($$limit{tag} =~ /^\d+$/ and $$limit{tag} < 10) {
714 # MARC control field; mfr.subfield is NULL
715 push @wheres, "( tag = ? AND $fts_where )";
716 push @binds, $$limit{tag};
717 $log->debug("Limiting query using { tag => $$limit{tag} }", DEBUG);
719 push @wheres, "( tag = ? AND subfield LIKE ? AND $fts_where )";
720 push @binds, $$limit{tag}, $$limit{subfield};
721 $log->debug("Limiting query using { tag => $$limit{tag}, subfield => $$limit{subfield} }", DEBUG);
724 my $where = join(' OR ', @wheres);
726 my $select = "SELECT record, sum($rank) FROM $search_table WHERE $where GROUP BY 1 ORDER BY 2 DESC;";
728 $log->debug("Search SQL :: [$select]",DEBUG);
730 my $recs = metabib::full_rec->db_Main->selectall_arrayref($select, {}, @binds);
731 $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
733 $client->respond($_) for (@$recs);
736 __PACKAGE__->register_method(
737 api_name => 'open-ils.storage.direct.metabib.full_rec.search_fts.value',
738 method => 'search_full_rec',
743 __PACKAGE__->register_method(
744 api_name => 'open-ils.storage.direct.metabib.full_rec.search_fts.index_vector',
745 method => 'search_full_rec',
752 # XXX factored most of the PG dependant stuff out of here... need to find a way to do "dependants".
753 sub search_class_fts {
758 my $term = $args{term};
759 my $ou = $args{org_unit};
760 my $ou_type = $args{depth};
761 my $limit = $args{limit};
762 my $offset = $args{offset};
764 my $limit_clause = '';
765 my $offset_clause = '';
767 $limit_clause = "LIMIT $limit" if (defined $limit and int($limit) > 0);
768 $offset_clause = "OFFSET $offset" if (defined $offset and int($offset) > 0);
771 my ($t_filter, $f_filter) = ('','');
774 my ($t, $f) = split '-', $args{format};
775 @types = split '', $t;
776 @forms = split '', $f;
778 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
782 $f_filter .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
788 my $descendants = defined($ou_type) ?
789 "actor.org_unit_descendants($ou, $ou_type)" :
790 "actor.org_unit_descendants($ou)";
792 my $class = $self->{cdbi};
793 my $search_table = $class->table;
795 my $metabib_record_descriptor = metabib::record_descriptor->table;
796 my $metabib_metarecord = metabib::metarecord->table;
797 my $metabib_metarecord_source_map_table = metabib::metarecord_source_map->table;
798 my $asset_call_number_table = asset::call_number->table;
799 my $asset_copy_table = asset::copy->table;
800 my $cs_table = config::copy_status->table;
801 my $cl_table = asset::copy_location->table;
803 my ($index_col) = $class->columns('FTS');
804 $index_col ||= 'value';
806 (my $search_class = $self->api_name) =~ s/.*metabib.(\w+).search_fts.*/$1/o;
807 my $fts = OpenILS::Application::Storage::FTS->compile($search_class => $term, 'f.value', "f.$index_col");
809 my $fts_where = $fts->sql_where_clause;
810 my @fts_ranks = $fts->fts_rank;
812 my $rank = join(' + ', @fts_ranks);
814 my $has_vols = 'AND cn.owning_lib = d.id';
815 my $has_copies = 'AND cp.call_number = cn.id';
816 my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.opac_visible IS TRUE AND cl.opac_visible IS TRUE';
818 my $visible_count = ', count(DISTINCT cp.id)';
819 my $visible_count_test = 'HAVING count(DISTINCT cp.id) > 0';
821 if ($self->api_name =~ /staff/o) {
822 $copies_visible = '';
823 $visible_count_test = '';
824 $has_copies = '' if ($ou_type == 0);
825 $has_vols = '' if ($ou_type == 0);
828 my $rank_calc = <<" RANK";
830 * CASE WHEN f.value ILIKE ? THEN 1.2 ELSE 1 END -- phrase order
831 * CASE WHEN f.value ILIKE ? THEN 1.5 ELSE 1 END -- first word match
832 * CASE WHEN f.value ~* ? THEN 2 ELSE 1 END -- only word match
833 )/COUNT(m.source)), MIN(COALESCE(CHAR_LENGTH(f.value),1))
836 $rank_calc = ',1 , 1' if ($self->api_name =~ /unordered/o);
838 if ($copies_visible) {
840 SELECT m.metarecord $rank_calc $visible_count, CASE WHEN COUNT(DISTINCT m.source) = 1 THEN MAX(m.source) ELSE MAX(0) END
841 FROM $search_table f,
842 $metabib_metarecord_source_map_table m,
843 $asset_call_number_table cn,
844 $asset_copy_table cp,
847 $metabib_record_descriptor rd,
850 AND m.source = f.source
851 AND cn.record = m.source
852 AND rd.record = m.source
853 AND cp.status = cs.id
854 AND cp.location = cl.id
860 GROUP BY 1 $visible_count_test
862 $limit_clause $offset_clause
866 SELECT m.metarecord $rank_calc, 0, CASE WHEN COUNT(DISTINCT m.source) = 1 THEN MAX(m.source) ELSE MAX(0) END
867 FROM $search_table f,
868 $metabib_metarecord_source_map_table m,
869 $metabib_record_descriptor rd
871 AND m.source = f.source
872 AND rd.record = m.source
877 $limit_clause $offset_clause
881 $log->debug("Field Search SQL :: [$select]",DEBUG);
883 my $SQLstring = join('%',$fts->words);
884 my $REstring = join('\\s+',$fts->words);
885 my $first_word = ($fts->words)[0].'%';
886 my $recs = ($self->api_name =~ /unordered/o) ?
887 $class->db_Main->selectall_arrayref($select, {}, @types, @forms) :
888 $class->db_Main->selectall_arrayref($select, {},
889 '%'.lc($SQLstring).'%', # phrase order match
890 lc($first_word), # first word match
891 '^\\s*'.lc($REstring).'\\s*/?\s*$', # full exact match
895 $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
897 $client->respond($_) for (map { [@$_[0,1,3,4]] } @$recs);
901 for my $class ( qw/title author subject keyword series identifier/ ) {
902 __PACKAGE__->register_method(
903 api_name => "open-ils.storage.metabib.$class.search_fts.metarecord",
904 method => 'search_class_fts',
907 cdbi => "metabib::${class}_field_entry",
910 __PACKAGE__->register_method(
911 api_name => "open-ils.storage.metabib.$class.search_fts.metarecord.unordered",
912 method => 'search_class_fts',
915 cdbi => "metabib::${class}_field_entry",
918 __PACKAGE__->register_method(
919 api_name => "open-ils.storage.metabib.$class.search_fts.metarecord.staff",
920 method => 'search_class_fts',
923 cdbi => "metabib::${class}_field_entry",
926 __PACKAGE__->register_method(
927 api_name => "open-ils.storage.metabib.$class.search_fts.metarecord.staff.unordered",
928 method => 'search_class_fts',
931 cdbi => "metabib::${class}_field_entry",
936 # XXX factored most of the PG dependant stuff out of here... need to find a way to do "dependants".
937 sub search_class_fts_count {
942 my $term = $args{term};
943 my $ou = $args{org_unit};
944 my $ou_type = $args{depth};
945 my $limit = $args{limit} || 100;
946 my $offset = $args{offset} || 0;
948 my $descendants = defined($ou_type) ?
949 "actor.org_unit_descendants($ou, $ou_type)" :
950 "actor.org_unit_descendants($ou)";
953 my ($t_filter, $f_filter) = ('','');
956 my ($t, $f) = split '-', $args{format};
957 @types = split '', $t;
958 @forms = split '', $f;
960 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
964 $f_filter .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
969 (my $search_class = $self->api_name) =~ s/.*metabib.(\w+).search_fts.*/$1/o;
971 my $class = $self->{cdbi};
972 my $search_table = $class->table;
974 my $metabib_record_descriptor = metabib::record_descriptor->table;
975 my $metabib_metarecord_source_map_table = metabib::metarecord_source_map->table;
976 my $asset_call_number_table = asset::call_number->table;
977 my $asset_copy_table = asset::copy->table;
978 my $cs_table = config::copy_status->table;
979 my $cl_table = asset::copy_location->table;
981 my ($index_col) = $class->columns('FTS');
982 $index_col ||= 'value';
984 my $fts = OpenILS::Application::Storage::FTS->compile($search_class => $term, 'value',"$index_col");
986 my $fts_where = $fts->sql_where_clause;
988 my $has_vols = 'AND cn.owning_lib = d.id';
989 my $has_copies = 'AND cp.call_number = cn.id';
990 my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.opac_visible IS TRUE AND cl.opac_visible IS TRUE';
991 if ($self->api_name =~ /staff/o) {
992 $copies_visible = '';
993 $has_vols = '' if ($ou_type == 0);
994 $has_copies = '' if ($ou_type == 0);
997 # XXX test an "EXISTS version of descendant checking...
999 if ($copies_visible) {
1001 SELECT count(distinct m.metarecord)
1002 FROM $search_table f,
1003 $metabib_metarecord_source_map_table m,
1004 $metabib_metarecord_source_map_table mr,
1005 $asset_call_number_table cn,
1006 $asset_copy_table cp,
1009 $metabib_record_descriptor rd,
1012 AND mr.source = f.source
1013 AND mr.metarecord = m.metarecord
1014 AND cn.record = m.source
1015 AND rd.record = m.source
1016 AND cp.status = cs.id
1017 AND cp.location = cl.id
1026 SELECT count(distinct m.metarecord)
1027 FROM $search_table f,
1028 $metabib_metarecord_source_map_table m,
1029 $metabib_metarecord_source_map_table mr,
1030 $metabib_record_descriptor rd
1032 AND mr.source = f.source
1033 AND mr.metarecord = m.metarecord
1034 AND rd.record = m.source
1040 $log->debug("Field Search Count SQL :: [$select]",DEBUG);
1042 my $recs = $class->db_Main->selectrow_arrayref($select, {}, @types, @forms)->[0];
1044 $log->debug("Count Search yielded $recs results.",DEBUG);
1049 for my $class ( qw/title author subject keyword series identifier/ ) {
1050 __PACKAGE__->register_method(
1051 api_name => "open-ils.storage.metabib.$class.search_fts.metarecord_count",
1052 method => 'search_class_fts_count',
1055 cdbi => "metabib::${class}_field_entry",
1058 __PACKAGE__->register_method(
1059 api_name => "open-ils.storage.metabib.$class.search_fts.metarecord_count.staff",
1060 method => 'search_class_fts_count',
1063 cdbi => "metabib::${class}_field_entry",
1069 # XXX factored most of the PG dependant stuff out of here... need to find a way to do "dependants".
1070 sub postfilter_search_class_fts {
1075 my $term = $args{term};
1076 my $sort = $args{'sort'};
1077 my $sort_dir = $args{sort_dir} || 'DESC';
1078 my $ou = $args{org_unit};
1079 my $ou_type = $args{depth};
1080 my $limit = $args{limit} || 10;
1081 my $visibility_limit = $args{visibility_limit} || 5000;
1082 my $offset = $args{offset} || 0;
1084 my $outer_limit = 1000;
1086 my $limit_clause = '';
1087 my $offset_clause = '';
1089 $limit_clause = "LIMIT $outer_limit";
1090 $offset_clause = "OFFSET $offset" if (defined $offset and int($offset) > 0);
1092 my (@types,@forms,@lang,@aud,@lit_form);
1093 my ($t_filter, $f_filter) = ('','');
1094 my ($a_filter, $l_filter, $lf_filter) = ('','','');
1095 my ($ot_filter, $of_filter) = ('','');
1096 my ($oa_filter, $ol_filter, $olf_filter) = ('','','');
1098 if (my $a = $args{audience}) {
1099 $a = [$a] if (!ref($a));
1102 $a_filter = ' AND rd.audience IN ('.join(',',map{'?'}@aud).')';
1103 $oa_filter = ' AND ord.audience IN ('.join(',',map{'?'}@aud).')';
1106 if (my $l = $args{language}) {
1107 $l = [$l] if (!ref($l));
1110 $l_filter = ' AND rd.item_lang IN ('.join(',',map{'?'}@lang).')';
1111 $ol_filter = ' AND ord.item_lang IN ('.join(',',map{'?'}@lang).')';
1114 if (my $f = $args{lit_form}) {
1115 $f = [$f] if (!ref($f));
1118 $lf_filter = ' AND rd.lit_form IN ('.join(',',map{'?'}@lit_form).')';
1119 $olf_filter = ' AND ord.lit_form IN ('.join(',',map{'?'}@lit_form).')';
1122 if ($args{format}) {
1123 my ($t, $f) = split '-', $args{format};
1124 @types = split '', $t;
1125 @forms = split '', $f;
1127 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
1128 $ot_filter = ' AND ord.item_type IN ('.join(',',map{'?'}@types).')';
1132 $f_filter .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
1133 $of_filter .= ' AND ord.item_form IN ('.join(',',map{'?'}@forms).')';
1138 my $descendants = defined($ou_type) ?
1139 "actor.org_unit_descendants($ou, $ou_type)" :
1140 "actor.org_unit_descendants($ou)";
1142 my $class = $self->{cdbi};
1143 my $search_table = $class->table;
1145 my $metabib_full_rec = metabib::full_rec->table;
1146 my $metabib_record_descriptor = metabib::record_descriptor->table;
1147 my $metabib_metarecord = metabib::metarecord->table;
1148 my $metabib_metarecord_source_map_table = metabib::metarecord_source_map->table;
1149 my $asset_call_number_table = asset::call_number->table;
1150 my $asset_copy_table = asset::copy->table;
1151 my $cs_table = config::copy_status->table;
1152 my $cl_table = asset::copy_location->table;
1153 my $br_table = biblio::record_entry->table;
1155 my ($index_col) = $class->columns('FTS');
1156 $index_col ||= 'value';
1158 (my $search_class = $self->api_name) =~ s/.*metabib.(\w+).post_filter.*/$1/o;
1160 my $fts = OpenILS::Application::Storage::FTS->compile($search_class => $term, 'f.value', "f.$index_col");
1162 my $SQLstring = join('%',map { lc($_) } $fts->words);
1163 my $REstring = '^' . join('\s+',map { lc($_) } $fts->words) . '\W*$';
1164 my $first_word = lc(($fts->words)[0]).'%';
1166 my $fts_where = $fts->sql_where_clause;
1167 my @fts_ranks = $fts->fts_rank;
1170 $bonus{'metabib::identifier_field_entry'} =
1171 $bonus{'metabib::keyword_field_entry'} = [
1172 { 'CASE WHEN f.value ILIKE ? THEN 1.2 ELSE 1 END' => $SQLstring }
1175 $bonus{'metabib::title_field_entry'} =
1176 $bonus{'metabib::series_field_entry'} = [
1177 { 'CASE WHEN f.value ILIKE ? THEN 1.5 ELSE 1 END' => $first_word },
1178 { 'CASE WHEN f.value ~* ? THEN 2 ELSE 1 END' => $REstring },
1179 @{ $bonus{'metabib::keyword_field_entry'} }
1182 my $bonus_list = join ' * ', map { keys %$_ } @{ $bonus{$class} };
1183 $bonus_list ||= '1';
1185 my @bonus_values = map { values %$_ } @{ $bonus{$class} };
1187 my $relevance = join(' + ', @fts_ranks);
1188 $relevance = <<" RANK";
1189 (SUM( ( $relevance ) * ( $bonus_list ) )/COUNT(m.source))
1192 my $string_default_sort = 'zzzz';
1193 $string_default_sort = 'AAAA' if ($sort_dir eq 'DESC');
1195 my $number_default_sort = '9999';
1196 $number_default_sort = '0000' if ($sort_dir eq 'DESC');
1198 my $rank = $relevance;
1199 if (lc($sort) eq 'pubdate') {
1202 SELECT COALESCE(SUBSTRING(frp.value FROM E'\\\\d+'),'$number_default_sort')::INT
1203 FROM $metabib_full_rec frp
1204 WHERE frp.record = mr.master_record
1206 AND frp.subfield = 'c'
1210 } elsif (lc($sort) eq 'create_date') {
1212 ( FIRST (( SELECT create_date FROM $br_table rbr WHERE rbr.id = mr.master_record)) )
1214 } elsif (lc($sort) eq 'edit_date') {
1216 ( FIRST (( SELECT edit_date FROM $br_table rbr WHERE rbr.id = mr.master_record)) )
1218 } elsif (lc($sort) eq 'title') {
1221 SELECT COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
1222 FROM $metabib_full_rec frt
1223 WHERE frt.record = mr.master_record
1225 AND frt.subfield = 'a'
1229 } elsif (lc($sort) eq 'author') {
1232 SELECT COALESCE(LTRIM(fra.value),'$string_default_sort')
1233 FROM $metabib_full_rec fra
1234 WHERE fra.record = mr.master_record
1235 AND fra.tag LIKE '1%'
1236 AND fra.subfield = 'a'
1237 ORDER BY fra.tag::text::int
1245 my $select = <<" SQL";
1246 SELECT m.metarecord,
1248 CASE WHEN COUNT(DISTINCT smrs.source) = 1 THEN MIN(m.source) ELSE 0 END,
1250 FROM $search_table f,
1251 $metabib_metarecord_source_map_table m,
1252 $metabib_metarecord_source_map_table smrs,
1253 $metabib_metarecord mr,
1254 $metabib_record_descriptor rd
1256 AND smrs.metarecord = mr.id
1257 AND m.source = f.source
1258 AND m.metarecord = mr.id
1259 AND rd.record = smrs.source
1265 GROUP BY m.metarecord
1266 ORDER BY 4 $sort_dir, MIN(COALESCE(CHAR_LENGTH(f.value),1))
1267 LIMIT $visibility_limit
1274 FROM $asset_call_number_table cn,
1275 $metabib_metarecord_source_map_table mrs,
1276 $asset_copy_table cp,
1281 $metabib_record_descriptor ord,
1283 WHERE mrs.metarecord = s.metarecord
1284 AND br.id = mrs.source
1285 AND cn.record = mrs.source
1286 AND cp.status = cs.id
1287 AND cp.location = cl.id
1288 AND cn.owning_lib = d.id
1289 AND cp.call_number = cn.id
1290 AND cp.opac_visible IS TRUE
1291 AND cs.opac_visible IS TRUE
1292 AND cl.opac_visible IS TRUE
1293 AND d.opac_visible IS TRUE
1294 AND br.active IS TRUE
1295 AND br.deleted IS FALSE
1296 AND ord.record = mrs.source
1302 ORDER BY 4 $sort_dir
1304 } elsif ($self->api_name !~ /staff/o) {
1311 FROM $asset_call_number_table cn,
1312 $metabib_metarecord_source_map_table mrs,
1313 $asset_copy_table cp,
1318 $metabib_record_descriptor ord
1320 WHERE mrs.metarecord = s.metarecord
1321 AND br.id = mrs.source
1322 AND cn.record = mrs.source
1323 AND cp.status = cs.id
1324 AND cp.location = cl.id
1325 AND cp.circ_lib = d.id
1326 AND cp.call_number = cn.id
1327 AND cp.opac_visible IS TRUE
1328 AND cs.opac_visible IS TRUE
1329 AND cl.opac_visible IS TRUE
1330 AND d.opac_visible IS TRUE
1331 AND br.active IS TRUE
1332 AND br.deleted IS FALSE
1333 AND ord.record = mrs.source
1341 ORDER BY 4 $sort_dir
1350 FROM $asset_call_number_table cn,
1351 $asset_copy_table cp,
1352 $metabib_metarecord_source_map_table mrs,
1355 $metabib_record_descriptor ord
1357 WHERE mrs.metarecord = s.metarecord
1358 AND br.id = mrs.source
1359 AND cn.record = mrs.source
1360 AND cn.id = cp.call_number
1361 AND br.deleted IS FALSE
1362 AND cn.deleted IS FALSE
1363 AND ord.record = mrs.source
1364 AND ( cn.owning_lib = d.id
1365 OR ( cp.circ_lib = d.id
1366 AND cp.deleted IS FALSE
1378 FROM $asset_call_number_table cn,
1379 $metabib_metarecord_source_map_table mrs,
1380 $metabib_record_descriptor ord
1381 WHERE mrs.metarecord = s.metarecord
1382 AND cn.record = mrs.source
1383 AND ord.record = mrs.source
1391 ORDER BY 4 $sort_dir
1396 $log->debug("Field Search SQL :: [$select]",DEBUG);
1398 my $recs = $class->db_Main->selectall_arrayref(
1400 (@bonus_values > 0 ? @bonus_values : () ),
1401 ( (!$sort && @bonus_values > 0) ? @bonus_values : () ),
1402 @types, @forms, @aud, @lang, @lit_form,
1403 @types, @forms, @aud, @lang, @lit_form,
1404 ($self->api_name =~ /staff/o ? (@types, @forms, @aud, @lang, @lit_form) : () ) );
1406 $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
1409 $max = 1 if (!@$recs);
1411 $max = $$_[1] if ($$_[1] > $max);
1414 my $count = scalar(@$recs);
1415 for my $rec (@$recs[$offset .. $offset + $limit - 1]) {
1416 my ($mrid,$rank,$skip) = @$rec;
1417 $client->respond( [$mrid, sprintf('%0.3f',$rank/$max), $skip, $count] );
1422 for my $class ( qw/title author subject keyword series identifier/ ) {
1423 __PACKAGE__->register_method(
1424 api_name => "open-ils.storage.metabib.$class.post_filter.search_fts.metarecord",
1425 method => 'postfilter_search_class_fts',
1428 cdbi => "metabib::${class}_field_entry",
1431 __PACKAGE__->register_method(
1432 api_name => "open-ils.storage.metabib.$class.post_filter.search_fts.metarecord.staff",
1433 method => 'postfilter_search_class_fts',
1436 cdbi => "metabib::${class}_field_entry",
1443 my $_cdbi = { title => "metabib::title_field_entry",
1444 author => "metabib::author_field_entry",
1445 subject => "metabib::subject_field_entry",
1446 keyword => "metabib::keyword_field_entry",
1447 series => "metabib::series_field_entry",
1448 identifier => "metabib::identifier_field_entry",
1451 # XXX factored most of the PG dependant stuff out of here... need to find a way to do "dependants".
1452 sub postfilter_search_multi_class_fts {
1457 my $sort = $args{'sort'};
1458 my $sort_dir = $args{sort_dir} || 'DESC';
1459 my $ou = $args{org_unit};
1460 my $ou_type = $args{depth};
1461 my $limit = $args{limit} || 10;
1462 my $offset = $args{offset} || 0;
1463 my $visibility_limit = $args{visibility_limit} || 5000;
1466 $ou = actor::org_unit->search( { parent_ou => undef } )->next->id;
1469 if (!defined($args{org_unit})) {
1470 die "No target organizational unit passed to ".$self->api_name;
1473 if (! scalar( keys %{$args{searches}} )) {
1474 die "No search arguments were passed to ".$self->api_name;
1477 my $outer_limit = 1000;
1479 my $limit_clause = '';
1480 my $offset_clause = '';
1482 $limit_clause = "LIMIT $outer_limit";
1483 $offset_clause = "OFFSET $offset" if (defined $offset and int($offset) > 0);
1485 my ($avail_filter,@types,@forms,@lang,@aud,@lit_form,@vformats) = ('');
1486 my ($t_filter, $f_filter, $v_filter) = ('','','');
1487 my ($a_filter, $l_filter, $lf_filter) = ('','','');
1488 my ($ot_filter, $of_filter, $ov_filter) = ('','','');
1489 my ($oa_filter, $ol_filter, $olf_filter) = ('','','');
1491 if ($args{available}) {
1492 $avail_filter = ' AND cp.status IN (0,7,12)';
1495 if (my $a = $args{audience}) {
1496 $a = [$a] if (!ref($a));
1499 $a_filter = ' AND rd.audience IN ('.join(',',map{'?'}@aud).')';
1500 $oa_filter = ' AND ord.audience IN ('.join(',',map{'?'}@aud).')';
1503 if (my $l = $args{language}) {
1504 $l = [$l] if (!ref($l));
1507 $l_filter = ' AND rd.item_lang IN ('.join(',',map{'?'}@lang).')';
1508 $ol_filter = ' AND ord.item_lang IN ('.join(',',map{'?'}@lang).')';
1511 if (my $f = $args{lit_form}) {
1512 $f = [$f] if (!ref($f));
1515 $lf_filter = ' AND rd.lit_form IN ('.join(',',map{'?'}@lit_form).')';
1516 $olf_filter = ' AND ord.lit_form IN ('.join(',',map{'?'}@lit_form).')';
1519 if (my $f = $args{item_form}) {
1520 $f = [$f] if (!ref($f));
1523 $f_filter = ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
1524 $of_filter = ' AND ord.item_form IN ('.join(',',map{'?'}@forms).')';
1527 if (my $t = $args{item_type}) {
1528 $t = [$t] if (!ref($t));
1531 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
1532 $ot_filter = ' AND ord.item_type IN ('.join(',',map{'?'}@types).')';
1535 if (my $v = $args{vr_format}) {
1536 $v = [$v] if (!ref($v));
1539 $v_filter = ' AND rd.vr_format IN ('.join(',',map{'?'}@vformats).')';
1540 $ov_filter = ' AND ord.vr_format IN ('.join(',',map{'?'}@vformats).')';
1544 # XXX legacy format and item type support
1545 if ($args{format}) {
1546 my ($t, $f) = split '-', $args{format};
1547 @types = split '', $t;
1548 @forms = split '', $f;
1550 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
1551 $ot_filter = ' AND ord.item_type IN ('.join(',',map{'?'}@types).')';
1555 $f_filter .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
1556 $of_filter .= ' AND ord.item_form IN ('.join(',',map{'?'}@forms).')';
1562 my $descendants = defined($ou_type) ?
1563 "actor.org_unit_descendants($ou, $ou_type)" :
1564 "actor.org_unit_descendants($ou)";
1566 my $search_table_list = '';
1568 my $join_table_list = '';
1571 my $field_table = config::metabib_field->table;
1575 my $prev_search_group;
1576 my $curr_search_group;
1580 for my $search_group (sort keys %{$args{searches}}) {
1581 (my $search_group_name = $search_group) =~ s/\|/_/gso;
1582 ($search_class,$search_field) = split /\|/, $search_group;
1583 $log->debug("Searching class [$search_class] and field [$search_field]",DEBUG);
1585 if ($search_field) {
1586 unless ( $metabib_field = config::metabib_field->search( field_class => $search_class, name => $search_field )->next ) {
1587 $log->warn("Requested class [$search_class] or field [$search_field] does not exist!");
1592 $prev_search_group = $curr_search_group if ($curr_search_group);
1594 $curr_search_group = $search_group_name;
1596 my $class = $_cdbi->{$search_class};
1597 my $search_table = $class->table;
1599 my ($index_col) = $class->columns('FTS');
1600 $index_col ||= 'value';
1603 my $fts = OpenILS::Application::Storage::FTS->compile($search_class => $args{searches}{$search_group}{term}, $search_group_name.'.value', "$search_group_name.$index_col");
1605 my $fts_where = $fts->sql_where_clause;
1606 my @fts_ranks = $fts->fts_rank;
1608 my $SQLstring = join('%',map { lc($_) } $fts->words);
1609 my $REstring = '^' . join('\s+',map { lc($_) } $fts->words) . '\W*$';
1610 my $first_word = lc(($fts->words)[0]).'%';
1612 $_.=" * (SELECT weight FROM $field_table WHERE $search_group_name.field = id)" for (@fts_ranks);
1613 my $rank = join(' + ', @fts_ranks);
1616 $bonus{'keyword'} = [ { "CASE WHEN $search_group_name.value LIKE ? THEN 10 ELSE 1 END" => $SQLstring } ];
1617 $bonus{'author'} = [ { "CASE WHEN $search_group_name.value ILIKE ? THEN 10 ELSE 1 END" => $first_word } ];
1619 $bonus{'series'} = [
1620 { "CASE WHEN $search_group_name.value LIKE ? THEN 1.5 ELSE 1 END" => $first_word },
1621 { "CASE WHEN $search_group_name.value ~ ? THEN 20 ELSE 1 END" => $REstring },
1624 $bonus{'title'} = [ @{ $bonus{'series'} }, @{ $bonus{'keyword'} } ];
1626 my $bonus_list = join ' * ', map { keys %$_ } @{ $bonus{$search_class} };
1627 $bonus_list ||= '1';
1629 push @bonus_lists, $bonus_list;
1630 push @bonus_values, map { values %$_ } @{ $bonus{$search_class} };
1633 #---------------------
1635 $search_table_list .= "$search_table $search_group_name, ";
1636 push @rank_list,$rank;
1637 $fts_list .= " AND $fts_where AND m.source = $search_group_name.source";
1639 if ($metabib_field) {
1640 $join_table_list .= " AND $search_group_name.field = " . $metabib_field->id;
1641 $metabib_field = undef;
1644 if ($prev_search_group) {
1645 $join_table_list .= " AND $prev_search_group.source = $curr_search_group.source";
1649 my $metabib_record_descriptor = metabib::record_descriptor->table;
1650 my $metabib_full_rec = metabib::full_rec->table;
1651 my $metabib_metarecord = metabib::metarecord->table;
1652 my $metabib_metarecord_source_map_table = metabib::metarecord_source_map->table;
1653 my $asset_call_number_table = asset::call_number->table;
1654 my $asset_copy_table = asset::copy->table;
1655 my $cs_table = config::copy_status->table;
1656 my $cl_table = asset::copy_location->table;
1657 my $br_table = biblio::record_entry->table;
1658 my $source_table = config::bib_source->table;
1660 my $bonuses = join (' * ', @bonus_lists);
1661 my $relevance = join (' + ', @rank_list);
1662 $relevance = "SUM( ($relevance) * ($bonuses) )/COUNT(DISTINCT smrs.source)";
1664 my $string_default_sort = 'zzzz';
1665 $string_default_sort = 'AAAA' if ($sort_dir eq 'DESC');
1667 my $number_default_sort = '9999';
1668 $number_default_sort = '0000' if ($sort_dir eq 'DESC');
1672 my $secondary_sort = <<" SORT";
1674 SELECT COALESCE(LTRIM(SUBSTR( sfrt.value, COALESCE(SUBSTRING(sfrt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
1675 FROM $metabib_full_rec sfrt,
1676 $metabib_metarecord mr
1677 WHERE sfrt.record = mr.master_record
1678 AND sfrt.tag = '245'
1679 AND sfrt.subfield = 'a'
1684 my $rank = $relevance;
1685 if (lc($sort) eq 'pubdate') {
1688 SELECT COALESCE(SUBSTRING(frp.value FROM E'\\\\d+'),'$number_default_sort')::INT
1689 FROM $metabib_full_rec frp
1690 WHERE frp.record = mr.master_record
1692 AND frp.subfield = 'c'
1696 } elsif (lc($sort) eq 'create_date') {
1698 ( FIRST (( SELECT create_date FROM $br_table rbr WHERE rbr.id = mr.master_record)) )
1700 } elsif (lc($sort) eq 'edit_date') {
1702 ( FIRST (( SELECT edit_date FROM $br_table rbr WHERE rbr.id = mr.master_record)) )
1704 } elsif (lc($sort) eq 'title') {
1707 SELECT COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
1708 FROM $metabib_full_rec frt
1709 WHERE frt.record = mr.master_record
1711 AND frt.subfield = 'a'
1715 $secondary_sort = <<" SORT";
1717 SELECT COALESCE(SUBSTRING(sfrp.value FROM E'\\\\d+'),'$number_default_sort')::INT
1718 FROM $metabib_full_rec sfrp
1719 WHERE sfrp.record = mr.master_record
1720 AND sfrp.tag = '260'
1721 AND sfrp.subfield = 'c'
1725 } elsif (lc($sort) eq 'author') {
1728 SELECT COALESCE(LTRIM(fra.value),'$string_default_sort')
1729 FROM $metabib_full_rec fra
1730 WHERE fra.record = mr.master_record
1731 AND fra.tag LIKE '1%'
1732 AND fra.subfield = 'a'
1733 ORDER BY fra.tag::text::int
1738 push @bonus_values, @bonus_values;
1743 my $select = <<" SQL";
1744 SELECT m.metarecord,
1746 CASE WHEN COUNT(DISTINCT smrs.source) = 1 THEN FIRST(m.source) ELSE 0 END,
1749 FROM $search_table_list
1750 $metabib_metarecord mr,
1751 $metabib_metarecord_source_map_table m,
1752 $metabib_metarecord_source_map_table smrs
1753 WHERE m.metarecord = smrs.metarecord
1754 AND mr.id = m.metarecord
1757 GROUP BY m.metarecord
1758 -- ORDER BY 4 $sort_dir
1759 LIMIT $visibility_limit
1762 if ($self->api_name !~ /staff/o) {
1769 FROM $asset_call_number_table cn,
1770 $metabib_metarecord_source_map_table mrs,
1771 $asset_copy_table cp,
1776 $metabib_record_descriptor ord
1777 WHERE mrs.metarecord = s.metarecord
1778 AND br.id = mrs.source
1779 AND cn.record = mrs.source
1780 AND cp.status = cs.id
1781 AND cp.location = cl.id
1782 AND cp.circ_lib = d.id
1783 AND cp.call_number = cn.id
1784 AND cp.opac_visible IS TRUE
1785 AND cs.opac_visible IS TRUE
1786 AND cl.opac_visible IS TRUE
1787 AND d.opac_visible IS TRUE
1788 AND br.active IS TRUE
1789 AND br.deleted IS FALSE
1790 AND cp.deleted IS FALSE
1791 AND cn.deleted IS FALSE
1792 AND ord.record = mrs.source
1805 $metabib_metarecord_source_map_table mrs,
1806 $metabib_record_descriptor ord,
1808 WHERE mrs.metarecord = s.metarecord
1809 AND ord.record = mrs.source
1810 AND br.id = mrs.source
1811 AND br.source = src.id
1812 AND src.transcendant IS TRUE
1820 ORDER BY 4 $sort_dir, 5
1827 $metabib_metarecord_source_map_table omrs,
1828 $metabib_record_descriptor ord
1829 WHERE omrs.metarecord = s.metarecord
1830 AND ord.record = omrs.source
1833 FROM $asset_call_number_table cn,
1834 $asset_copy_table cp,
1837 WHERE br.id = omrs.source
1838 AND cn.record = omrs.source
1839 AND br.deleted IS FALSE
1840 AND cn.deleted IS FALSE
1841 AND cp.call_number = cn.id
1842 AND ( cn.owning_lib = d.id
1843 OR ( cp.circ_lib = d.id
1844 AND cp.deleted IS FALSE
1852 FROM $asset_call_number_table cn
1853 WHERE cn.record = omrs.source
1854 AND cn.deleted IS FALSE
1860 $metabib_metarecord_source_map_table mrs,
1861 $metabib_record_descriptor ord,
1863 WHERE mrs.metarecord = s.metarecord
1864 AND br.id = mrs.source
1865 AND br.source = src.id
1866 AND src.transcendant IS TRUE
1882 ORDER BY 4 $sort_dir, 5
1887 $log->debug("Field Search SQL :: [$select]",DEBUG);
1889 my $recs = $_cdbi->{title}->db_Main->selectall_arrayref(
1892 @types, @forms, @vformats, @aud, @lang, @lit_form,
1893 @types, @forms, @vformats, @aud, @lang, @lit_form,
1894 # ($self->api_name =~ /staff/o ? (@types, @forms, @aud, @lang, @lit_form) : () )
1897 $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
1900 $max = 1 if (!@$recs);
1902 $max = $$_[1] if ($$_[1] > $max);
1905 my $count = scalar(@$recs);
1906 for my $rec (@$recs[$offset .. $offset + $limit - 1]) {
1907 next unless ($$rec[0]);
1908 my ($mrid,$rank,$skip) = @$rec;
1909 $client->respond( [$mrid, sprintf('%0.3f',$rank/$max), $skip, $count] );
1914 __PACKAGE__->register_method(
1915 api_name => "open-ils.storage.metabib.post_filter.multiclass.search_fts.metarecord",
1916 method => 'postfilter_search_multi_class_fts',
1921 __PACKAGE__->register_method(
1922 api_name => "open-ils.storage.metabib.post_filter.multiclass.search_fts.metarecord.staff",
1923 method => 'postfilter_search_multi_class_fts',
1929 __PACKAGE__->register_method(
1930 api_name => "open-ils.storage.metabib.multiclass.search_fts",
1931 method => 'postfilter_search_multi_class_fts',
1936 __PACKAGE__->register_method(
1937 api_name => "open-ils.storage.metabib.multiclass.search_fts.staff",
1938 method => 'postfilter_search_multi_class_fts',
1944 # XXX factored most of the PG dependant stuff out of here... need to find a way to do "dependants".
1945 sub biblio_search_multi_class_fts {
1950 my $sort = $args{'sort'};
1951 my $sort_dir = $args{sort_dir} || 'DESC';
1952 my $ou = $args{org_unit};
1953 my $ou_type = $args{depth};
1954 my $limit = $args{limit} || 10;
1955 my $offset = $args{offset} || 0;
1956 my $pref_lang = $args{preferred_language} || 'eng';
1957 my $visibility_limit = $args{visibility_limit} || 5000;
1960 $ou = actor::org_unit->search( { parent_ou => undef } )->next->id;
1963 if (! scalar( keys %{$args{searches}} )) {
1964 die "No search arguments were passed to ".$self->api_name;
1967 my $outer_limit = 1000;
1969 my $limit_clause = '';
1970 my $offset_clause = '';
1972 $limit_clause = "LIMIT $outer_limit";
1973 $offset_clause = "OFFSET $offset" if (defined $offset and int($offset) > 0);
1975 my ($avail_filter,@types,@forms,@lang,@aud,@lit_form,@vformats) = ('');
1976 my ($t_filter, $f_filter, $v_filter) = ('','','');
1977 my ($a_filter, $l_filter, $lf_filter) = ('','','');
1978 my ($ot_filter, $of_filter, $ov_filter) = ('','','');
1979 my ($oa_filter, $ol_filter, $olf_filter) = ('','','');
1981 if ($args{available}) {
1982 $avail_filter = ' AND cp.status IN (0,7,12)';
1985 if (my $a = $args{audience}) {
1986 $a = [$a] if (!ref($a));
1989 $a_filter = ' AND rd.audience IN ('.join(',',map{'?'}@aud).')';
1990 $oa_filter = ' AND ord.audience IN ('.join(',',map{'?'}@aud).')';
1993 if (my $l = $args{language}) {
1994 $l = [$l] if (!ref($l));
1997 $l_filter = ' AND rd.item_lang IN ('.join(',',map{'?'}@lang).')';
1998 $ol_filter = ' AND ord.item_lang IN ('.join(',',map{'?'}@lang).')';
2001 if (my $f = $args{lit_form}) {
2002 $f = [$f] if (!ref($f));
2005 $lf_filter = ' AND rd.lit_form IN ('.join(',',map{'?'}@lit_form).')';
2006 $olf_filter = ' AND ord.lit_form IN ('.join(',',map{'?'}@lit_form).')';
2009 if (my $f = $args{item_form}) {
2010 $f = [$f] if (!ref($f));
2013 $f_filter = ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
2014 $of_filter = ' AND ord.item_form IN ('.join(',',map{'?'}@forms).')';
2017 if (my $t = $args{item_type}) {
2018 $t = [$t] if (!ref($t));
2021 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
2022 $ot_filter = ' AND ord.item_type IN ('.join(',',map{'?'}@types).')';
2025 if (my $v = $args{vr_format}) {
2026 $v = [$v] if (!ref($v));
2029 $v_filter = ' AND rd.vr_format IN ('.join(',',map{'?'}@vformats).')';
2030 $ov_filter = ' AND ord.vr_format IN ('.join(',',map{'?'}@vformats).')';
2033 # XXX legacy format and item type support
2034 if ($args{format}) {
2035 my ($t, $f) = split '-', $args{format};
2036 @types = split '', $t;
2037 @forms = split '', $f;
2039 $t_filter = ' AND rd.item_type IN ('.join(',',map{'?'}@types).')';
2040 $ot_filter = ' AND ord.item_type IN ('.join(',',map{'?'}@types).')';
2044 $f_filter .= ' AND rd.item_form IN ('.join(',',map{'?'}@forms).')';
2045 $of_filter .= ' AND ord.item_form IN ('.join(',',map{'?'}@forms).')';
2050 my $descendants = defined($ou_type) ?
2051 "actor.org_unit_descendants($ou, $ou_type)" :
2052 "actor.org_unit_descendants($ou)";
2054 my $search_table_list = '';
2056 my $join_table_list = '';
2059 my $field_table = config::metabib_field->table;
2063 my $prev_search_group;
2064 my $curr_search_group;
2068 for my $search_group (sort keys %{$args{searches}}) {
2069 (my $search_group_name = $search_group) =~ s/\|/_/gso;
2070 ($search_class,$search_field) = split /\|/, $search_group;
2071 $log->debug("Searching class [$search_class] and field [$search_field]",DEBUG);
2073 if ($search_field) {
2074 unless ( $metabib_field = config::metabib_field->search( field_class => $search_class, name => $search_field )->next ) {
2075 $log->warn("Requested class [$search_class] or field [$search_field] does not exist!");
2080 $prev_search_group = $curr_search_group if ($curr_search_group);
2082 $curr_search_group = $search_group_name;
2084 my $class = $_cdbi->{$search_class};
2085 my $search_table = $class->table;
2087 my ($index_col) = $class->columns('FTS');
2088 $index_col ||= 'value';
2091 my $fts = OpenILS::Application::Storage::FTS->compile($search_class => $args{searches}{$search_group}{term}, $search_group_name.'.value', "$search_group_name.$index_col");
2093 my $fts_where = $fts->sql_where_clause;
2094 my @fts_ranks = $fts->fts_rank;
2096 my $SQLstring = join('%',map { lc($_) } $fts->words) .'%';
2097 my $REstring = '^' . join('\s+',map { lc($_) } $fts->words) . '\W*$';
2098 my $first_word = lc(($fts->words)[0]).'%';
2100 $_.=" * (SELECT weight FROM $field_table WHERE $search_group_name.field = id)" for (@fts_ranks);
2101 my $rank = join(' + ', @fts_ranks);
2104 $bonus{'subject'} = [];
2105 $bonus{'author'} = [ { "CASE WHEN $search_group_name.value ILIKE ? THEN 1.5 ELSE 1 END" => $first_word } ];
2107 $bonus{'keyword'} = [ { "CASE WHEN $search_group_name.value ILIKE ? THEN 10 ELSE 1 END" => $SQLstring } ];
2109 $bonus{'series'} = [
2110 { "CASE WHEN $search_group_name.value ILIKE ? THEN 1.5 ELSE 1 END" => $first_word },
2111 { "CASE WHEN $search_group_name.value ~ ? THEN 20 ELSE 1 END" => $REstring },
2114 $bonus{'title'} = [ @{ $bonus{'series'} }, @{ $bonus{'keyword'} } ];
2117 push @{ $bonus{'title'} }, { "CASE WHEN rd.item_lang = ? THEN 10 ELSE 1 END" => $pref_lang };
2118 push @{ $bonus{'author'} }, { "CASE WHEN rd.item_lang = ? THEN 10 ELSE 1 END" => $pref_lang };
2119 push @{ $bonus{'subject'} }, { "CASE WHEN rd.item_lang = ? THEN 10 ELSE 1 END" => $pref_lang };
2120 push @{ $bonus{'keyword'} }, { "CASE WHEN rd.item_lang = ? THEN 10 ELSE 1 END" => $pref_lang };
2121 push @{ $bonus{'series'} }, { "CASE WHEN rd.item_lang = ? THEN 10 ELSE 1 END" => $pref_lang };
2124 my $bonus_list = join ' * ', map { keys %$_ } @{ $bonus{$search_class} };
2125 $bonus_list ||= '1';
2127 push @bonus_lists, $bonus_list;
2128 push @bonus_values, map { values %$_ } @{ $bonus{$search_class} };
2130 #---------------------
2132 $search_table_list .= "$search_table $search_group_name, ";
2133 push @rank_list,$rank;
2134 $fts_list .= " AND $fts_where AND b.id = $search_group_name.source";
2136 if ($metabib_field) {
2137 $fts_list .= " AND $curr_search_group.field = " . $metabib_field->id;
2138 $metabib_field = undef;
2141 if ($prev_search_group) {
2142 $join_table_list .= " AND $prev_search_group.source = $curr_search_group.source";
2146 my $metabib_record_descriptor = metabib::record_descriptor->table;
2147 my $metabib_full_rec = metabib::full_rec->table;
2148 my $metabib_metarecord = metabib::metarecord->table;
2149 my $metabib_metarecord_source_map_table = metabib::metarecord_source_map->table;
2150 my $asset_call_number_table = asset::call_number->table;
2151 my $asset_copy_table = asset::copy->table;
2152 my $cs_table = config::copy_status->table;
2153 my $cl_table = asset::copy_location->table;
2154 my $br_table = biblio::record_entry->table;
2155 my $source_table = config::bib_source->table;
2158 my $bonuses = join (' * ', @bonus_lists);
2159 my $relevance = join (' + ', @rank_list);
2160 $relevance = "AVG( ($relevance) * ($bonuses) )";
2162 my $string_default_sort = 'zzzz';
2163 $string_default_sort = 'AAAA' if ($sort_dir eq 'DESC');
2165 my $number_default_sort = '9999';
2166 $number_default_sort = '0000' if ($sort_dir eq 'DESC');
2168 my $rank = $relevance;
2169 if (lc($sort) eq 'pubdate') {
2172 SELECT COALESCE(SUBSTRING(frp.value FROM E'\\\\d{4}'),'$number_default_sort')::INT
2173 FROM $metabib_full_rec frp
2174 WHERE frp.record = b.id
2176 AND frp.subfield = 'c'
2180 } elsif (lc($sort) eq 'create_date') {
2182 ( FIRST (( SELECT create_date FROM $br_table rbr WHERE rbr.id = b.id)) )
2184 } elsif (lc($sort) eq 'edit_date') {
2186 ( FIRST (( SELECT edit_date FROM $br_table rbr WHERE rbr.id = b.id)) )
2188 } elsif (lc($sort) eq 'title') {
2191 SELECT COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
2192 FROM $metabib_full_rec frt
2193 WHERE frt.record = b.id
2195 AND frt.subfield = 'a'
2199 } elsif (lc($sort) eq 'author') {
2202 SELECT COALESCE(LTRIM(fra.value),'$string_default_sort')
2203 FROM $metabib_full_rec fra
2204 WHERE fra.record = b.id
2205 AND fra.tag LIKE '1%'
2206 AND fra.subfield = 'a'
2207 ORDER BY fra.tag::text::int
2212 push @bonus_values, @bonus_values;
2217 my $select = <<" SQL";
2222 FROM $search_table_list
2223 $metabib_record_descriptor rd,
2226 WHERE rd.record = b.id
2227 AND b.active IS TRUE
2228 AND b.deleted IS FALSE
2237 GROUP BY b.id, b.source
2238 ORDER BY 3 $sort_dir
2239 LIMIT $visibility_limit
2242 if ($self->api_name !~ /staff/o) {
2247 LEFT OUTER JOIN $source_table src ON (s.source = src.id)
2250 FROM $asset_call_number_table cn,
2251 $asset_copy_table cp,
2255 WHERE cn.record = s.id
2256 AND cp.status = cs.id
2257 AND cp.location = cl.id
2258 AND cp.call_number = cn.id
2259 AND cp.opac_visible IS TRUE
2260 AND cs.opac_visible IS TRUE
2261 AND cl.opac_visible IS TRUE
2262 AND d.opac_visible IS TRUE
2263 AND cp.deleted IS FALSE
2264 AND cn.deleted IS FALSE
2265 AND cp.circ_lib = d.id
2269 OR src.transcendant IS TRUE
2270 ORDER BY 3 $sort_dir
2277 LEFT OUTER JOIN $source_table src ON (s.source = src.id)
2280 FROM $asset_call_number_table cn,
2281 $asset_copy_table cp,
2283 WHERE cn.record = s.id
2284 AND cp.call_number = cn.id
2285 AND cn.deleted IS FALSE
2286 AND cp.circ_lib = d.id
2287 AND cp.deleted IS FALSE
2293 FROM $asset_call_number_table cn
2294 WHERE cn.record = s.id
2297 OR src.transcendant IS TRUE
2298 ORDER BY 3 $sort_dir
2303 $log->debug("Field Search SQL :: [$select]",DEBUG);
2305 my $recs = $_cdbi->{title}->db_Main->selectall_arrayref(
2307 @bonus_values, @types, @forms, @vformats, @aud, @lang, @lit_form
2310 $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
2312 my $count = scalar(@$recs);
2313 for my $rec (@$recs[$offset .. $offset + $limit - 1]) {
2314 next unless ($$rec[0]);
2315 my ($mrid,$rank) = @$rec;
2316 $client->respond( [$mrid, sprintf('%0.3f',$rank), $count] );
2321 __PACKAGE__->register_method(
2322 api_name => "open-ils.storage.biblio.multiclass.search_fts.record",
2323 method => 'biblio_search_multi_class_fts',
2328 __PACKAGE__->register_method(
2329 api_name => "open-ils.storage.biblio.multiclass.search_fts.record.staff",
2330 method => 'biblio_search_multi_class_fts',
2335 __PACKAGE__->register_method(
2336 api_name => "open-ils.storage.biblio.multiclass.search_fts",
2337 method => 'biblio_search_multi_class_fts',
2342 __PACKAGE__->register_method(
2343 api_name => "open-ils.storage.biblio.multiclass.search_fts.staff",
2344 method => 'biblio_search_multi_class_fts',
2352 my $default_preferred_language;
2353 my $default_preferred_language_weight;
2355 # XXX factored most of the PG dependant stuff out of here... need to find a way to do "dependants".
2361 if (!$locale_map{COMPLETE}) {
2363 my @locales = config::i18n_locale->search_where({ code => { '<>' => '' } });
2364 for my $locale ( @locales ) {
2365 $locale_map{lc($locale->code)} = $locale->marc_code;
2367 $locale_map{COMPLETE} = 1;
2371 my $config = OpenSRF::Utils::SettingsClient->new();
2373 if (!$default_preferred_language) {
2375 $default_preferred_language = $config->config_value(
2376 apps => 'open-ils.search' => app_settings => 'default_preferred_language'
2377 ) || $config->config_value(
2378 apps => 'open-ils.storage' => app_settings => 'default_preferred_language'
2383 if (!$default_preferred_language_weight) {
2385 $default_preferred_language_weight = $config->config_value(
2386 apps => 'open-ils.storage' => app_settings => 'default_preferred_language_weight'
2387 ) || $config->config_value(
2388 apps => 'open-ils.storage' => app_settings => 'default_preferred_language_weight'
2392 # inclusion, exclusion, delete_adjusted_inclusion, delete_adjusted_exclusion
2393 my $estimation_strategy = $args{estimation_strategy} || 'inclusion';
2395 my $ou = $args{org_unit};
2396 my $limit = $args{limit} || 10;
2397 my $offset = $args{offset} || 0;
2400 $ou = actor::org_unit->search( { parent_ou => undef } )->next->id;
2403 if (! scalar( keys %{$args{searches}} )) {
2404 die "No search arguments were passed to ".$self->api_name;
2407 my (@between,@statuses,@locations,@types,@forms,@lang,@aud,@lit_form,@vformats,@bib_level);
2409 if (!defined($args{preferred_language})) {
2410 my $ses_locale = $client->session ? $client->session->session_locale : $default_preferred_language;
2411 $args{preferred_language} =
2412 $locale_map{ lc($ses_locale) } || 'eng';
2415 if (!defined($args{preferred_language_weight})) {
2416 $args{preferred_language_weight} = $default_preferred_language_weight || 2;
2419 if ($args{available}) {
2420 @statuses = (0,7,12);
2423 if (my $s = $args{locations}) {
2424 $s = [$s] if (!ref($s));
2428 if (my $b = $args{between}) {
2429 if (ref($b) && @$b == 2) {
2434 if (my $s = $args{statuses}) {
2435 $s = [$s] if (!ref($s));
2439 if (my $a = $args{audience}) {
2440 $a = [$a] if (!ref($a));
2444 if (my $l = $args{language}) {
2445 $l = [$l] if (!ref($l));
2449 if (my $f = $args{lit_form}) {
2450 $f = [$f] if (!ref($f));
2454 if (my $f = $args{item_form}) {
2455 $f = [$f] if (!ref($f));
2459 if (my $t = $args{item_type}) {
2460 $t = [$t] if (!ref($t));
2464 if (my $b = $args{bib_level}) {
2465 $b = [$b] if (!ref($b));
2469 if (my $v = $args{vr_format}) {
2470 $v = [$v] if (!ref($v));
2474 # XXX legacy format and item type support
2475 if ($args{format}) {
2476 my ($t, $f) = split '-', $args{format};
2477 @types = split '', $t;
2478 @forms = split '', $f;
2481 my %stored_proc_search_args;
2482 for my $search_group (sort keys %{$args{searches}}) {
2483 (my $search_group_name = $search_group) =~ s/\|/_/gso;
2484 my ($search_class,$search_field) = split /\|/, $search_group;
2485 $log->debug("Searching class [$search_class] and field [$search_field]",DEBUG);
2487 if ($search_field) {
2488 unless ( config::metabib_field->search( field_class => $search_class, name => $search_field )->next ) {
2489 $log->warn("Requested class [$search_class] or field [$search_field] does not exist!");
2494 my $class = $_cdbi->{$search_class};
2495 my $search_table = $class->table;
2497 my ($index_col) = $class->columns('FTS');
2498 $index_col ||= 'value';
2501 my $fts = OpenILS::Application::Storage::FTS->compile(
2502 $search_class => $args{searches}{$search_group}{term},
2503 $search_group_name.'.value',
2504 "$search_group_name.$index_col"
2506 $fts->sql_where_clause; # this builds the ranks for us
2508 my @fts_ranks = $fts->fts_rank;
2509 my @fts_queries = $fts->fts_query;
2510 my @phrases = map { lc($_) } $fts->phrases;
2511 my @words = map { lc($_) } $fts->words;
2513 $stored_proc_search_args{$search_group} = {
2514 fts_rank => \@fts_ranks,