my $node_rank = 'COALESCE(' . $node->rank . " * ${talias}.weight * 1000, 0.0)";
- $from .= "\n" . ${spc} x 4 ."LEFT JOIN (\n"
+ my $search_cte = ",\n $talias AS (\n"
. ${spc} x 5 . "SELECT fe.*, fe_weight.weight, ${talias}_xq.tsq, ${talias}_xq.tsq_rank /* search */\n"
. ${spc} x 6 . "FROM $table AS fe\n"
. ${spc} x 7 . "JOIN config.metabib_field AS fe_weight ON (fe_weight.id = fe.field)";
- if ($node->dummy_count < @{$node->only_atoms} ) {
+ if ($node->dummy_count < @{$node->only_atoms} or @{$node->phrases}) {
$with .= ",\n " if $with;
$with .= "${talias}_xq AS (SELECT ". $node->tsquery ." AS tsq,". $node->tsquery_rank ." AS tsq_rank )";
if ($node->combined_search) {
- $from .= "\n" . ${spc} x 7 . "JOIN $ctable AS com ON (com.record = fe.source";
+ $search_cte .= "\n" . ${spc} x 7 . "JOIN $ctable AS com ON (com.record = fe.source";
if (@field_ids) {
- $from .= " AND com.metabib_field IN (" . join(',',@field_ids) . "))";
+ $search_cte .= " AND com.metabib_field IN (" . join(',',@field_ids) . "))";
} else {
- $from .= " AND com.metabib_field IS NULL)";
+ $search_cte .= " AND com.metabib_field IS NULL)";
}
- $from .= "\n" . ${spc} x 7 . "JOIN ${talias}_xq ON (com.index_vector @@ ${talias}_xq.tsq)";
+ $search_cte .= "\n" . ${spc} x 7 . "JOIN ${talias}_xq ON (com.index_vector @@ ${talias}_xq.tsq)";
} else {
- $from .= "\n" . ${spc} x 7 . "JOIN ${talias}_xq ON (fe.index_vector @@ ${talias}_xq.tsq)";
+ $search_cte .= "\n" . ${spc} x 7 . "JOIN ${talias}_xq ON (fe.index_vector @@ ${talias}_xq.tsq)";
}
} else {
- $from .= "\n" . ${spc} x 7 . ", (SELECT NULL::tsquery AS tsq, NULL::tsquery AS tsq_rank ) AS ${talias}_xq";
+ $search_cte .= "\n" . ${spc} x 7 . ", (SELECT NULL::tsquery AS tsq, NULL::tsquery AS tsq_rank ) AS ${talias}_xq";
}
if (@field_ids) {
- $from .= "\n" . ${spc} x 6 . "WHERE fe_weight.id IN (" .
+ $search_cte .= "\n" . ${spc} x 6 . "WHERE fe_weight.id IN (" .
join(',', @field_ids) . ")";
}
# NOTE: only real fields that match the (component) tsquery will
# get to contribute to and increased rank for the record.
- $from .= "\n" . ${spc} x 8 . "UNION ALL\n"
+ $search_cte .= "\n" . ${spc} x 8 . "UNION ALL\n"
. ${spc} x 5 . "SELECT fe.id, fe.source, fe.field, fe.value, fe.index_vector, "
. "fe_weight.weight, ${talias}_xq.tsq, ${talias}_xq.tsq_rank /* virtual field addition */\n"
. ${spc} x 6 . "FROM $vtable AS fe\n"
}
}
- $from .= "\n" . ${spc} x 4 . ") AS $talias ON (m.source = ${talias}.source)";
+ $with .= $search_cte . ')';
+ $from .= "\n" . ${spc} x 4 . "LEFT JOIN $talias ON (m.source = ${talias}.source)";
my %used_bumps;
my @bumps;
my $classname = $self->node->classname;
- return $self->sql("to_tsquery('$classname','')") if $self->{dummy};
+ return $self->sql("''::tsquery") if $self->{dummy};
my $normalizers = $self->node->plan->QueryParser->query_normalizers( $classname );
- my $fields = $self->node->fields;
-
- my $lang;
- my $filter = $self->node->plan->find_filter('preferred_language');
- $lang ||= $filter->args->[0] if ($filter && $filter->args);
- $lang ||= $self->node->plan->QueryParser->default_preferred_language;
- my $ts_configs = [];
-
- if (!@$fields) {
- $ts_configs = $self->node->plan->QueryParser->class_ts_config($classname, $lang);
- } else {
- for my $field (@$fields) {
- push @$ts_configs, @{$self->node->plan->QueryParser->field_ts_config($classname, $field, $lang)};
- }
- }
- $ts_configs = [keys %{{map { $_ => 1 } @$ts_configs}}];
-
- # Assume we want exact if none otherwise provided.
- # Because we can reasonably expect this to exist
- $ts_configs = ['simple'] unless (scalar @$ts_configs);
+ my $fields = $self->node->fields;
$fields = $self->node->plan->QueryParser->search_fields->{$classname} if (!@$fields);
my %norms;
$suffix_op = ":$suffix" if $suffix;
$suffix_after = "|| '$suffix_op'" if $suffix;
+ my $ts_configs = $self->node->ts_configs;
+
my @sql_set = ();
for my $ts_config (@$ts_configs) {
- push @sql_set, "to_tsquery('$ts_config', COALESCE(NULLIF($prefix '(' || btrim(regexp_replace($sql,E'(?:\\\\s+|:)','$suffix_op&','g'),'&|') $suffix_after || ')', '()'), ''))";
+ push @sql_set, "to_tsquery('$ts_config', COALESCE(NULLIF($prefix '(' || btrim(regexp_replace($sql,E'(?:\\\\s+|:)','$suffix_op&','g'),'&|') $suffix_after || ')', $prefix '()'), ''))";
}
$sql = join($joiner, @sql_set);
use List::MoreUtils qw/uniq/;
use Data::Dumper;
+sub ts_configs {
+ my $self = shift;
+ my $ts_configs = $self->{ts_configs} || [];
+
+ if (!@$ts_configs) {
+ my $classname = $self->classname;
+ my $lang;
+ my $filter = $self->plan->find_filter('preferred_language');
+ $lang ||= $filter->args->[0] if ($filter && $filter->args);
+ $lang ||= $self->plan->QueryParser->default_preferred_language;
+
+ my $fields = $self->fields;
+ if (!@$fields) {
+ $ts_configs = $self->plan->QueryParser->class_ts_config($classname, $lang);
+ } else {
+ for my $field (@$fields) {
+ push @$ts_configs, @{$self->plan->QueryParser->field_ts_config($classname, $field, $lang)};
+ }
+ }
+ $ts_configs = [keys %{{map { $_ => 1 } @$ts_configs}}];
+
+ # Assume we want exact if none otherwise provided.
+ # Because we can reasonably expect this to exist
+ $ts_configs = ['simple'] unless (scalar @$ts_configs);
+ $self->{ts_configs} = $ts_configs;
+ }
+
+ return $ts_configs;
+}
+
sub abstract_node_additions {
my $self = shift;
my $aq = shift;
for my $atom (@{$self->query_atoms}) {
if (ref($atom)) {
- $self->{tsquery} .= "\n" . ${spc} x 3 . $atom->sql;
+ $self->{tsquery} .= "\n" . ${spc} x 3;
+ $self->{tsquery} .= '(' if $atom->explicit_start;
+ $self->{tsquery} .= $atom->sql;
+ $self->{tsquery} .= ')' if $atom->explicit_end;
} else {
$self->{tsquery} .= $atom x 2;
}
# any phrases that are more than empty or all-whitespace
if (my @phrases = grep { /\S+/ } @{$self->phrases}) {
my $neg = $self->negate ? '!!' : '';
- $self->{tsquery} = '('.$self->{tsquery}.') && ' .
- join(' && ',
- map {
- "${neg}phraseto_tsquery('simple', \$_$$\$$_\$_$$\$)"
- } @phrases
- );
+ $self->{tsquery} ||= "''::tsquery";
+ $self->{tsquery} .= ' && ' . join(
+ ' && ',
+ map { "${neg}phraseto_tsquery('simple', \$_$$\$$_\$_$$\$)" } @phrases
+ );
}
return $self->{tsquery};
}
$self->{tsquery_rank} = join(' ||', @atomlines);
$self->{tsquery_rank} = "''::tsquery" unless $self->{tsquery_rank};
+
+ # any non-negated phrases that are more than empty or all-whitespace
+ if (!$self->negate) {
+ if (my @phrases = grep { /\S+/ } @{$self->phrases}) {
+ for my $tsc (@{$self->ts_configs}) {
+ $self->{tsquery_rank} .= ' || ' . join(
+ ' || ',
+ map { "phraseto_tsquery('$tsc', \$_$$\$$_\$_$$\$)" } @phrases
+ );
+ }
+ }
+ }
+
return $self->{tsquery_rank};
}