blob: e367f9dee4564f0d2c46b1f20a0cd744e73817db [file] [log] [blame]
package Krawfish::Koral::Query;
use Role::Tiny;
# use Krawfish::Koral::Query::Builder;
use Krawfish::Log;
use Mojo::Util qw/md5_sum/;
use warnings;
use strict;
with 'Krawfish::Koral::Report';
# TODO:
# Share a role with Koral::Corpus and Koral::Report::Failure
requires qw/normalize
optimize
identify
type
operands
operand
finalize
uses_classes
is_anywhere
is_optional
is_null
is_nowhere
is_leaf
is_extended_right
is_extended_left
is_negative
maybe_anchor
maybe_unsorted
min_span
max_span
from_koral
to_koral_fragment
to_string
to_signature/;
# Base class for span queries
# TODO:
# - extended_* may be queried
# automatically without parameter
# - rename all sorts of single ops to operand
# - rename all sorts of multiple ops to operands
# TODO:
# This is now double with Krawfish::Koral!
use constant {
DEBUG => 0
};
# Constructor
sub new {
my $class = shift;
bless {
anywhere => 0,
optional => 0,
null => 0,
negative => 0,
extended => 0,
extended_left => 0,
extended_right => 0
}, $class;
};
#########################################
# Query Planning methods and attributes #
#########################################
# Refer to common subqueries
sub refer {
$_[0];
};
# Translate to ids
# TODO:
# If "nowhere" returns, optimize away
# before ->optimize().
sub identify {
my ($self, $dict) = @_;
my $ops = $self->operands;
return $self unless $ops;
for (my $i = 0; $i < @$ops; $i++) {
$ops->[$i] = $ops->[$i]->identify($dict);
};
return $self;
};
# Check for cached subqueries
sub cache {
$_[0];
};
# This is the class to be overwritten
# by subclasses
sub _finalize {
$_[0];
};
# Treat the operand like a root operand
sub finalize {
my $self = shift;
if (DEBUG) {
print_log('kq_query', 'Finalize query ' . $self->to_string);
};
my $query = $self;
# The query matches everywhere
if ($query->is_anywhere || $query->is_null) {
$self->error(780, "This query matches everywhere");
return;
};
# The query matches nowhere
if ($query->is_nowhere) {
return $query->builder->nowhere;
};
if ($query->is_negative) {
$query->warning(782, 'Exclusivity of query is ignored');
# TODO:
# Better not search at all, because in case the query was classed,
# this class information would be lost in the normalization process, so
# {1:[!der]} would become [der], which is somehow weird.
$query->is_negative(0);
};
# Ignore optionality
if ($query->is_optional) {
$query->warning(781, "Optionality of query is ignored");
$query->is_optional(0);
};
# Use a finalize method
$query = $query->_finalize;
# TODO:
# This needs to be in the finalize stage
# on the segment level!
# There is a possible 'anywhere' extension,
# that may exceed the text boundary
if ($query->is_extended_right) {
return $self->builder->in_text($query);
};
# Return the planned query
# TODO:
# Check for serialization errors
return $query;
};
sub remove_unused_classes {
my ($self, $classes) = @_;
my $used = $self->uses_classes;
# Pass classes required for highlighting or grouping,
# and take classes from uses_classes() into account.
# This is not done recursively, as it first needs to
# gather all classes and then can remove them.
};
sub uses_classes {
warn 'Not yet implemented';
};
# Remove classes passed as an array references
sub remove_classes {
my ($self, $keep) = @_;
unless ($keep) {
$keep = [];
};
my $ops = $self->operands;
return $self unless $ops;
for (my $i = 0; $i < @$ops; $i++) {
$ops->[$i] = $ops->[$i]->remove_classes($keep);
};
return $self;
};
# Get and set operands
sub operands {
my $self = shift;
if (@_) {
my $ops = shift;
my @new_ops = ();
foreach my $op (@$ops) {
$self->move_info_from($op);
push @new_ops, $op;
};
$self->{operands} = \@new_ops;
};
$self->{operands};
};
# Get and set first and only operand
sub operand {
my $self = shift;
if (@_ == 1) {
$self->{operands} = [shift];
};
return $self->{operands}->[0];
};
# Matches everything
sub is_anywhere {
my $self = shift;
if (defined $_[0]) {
$self->{anywhere} = shift;
};
return $self->{anywhere} // 0;
};
# Is optional
sub is_optional {
my $self = shift;
if (defined $_[0]) {
$self->{optional} = shift;
};
return $self->{optional} // 0;
};
# Null is empty - e.g. in
# Der >alte{0}< Mann
sub is_null {
my $self = shift;
if (defined $_[0]) {
$self->{null} = shift;
};
return $self->{null} // 0;
};
# Nothing matches nowhere - e.g. in
# Der [alte & !alte] Mann
sub is_nowhere {
my $self = shift;
if (defined $_[0]) {
$self->{nowhere} = shift;
};
return $self->{nowhere} // 0;
};
# Check if the query is a leaf node in the tree
sub is_leaf {
0;
};
# Check if the result of the query is extended to the right
sub is_extended_right {
$_[0]->{extended_right} // 0
};
# Check if the result of the query is extended to the left
sub is_extended_left {
$_[0]->{extended_left} // 0
};
# Check if the result of the query is extended
sub is_extended {
$_[0]->is_extended_right || $_[0]->is_extended_left // 0
};
# Is negative
sub is_negative {
my $self = shift;
if (scalar @_ == 1) {
$self->{negative} = shift;
return $self;
};
return $self->{negative} // 0;
};
# Toggle negativity
sub toggle_negative {
my $self = shift;
$self->is_negative($self->is_negative ? 0 : 1);
return $self;
};
# TODO: Probably better to be renamed "potential_anchor"
sub maybe_anchor {
my $self = shift;
return if $self->is_negative;
return if $self->is_optional;
return if $self->is_anywhere;
return 1;
};
# Check if the wrapped query may need to be sorted
# on focussing on a specific class.
# Normally spans are always sorted, but in case of
# a wrapped relation query, classed operands may
# be in arbitrary order. When focussing on these
# classes, the span has to me reordered.
# TODO:
# Rename to classes_maybe_unsorted
sub maybe_unsorted {
$_[0]->{maybe_unsorted} // 0
};
#############################
# Query Application methods #
#############################
# Serialize
#sub to_koral_query {
# my $self = shift;
# my $koral = $self->to_koral_fragment;
# $koral->{'@context'} = CONTEXT;
# $koral;
#};
sub to_neutral {
$_[0]->to_string;
};
# TODO: This may be optimizable and
# implemented in all query and corpus wrappers
sub to_signature {
md5_sum $_[0]->to_string;
};
# TODO: Returns a value of complexity of the query,
# that can be used to decide, if a query should be cached.
sub complexity {
warn 'override';
};
# Create KoralQuery builder
sub builder {
return Krawfish::Koral::Query::Builder->new;
};
# sub replace_references {
# my ($self, $refs) = @_;
# my $sig = $self->signature;
#
# # Subquery is identical to given query
# if ($refs->{$sig}) {
# ...
# }
# else {
# $refs->{$sig} = $self->operand;
# };
# };
1;