| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 3 |  |  | 3 |  | 4218 | use v5.14; | 
|  | 3 |  |  |  |  | 10 |  | 
| 2 | 3 |  |  | 3 |  | 14 | use warnings; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 89 |  | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | =head1 NAME | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | Attean::SimpleQueryEvaluator - Simple query evaluator | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | =head1 VERSION | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | This document describes Attean::SimpleQueryEvaluator version 0.032 | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | use v5.14; | 
| 15 |  |  |  |  |  |  | use Attean; | 
| 16 |  |  |  |  |  |  | my $algebra = Attean->get_parser('SPARQL')->parse('SELECT * WHERE { ... }'); | 
| 17 |  |  |  |  |  |  | my $active_graph = Attean::IRI->new('http://example.org/'); | 
| 18 |  |  |  |  |  |  | my $e = Attean::SimpleQueryEvaluator->new( model => $model ); | 
| 19 |  |  |  |  |  |  | my $iter = $e->evaluate( $algebra, $active_graph ); | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | The Attean::SimpleQueryEvaluator class implements a simple query evaluator that, | 
| 24 |  |  |  |  |  |  | given an L<Attean::API::Algebra|Attean::API::Query> and a L<Attean::API::Model> | 
| 25 |  |  |  |  |  |  | object, evaluates the query represented by the algebra using data from the | 
| 26 |  |  |  |  |  |  | model, and returns a query result. | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | =head1 ATTRIBUTES | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | =over 4 | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | =cut | 
| 33 |  |  |  |  |  |  |  | 
| 34 | 3 |  |  | 3 |  | 15 | use Attean::Algebra; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 61 |  | 
| 35 | 3 |  |  | 3 |  | 17 | use Attean::Expression; | 
|  | 3 |  |  |  |  | 9 |  | 
|  | 3 |  |  |  |  | 80 |  | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | use Moo; | 
| 38 | 3 |  |  | 3 |  | 14 | use Encode qw(encode); | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 23 |  | 
| 39 | 3 |  |  | 3 |  | 973 | use Attean::RDF; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 123 |  | 
| 40 | 3 |  |  | 3 |  | 16 | use LWP::UserAgent; | 
|  | 3 |  |  |  |  | 13 |  | 
|  | 3 |  |  |  |  | 21 |  | 
| 41 | 3 |  |  | 3 |  | 2043 | use Scalar::Util qw(blessed); | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 42 | 3 |  |  | 3 |  | 17 | use List::Util qw(all any reduce); | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 130 |  | 
| 43 | 3 |  |  | 3 |  | 18 | use Types::Standard qw(ConsumerOf InstanceOf Bool Object); | 
|  | 3 |  |  |  |  | 10 |  | 
|  | 3 |  |  |  |  | 197 |  | 
| 44 | 3 |  |  | 3 |  | 19 | use URI::Escape; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 21 |  | 
| 45 | 3 |  |  | 3 |  | 2148 | use Attean::SPARQLClient; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 147 |  | 
| 46 | 3 |  |  | 3 |  | 28 | use namespace::clean; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 50 |  | 
| 47 | 3 |  |  | 3 |  | 14 |  | 
|  | 3 |  |  |  |  | 15 |  | 
|  | 3 |  |  |  |  | 18 |  | 
| 48 |  |  |  |  |  |  | =item C<< model >> | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | The L<Attean::API::Model> object used for query evaluation. | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | =cut | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | has 'model' => (is => 'ro', isa => ConsumerOf['Attean::API::Model'], required => 1); | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | =item C<< default_graph >> | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | The L<Attean::API::IRI> object representing the default graph in the C<< model >>. | 
| 59 |  |  |  |  |  |  | The default graph will be excluded from enumeration of graph names for query | 
| 60 |  |  |  |  |  |  | features such as C<< GRAPH ?g {} >>. | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | =cut | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | has 'default_graph'	=> (is => 'ro', isa => ConsumerOf['Attean::API::IRI'], required => 1); | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | has 'user_agent' => (is => 'rw', isa => InstanceOf['LWP::UserAgent'], default => sub { my $ua = LWP::UserAgent->new(); $ua->agent("Attean/$Attean::VERSION " . $ua->_agent); $ua }); | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | =item C<< request_signer >> | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | If set, used to modify HTTP::Request objects used in evaluating SERVICE calls | 
| 71 |  |  |  |  |  |  | before the request is made. This may be used to, for example, add cryptographic | 
| 72 |  |  |  |  |  |  | signature headers to the request. The modification is performed by calling | 
| 73 |  |  |  |  |  |  | C<< $request_signer->sign( $request ) >>. | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | =cut | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | has 'request_signer' => (is => 'rw', isa => Object); | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | has 'ground_blanks' => (is => 'rw', isa => Bool, default => 0); | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | =back | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | =head1 METHODS | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | =over 4 | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | =item C<< evaluate( $algebra, $active_graph ) >> | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | Returns an L<Attean::API::Iterator> object with results produced by evaluating | 
| 90 |  |  |  |  |  |  | the query C<< $algebra >> against the evaluator's C<< model >>, using the | 
| 91 |  |  |  |  |  |  | supplied C<< $active_graph >>. | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | =cut | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | my $self			= shift; | 
| 96 |  |  |  |  |  |  | my $algebra			= shift; | 
| 97 | 65 |  |  | 65 | 1 | 2602 | my $active_graph	= shift || Carp::confess "No active-graph passed to Attean::SimpleQueryEvaluator->evaluate"; | 
| 98 | 65 |  |  |  |  | 87 |  | 
| 99 | 65 |  | 33 |  |  | 127 | Carp::confess "No algebra passed for evaluation" unless ($algebra); | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 65 | 50 |  |  |  | 126 | my $expr_eval	= Attean::SimpleQueryEvaluator::ExpressionEvaluator->new( evaluator => $self ); | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 65 |  |  |  |  | 1029 | my @children	= @{ $algebra->children }; | 
| 104 |  |  |  |  |  |  | my ($child)		= $children[0]; | 
| 105 | 65 |  |  |  |  | 3362 | if ($algebra->isa('Attean::Algebra::Query') or $algebra->isa('Attean::Algebra::Update')) { | 
|  | 65 |  |  |  |  | 193 |  | 
| 106 | 65 |  |  |  |  | 127 | return $self->evaluate($algebra->child, $active_graph, @_); | 
| 107 | 65 | 50 | 33 |  |  | 1295 | } elsif ($algebra->isa('Attean::Algebra::BGP')) { | 
|  |  | 100 | 66 |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 108 | 0 |  |  |  |  | 0 | my @triples	= @{ $algebra->triples }; | 
| 109 |  |  |  |  |  |  | if (scalar(@triples) == 0) { | 
| 110 | 21 |  |  |  |  | 35 | my $b	= Attean::Result->new( bindings => {} ); | 
|  | 21 |  |  |  |  | 55 |  | 
| 111 | 21 | 50 |  |  |  | 45 | return Attean::ListIterator->new(variables => [], values => [$b], item_type => 'Attean::API::Result'); | 
| 112 | 0 |  |  |  |  | 0 | } else { | 
| 113 | 0 |  |  |  |  | 0 | my @iters; | 
| 114 |  |  |  |  |  |  | my @new_vars; | 
| 115 | 21 |  |  |  |  | 50 | my %blanks; | 
| 116 |  |  |  |  |  |  | foreach my $t (@triples) { | 
| 117 | 21 |  |  |  |  | 0 | push(@iters, $self->evaluate_pattern($t, $active_graph, \@new_vars, \%blanks)); | 
| 118 | 21 |  |  |  |  | 34 | } | 
| 119 | 24 |  |  |  |  | 184 | while (scalar(@iters) > 1) { | 
| 120 |  |  |  |  |  |  | my ($lhs, $rhs)	= splice(@iters, 0, 2); | 
| 121 | 21 |  |  |  |  | 785 | unshift(@iters, $lhs->join($rhs)); | 
| 122 | 3 |  |  |  |  | 9 | } | 
| 123 | 3 |  |  |  |  | 14 | return shift(@iters)->map(sub { shift->project_complement(@new_vars) }); | 
| 124 |  |  |  |  |  |  | } | 
| 125 | 21 |  |  | 60 |  | 1471 | } elsif ($algebra->isa('Attean::Algebra::Distinct') or $algebra->isa('Attean::Algebra::Reduced')) { | 
|  | 60 |  |  |  |  | 156 |  | 
| 126 |  |  |  |  |  |  | my %seen; | 
| 127 |  |  |  |  |  |  | my $iter	= $self->evaluate( $child, $active_graph ); | 
| 128 | 1 |  |  |  |  | 3 | return $iter->grep(sub { | 
| 129 | 1 |  |  |  |  | 3 | my $r	= shift; | 
| 130 |  |  |  |  |  |  | my $str	= $r->as_string; | 
| 131 | 4 |  |  | 4 |  | 7 | my $ok	= not($seen{ $str }) ? 1 : 0; | 
| 132 | 4 |  |  |  |  | 10 | $seen{ $str }++; | 
| 133 | 4 | 100 |  |  |  | 13 | return $ok; | 
| 134 | 4 |  |  |  |  | 18 | }); | 
| 135 | 4 |  |  |  |  | 18 | } elsif ($algebra->isa('Attean::Algebra::Extend')) { | 
| 136 | 1 |  |  |  |  | 62 | my $child	= $algebra; | 
| 137 |  |  |  |  |  |  | my @extends; | 
| 138 | 2 |  |  |  |  | 4 | my %extends; | 
| 139 | 2 |  |  |  |  | 5 | while ($child->isa('Attean::Algebra::Extend')) { | 
| 140 |  |  |  |  |  |  | my $expr			= $child->expression; | 
| 141 | 2 |  |  |  |  | 7 | my $var				= $child->variable->value; | 
| 142 | 2 |  |  |  |  | 7 | $extends{ $var }	= $expr; | 
| 143 | 2 |  |  |  |  | 7 | unshift(@extends, $var); | 
| 144 | 2 |  |  |  |  | 5 | ($child)			= @{ $child->children }; | 
| 145 | 2 |  |  |  |  | 4 | } | 
| 146 | 2 |  |  |  |  | 4 | return $self->evaluate( $child, $active_graph )->map(sub { | 
|  | 2 |  |  |  |  | 10 |  | 
| 147 |  |  |  |  |  |  | my $r	= shift; | 
| 148 |  |  |  |  |  |  | my %extension; | 
| 149 | 8 |  |  | 8 |  | 13 | my %row_cache; | 
| 150 | 8 |  |  |  |  | 12 | foreach my $var (@extends) { | 
| 151 |  |  |  |  |  |  | my $expr	= $extends{ $var }; | 
| 152 | 8 |  |  |  |  | 13 | my $val		= $expr_eval->evaluate_expression( $expr, $r, $active_graph, \%row_cache ); | 
| 153 | 8 |  |  |  |  | 15 | if ($val->does('Attean::API::Binding')) { | 
| 154 | 8 |  |  |  |  | 23 | # patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple) | 
| 155 | 8 | 50 |  |  |  | 362 | $val	= $val->ground($r); | 
| 156 |  |  |  |  |  |  | } | 
| 157 | 0 |  |  |  |  | 0 | # 					warn "Extend error: $@" if ($@); | 
| 158 |  |  |  |  |  |  | $r	= Attean::Result->new( bindings => { $var => $val } )->join($r) if ($val); | 
| 159 |  |  |  |  |  |  | } | 
| 160 | 8 | 50 |  |  |  | 313 | return $r; | 
| 161 |  |  |  |  |  |  | }); | 
| 162 | 8 |  |  |  |  | 33 | } elsif ($algebra->isa('Attean::Algebra::Filter')) { | 
| 163 | 2 |  |  |  |  | 5 | # TODO: Merge adjacent filter evaluation so that they can share a row_cache hash (as is done for Extend above) | 
| 164 |  |  |  |  |  |  | my $expr	= $algebra->expression; | 
| 165 |  |  |  |  |  |  | my $iter	= $self->evaluate( $child, $active_graph ); | 
| 166 | 1 |  |  |  |  | 5 | return $iter->grep(sub { | 
| 167 | 1 |  |  |  |  | 4 | my $t	= $expr_eval->evaluate_expression( $expr, shift, $active_graph, {} ); | 
| 168 |  |  |  |  |  |  | # 				if ($@) { warn "Filter evaluation: $@\n" }; | 
| 169 | 4 |  |  | 4 |  | 13 | return ($t ? $t->ebv : 0); | 
| 170 |  |  |  |  |  |  | }); | 
| 171 | 4 | 50 |  |  |  | 21 | } elsif ($algebra->isa('Attean::Algebra::OrderBy')) { | 
| 172 | 1 |  |  |  |  | 55 | local($Attean::API::Binding::ALLOW_IRI_COMPARISON)	= 1; | 
| 173 |  |  |  |  |  |  | my $iter	= $self->evaluate( $child, $active_graph ); | 
| 174 | 3 |  |  |  |  | 33 | my @rows	= $iter->elements; | 
| 175 | 3 |  |  |  |  | 11 | my @cmps	= @{ $algebra->comparators }; | 
| 176 | 3 |  |  |  |  | 101 | my @exprs	= map { $_->expression } @cmps; | 
| 177 | 3 |  |  |  |  | 6 | my @dirs	= map { $_->ascending } @cmps; | 
|  | 3 |  |  |  |  | 10 |  | 
| 178 | 3 |  |  |  |  | 7 | my @sorted	= map { $_->[0] } sort { | 
|  | 4 |  |  |  |  | 10 |  | 
| 179 | 3 |  |  |  |  | 5 | my ($ar, $avalues)	= @$a; | 
|  | 4 |  |  |  |  | 9 |  | 
| 180 | 12 |  |  |  |  | 19 | my ($br, $bvalues)	= @$b; | 
| 181 | 15 |  |  |  |  | 21 | my $c	= 0; | 
| 182 | 15 |  |  |  |  | 20 | foreach my $i (0 .. $#cmps) { | 
| 183 | 15 |  |  |  |  | 19 | my ($av, $bv)	= map { $_->[$i] } ($avalues, $bvalues); | 
| 184 | 15 |  |  |  |  | 29 | # Mirrors code in Attean::Plan::OrderBy->sort_rows | 
| 185 | 16 |  |  |  |  | 21 | if (blessed($av) and $av->does('Attean::API::Binding') and (not(defined($bv)) or not($bv->does('Attean::API::Binding')))) { | 
|  | 32 |  |  |  |  | 49 |  | 
| 186 |  |  |  |  |  |  | $c	= 1; | 
| 187 | 16 | 50 | 33 |  |  | 56 | } elsif (blessed($bv) and $bv->does('Attean::API::Binding') and (not(defined($av)) or not($av->does('Attean::API::Binding')))) { | 
|  |  | 50 | 0 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 188 | 0 |  |  |  |  | 0 | $c	= -1; | 
| 189 |  |  |  |  |  |  | } else { | 
| 190 | 0 |  |  |  |  | 0 | $c		= eval { $av ? $av->compare($bv) : 1 }; | 
| 191 |  |  |  |  |  |  | if ($@) { | 
| 192 | 16 | 50 |  |  |  | 603 | $c	= 1; | 
|  | 16 |  |  |  |  | 40 |  | 
| 193 | 16 | 50 |  |  |  | 28 | } | 
| 194 | 0 |  |  |  |  | 0 | } | 
| 195 |  |  |  |  |  |  | $c		*= -1 if ($dirs[$i] == 0); | 
| 196 |  |  |  |  |  |  | last unless ($c == 0); | 
| 197 | 16 | 100 |  |  |  | 32 | } | 
| 198 | 16 | 100 |  |  |  | 28 | $c | 
| 199 |  |  |  |  |  |  | } map { my $r = $_; [$r, [map { $expr_eval->evaluate_expression( $_, $r, $active_graph, {} ) } @exprs]] } @rows; | 
| 200 |  |  |  |  |  |  | return Attean::ListIterator->new( values => \@sorted, item_type => $iter->item_type, variables => $iter->variables); | 
| 201 | 3 |  |  |  |  | 7 | } elsif ($algebra->isa('Attean::Algebra::Service')) { | 
|  | 12 |  |  |  |  | 18 |  | 
|  | 12 |  |  |  |  | 14 |  | 
|  | 16 |  |  |  |  | 34 |  | 
| 202 | 3 |  |  |  |  | 66 | my $endpoint	= $algebra->endpoint->value; | 
| 203 |  |  |  |  |  |  | my ($pattern)	= @{ $algebra->children }; | 
| 204 | 1 |  |  |  |  | 6 | my $sparql		= Attean::Algebra::Project->new( variables => [ map { variable($_) } $pattern->in_scope_variables ], children => [ $pattern ] )->as_sparql; | 
| 205 | 1 |  |  |  |  | 3 | my $silent		= $algebra->silent; | 
|  | 1 |  |  |  |  | 4 |  | 
| 206 | 1 |  |  |  |  | 6 | my $client		= Attean::SPARQLClient->new( | 
|  | 3 |  |  |  |  | 144 |  | 
| 207 | 1 |  |  |  |  | 26 | endpoint => $endpoint, | 
| 208 | 1 |  |  |  |  | 21 | silent => $silent, | 
| 209 |  |  |  |  |  |  | user_agent => $self->user_agent, | 
| 210 |  |  |  |  |  |  | request_signer => $self->request_signer, | 
| 211 |  |  |  |  |  |  | ); | 
| 212 |  |  |  |  |  |  | return $client->query($sparql); | 
| 213 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Graph')) { | 
| 214 | 1 |  |  |  |  | 302 | my $graph	= $algebra->graph; | 
| 215 |  |  |  |  |  |  | return $self->evaluate($child, $graph) if ($graph->does('Attean::API::Term')); | 
| 216 | 3 |  |  |  |  | 10 |  | 
| 217 | 3 | 100 |  |  |  | 11 | my @iters; | 
| 218 |  |  |  |  |  |  | my $graphs	= $self->model->get_graphs(); | 
| 219 | 1 |  |  |  |  | 24 | my %vars; | 
| 220 | 1 |  |  |  |  | 22 | while (my $g = $graphs->next) { | 
| 221 | 1 |  |  |  |  | 27 | next if ($g->value eq $self->default_graph->value); | 
| 222 | 1 |  |  |  |  | 7 | my $gr	= Attean::Result->new( bindings => { $graph->value => $g } ); | 
| 223 | 2 | 100 |  |  |  | 15 | my $iter	= $self->evaluate($child, $g)->map(sub { if (my $result = shift->join($gr)) { return $result } else { return } }); | 
| 224 | 1 |  |  |  |  | 20 | foreach my $v (@{ $iter->variables }) { | 
| 225 | 1 | 50 |  | 4 |  | 23 | $vars{$v}++; | 
|  | 4 |  |  |  |  | 13 |  | 
|  | 4 |  |  |  |  | 15 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 226 | 1 |  |  |  |  | 32 | } | 
|  | 1 |  |  |  |  | 17 |  | 
| 227 | 2 |  |  |  |  | 8 | push(@iters, $iter); | 
| 228 |  |  |  |  |  |  | } | 
| 229 | 1 |  |  |  |  | 5 | return Attean::IteratorSequence->new( variables => [keys %vars], iterators => \@iters, item_type => 'Attean::API::Result' ); | 
| 230 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Group')) { | 
| 231 | 1 |  |  |  |  | 18 | my @groupby	= @{ $algebra->groupby }; | 
| 232 |  |  |  |  |  |  | my $iter	= $self->evaluate($child, $active_graph); | 
| 233 | 0 |  |  |  |  | 0 | my %groups; | 
|  | 0 |  |  |  |  | 0 |  | 
| 234 | 0 |  |  |  |  | 0 | while (my $r = $iter->next) { | 
| 235 | 0 |  |  |  |  | 0 | my %vars; | 
| 236 | 0 |  |  |  |  | 0 | my %row_cache; | 
| 237 | 0 |  |  |  |  | 0 | my @group_terms	= map { $expr_eval->evaluate_expression( $_, $r, $active_graph, \%row_cache ) } @groupby; | 
| 238 |  |  |  |  |  |  | my $key			= join(' ', map { blessed($_) ? $_->as_string : '' } @group_terms); | 
| 239 | 0 |  |  |  |  | 0 | my %group_bindings; | 
|  | 0 |  |  |  |  | 0 |  | 
| 240 | 0 | 0 |  |  |  | 0 | foreach my $i (0 .. $#group_terms) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 241 | 0 |  |  |  |  | 0 | my $v	= $groupby[$i]; | 
| 242 | 0 |  |  |  |  | 0 | if (blessed($v) and $v->isa('Attean::ValueExpression') and $v->value->does('Attean::API::Variable') and $group_terms[$i]) { | 
| 243 | 0 |  |  |  |  | 0 | $group_bindings{$v->value->value}	= $group_terms[$i]; | 
| 244 | 0 | 0 | 0 |  |  | 0 | } | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 245 | 0 |  |  |  |  | 0 | } | 
| 246 |  |  |  |  |  |  | $groups{$key}	= [Attean::Result->new( bindings => \%group_bindings ), []] unless (exists($groups{$key})); | 
| 247 |  |  |  |  |  |  | push(@{ $groups{$key}[1] }, $r); | 
| 248 | 0 | 0 |  |  |  | 0 | } | 
| 249 | 0 |  |  |  |  | 0 | my @keys	= keys %groups; | 
|  | 0 |  |  |  |  | 0 |  | 
| 250 |  |  |  |  |  |  | $groups{''}	= [Attean::Result->new( bindings => {} ), []] if (scalar(@keys) == 0); | 
| 251 | 0 |  |  |  |  | 0 | my $aggs	= $algebra->aggregates; | 
| 252 | 0 | 0 |  |  |  | 0 | my @results; | 
| 253 | 0 |  |  |  |  | 0 | my %vars; | 
| 254 | 0 |  |  |  |  | 0 | foreach my $key (keys %groups) { | 
| 255 |  |  |  |  |  |  | my %row_cache; | 
| 256 | 0 |  |  |  |  | 0 | my ($binding, $rows)	= @{ $groups{$key} }; | 
| 257 | 0 |  |  |  |  | 0 | my $count	= scalar(@$rows); | 
| 258 | 0 |  |  |  |  | 0 | my %bindings; | 
|  | 0 |  |  |  |  | 0 |  | 
| 259 | 0 |  |  |  |  | 0 | foreach my $i (0 .. $#{ $aggs }) { | 
| 260 | 0 |  |  |  |  | 0 | my $name	= $aggs->[$i]->variable->value; | 
| 261 | 0 |  |  |  |  | 0 | my $term	= $expr_eval->evaluate_expression( $aggs->[$i], $rows, $active_graph, {} ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 262 | 0 |  |  |  |  | 0 | # 					warn "AGGREGATE error: $@" if ($@); | 
| 263 | 0 |  |  |  |  | 0 | $vars{$name}++; | 
| 264 |  |  |  |  |  |  | $bindings{ $name } = $term if ($term); | 
| 265 | 0 |  |  |  |  | 0 | } | 
| 266 | 0 | 0 |  |  |  | 0 | push(@results, Attean::Result->new( bindings => \%bindings )->join($binding)); | 
| 267 |  |  |  |  |  |  | } | 
| 268 | 0 |  |  |  |  | 0 | return Attean::ListIterator->new(variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result'); | 
| 269 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Join')) { | 
| 270 | 0 |  |  |  |  | 0 | my ($lhs, $rhs)	= map { $self->evaluate($_, $active_graph) } @children; | 
| 271 |  |  |  |  |  |  | return $lhs->join($rhs); | 
| 272 | 2 |  |  |  |  | 8 | } elsif ($algebra->isa('Attean::Algebra::LeftJoin')) { | 
|  | 4 |  |  |  |  | 72 |  | 
| 273 | 2 |  |  |  |  | 73 | my $expr	= $algebra->expression; | 
| 274 |  |  |  |  |  |  | my ($lhs_iter, $rhs_iter)	= map { $self->evaluate($_, $active_graph) } @children; | 
| 275 | 0 |  |  |  |  | 0 | my @rhs		= $rhs_iter->elements; | 
| 276 | 0 |  |  |  |  | 0 | my @results; | 
|  | 0 |  |  |  |  | 0 |  | 
| 277 | 0 |  |  |  |  | 0 | my %vars	= map { $_ => 1 } (@{ $lhs_iter->variables }, @{ $rhs_iter->variables }); | 
| 278 | 0 |  |  |  |  | 0 | while (my $lhs = $lhs_iter->next) { | 
| 279 | 0 |  |  |  |  | 0 | my $joined	= 0; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 280 | 0 |  |  |  |  | 0 | foreach my $rhs (@rhs) { | 
| 281 | 0 |  |  |  |  | 0 | if (my $j = $lhs->join($rhs)) { | 
| 282 | 0 |  |  |  |  | 0 | if ($expr_eval->evaluate_expression( $expr, $j, $active_graph, {} )->ebv) { | 
| 283 | 0 | 0 |  |  |  | 0 | $joined++; | 
| 284 | 0 | 0 |  |  |  | 0 | push(@results, $j); | 
| 285 | 0 |  |  |  |  | 0 | } | 
| 286 | 0 |  |  |  |  | 0 | } | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  | push(@results, $lhs) unless ($joined); | 
| 289 |  |  |  |  |  |  | } | 
| 290 | 0 | 0 |  |  |  | 0 | return Attean::ListIterator->new( variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result'); | 
| 291 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Minus')) { | 
| 292 | 0 |  |  |  |  | 0 | my ($lhsi, $rhs)	= map { $self->evaluate($_, $active_graph) } @children; | 
| 293 |  |  |  |  |  |  | my @rhs				= $rhs->elements; | 
| 294 | 0 |  |  |  |  | 0 | my @results; | 
|  | 0 |  |  |  |  | 0 |  | 
| 295 | 0 |  |  |  |  | 0 | while (my $lhs = $lhsi->next) { | 
| 296 | 0 |  |  |  |  | 0 | my @compatible; | 
| 297 | 0 |  |  |  |  | 0 | my @disjoint; | 
| 298 | 0 |  |  |  |  | 0 | RHS: foreach my $rhs (@rhs) { | 
| 299 |  |  |  |  |  |  | if (my $j = $lhs->join($rhs)) { | 
| 300 | 0 |  |  |  |  | 0 | push(@compatible, 1); | 
| 301 | 0 | 0 |  |  |  | 0 | } else { | 
| 302 | 0 |  |  |  |  | 0 | push(@compatible, 0); | 
| 303 |  |  |  |  |  |  | } | 
| 304 | 0 |  |  |  |  | 0 |  | 
| 305 |  |  |  |  |  |  | my $intersects	= 0; | 
| 306 |  |  |  |  |  |  | my %lhs_dom	= map { $_ => 1 } $lhs->variables; | 
| 307 | 0 |  |  |  |  | 0 | foreach my $rvar ($rhs->variables) { | 
| 308 | 0 |  |  |  |  | 0 | if (exists $lhs_dom{$rvar}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 309 | 0 |  |  |  |  | 0 | $intersects	= 1; | 
| 310 | 0 | 0 |  |  |  | 0 | } | 
| 311 | 0 |  |  |  |  | 0 | } | 
| 312 |  |  |  |  |  |  | push(@disjoint, not($intersects)); | 
| 313 |  |  |  |  |  |  | } | 
| 314 | 0 |  |  |  |  | 0 |  | 
| 315 |  |  |  |  |  |  | my $count	= scalar(@rhs); | 
| 316 |  |  |  |  |  |  | my $keep	= 1; | 
| 317 | 0 |  |  |  |  | 0 | foreach my $i (0 .. $#rhs) { | 
| 318 | 0 |  |  |  |  | 0 | $keep	= 0 unless ($compatible[$i] == 0 or $disjoint[$i] == 1); | 
| 319 | 0 |  |  |  |  | 0 | } | 
| 320 | 0 | 0 | 0 |  |  | 0 |  | 
| 321 |  |  |  |  |  |  | push(@results, $lhs) if ($keep); | 
| 322 |  |  |  |  |  |  | } | 
| 323 | 0 | 0 |  |  |  | 0 | return Attean::ListIterator->new( variables => $lhsi->variables, values => \@results, item_type => 'Attean::API::Result'); | 
| 324 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Path')) { | 
| 325 | 0 |  |  |  |  | 0 | my $s			= $algebra->subject; | 
| 326 |  |  |  |  |  |  | my $path		= $algebra->path; | 
| 327 | 24 |  |  |  |  | 50 | my $o			= $algebra->object; | 
| 328 | 24 |  |  |  |  | 41 | my @children	= @{ $path->children }; | 
| 329 | 24 |  |  |  |  | 42 | my ($child)		= $children[0]; | 
| 330 | 24 |  |  |  |  | 36 |  | 
|  | 24 |  |  |  |  | 54 |  | 
| 331 | 24 |  |  |  |  | 42 | return $self->model->get_bindings( $s, $path->predicate, $o, $active_graph ) if ($path->isa('Attean::Algebra::PredicatePath')); | 
| 332 |  |  |  |  |  |  | if ($path->isa('Attean::Algebra::InversePath')) { | 
| 333 | 24 | 100 |  |  |  | 131 | my $path	= Attean::Algebra::Path->new( subject => $o, path => $child, object => $s ); | 
| 334 | 11 | 50 | 66 |  |  | 138 | return $self->evaluate( $path, $active_graph ); | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 335 | 0 |  |  |  |  | 0 | } elsif ($path->isa('Attean::Algebra::AlternativePath')) { | 
| 336 | 0 |  |  |  |  | 0 | my @children	= @{ $path->children }; | 
| 337 |  |  |  |  |  |  | my @algebras	= map { Attean::Algebra::Path->new( subject => $s, path => $_, object => $o ) } @children; | 
| 338 | 0 |  |  |  |  | 0 | my @iters		= map { $self->evaluate($_, $active_graph) } @algebras; | 
|  | 0 |  |  |  |  | 0 |  | 
| 339 | 0 |  |  |  |  | 0 | return Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type, variables => [$algebra->in_scope_variables] ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 340 | 0 |  |  |  |  | 0 | } elsif ($path->isa('Attean::Algebra::NegatedPropertySet')) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 341 | 0 |  |  |  |  | 0 | my $preds	= $path->predicates; | 
| 342 |  |  |  |  |  |  | my %preds	= map { $_->value => 1 } @$preds; | 
| 343 | 1 |  |  |  |  | 221 | my $filter	= $self->model->get_quads($s, undef, $o, $active_graph)->grep(sub { | 
| 344 | 1 |  |  |  |  | 6 | my $q	= shift; | 
|  | 1 |  |  |  |  | 7 |  | 
| 345 |  |  |  |  |  |  | my $p	= $q->predicate; | 
| 346 | 2 |  |  | 2 |  | 4 | return not exists $preds{ $p->value }; | 
| 347 | 2 |  |  |  |  | 6 | }); | 
| 348 | 2 |  |  |  |  | 11 | my %vars; | 
| 349 | 1 |  |  |  |  | 7 | $vars{subject}	= $s->value if ($s->does('Attean::API::Variable')); | 
| 350 | 1 |  |  |  |  | 31 | $vars{object}	= $o->value if ($o->does('Attean::API::Variable')); | 
| 351 | 1 | 50 |  |  |  | 3 | return $filter->map(sub { | 
| 352 | 1 | 50 |  |  |  | 20 | my $q	= shift; | 
| 353 |  |  |  |  |  |  | return unless $q; | 
| 354 | 1 |  |  | 1 |  | 3 | my %bindings	= map { $vars{$_} => $q->$_() } (keys %vars); | 
| 355 | 1 | 50 |  |  |  | 3 | return Attean::Result->new( bindings => \%bindings ); | 
| 356 | 1 |  |  |  |  | 4 | }, 'Attean::API::Result', variables => [values %vars]); | 
|  | 1 |  |  |  |  | 5 |  | 
| 357 | 1 |  |  |  |  | 18 | } elsif ($path->isa('Attean::Algebra::SequencePath')) { | 
| 358 | 1 |  |  |  |  | 23 | if (scalar(@children) == 1) { | 
| 359 |  |  |  |  |  |  | my $path	= Attean::Algebra::Path->new( subject => $s, path => $children[0], object => $o ); | 
| 360 | 1 | 50 |  |  |  | 4 | return $self->evaluate($path, $active_graph); | 
| 361 | 0 |  |  |  |  | 0 | } else { | 
| 362 | 0 |  |  |  |  | 0 | my @paths; | 
| 363 |  |  |  |  |  |  | my $first		= shift(@children); | 
| 364 | 1 |  |  |  |  | 3 | my $join		= Attean::Variable->new(); | 
| 365 | 1 |  |  |  |  | 2 | my @new_vars	= ($join->value); | 
| 366 | 1 |  |  |  |  | 20 | push(@paths, Attean::Algebra::Path->new( subject => $s, path => $first, object => $join )); | 
| 367 | 1 |  |  |  |  | 60 | foreach my $i (0 .. $#children) { | 
| 368 | 1 |  |  |  |  | 18 | my $newjoin	= Attean::Variable->new(); | 
| 369 | 1 |  |  |  |  | 4 | my $obj		= ($i == $#children) ? $o : $newjoin; | 
| 370 | 1 |  |  |  |  | 18 | push(@new_vars, $newjoin->value); | 
| 371 | 1 | 50 |  |  |  | 49 | push(@paths, Attean::Algebra::Path->new( subject => $join, path => $children[$i], object => $obj )); | 
| 372 | 1 |  |  |  |  | 4 | $join	= $newjoin; | 
| 373 | 1 |  |  |  |  | 16 | } | 
| 374 | 1 |  |  |  |  | 3 |  | 
| 375 |  |  |  |  |  |  | while (scalar(@paths) > 1) { | 
| 376 |  |  |  |  |  |  | my ($l, $r)	= splice(@paths, 0, 2); | 
| 377 | 1 |  |  |  |  | 3 | unshift(@paths, Attean::Algebra::Join->new( children => [$l, $r] )); | 
| 378 | 1 |  |  |  |  | 4 | } | 
| 379 | 1 |  |  |  |  | 18 | return $self->evaluate(shift(@paths), $active_graph)->map(sub { shift->project_complement(@new_vars) }); | 
| 380 |  |  |  |  |  |  | } | 
| 381 | 1 |  |  | 1 |  | 4 | } elsif ($path->isa('Attean::Algebra::ZeroOrMorePath') or $path->isa('Attean::Algebra::OneOrMorePath')) { | 
|  | 1 |  |  |  |  | 6 |  | 
| 382 |  |  |  |  |  |  | if ($s->does('Attean::API::TermOrTriple') and $o->does('Attean::API::Variable')) { | 
| 383 |  |  |  |  |  |  | my $v	= {}; | 
| 384 | 4 | 100 | 66 |  |  | 14 | if ($path->isa('Attean::Algebra::ZeroOrMorePath')) { | 
|  |  | 50 | 33 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
| 385 | 3 |  |  |  |  | 76 | $self->_ALP($active_graph, $s, $child, $v); | 
| 386 | 3 | 50 |  |  |  | 10 | } else { | 
| 387 | 3 |  |  |  |  | 9 | my $iter	= $self->_eval($active_graph, $s, $child); | 
| 388 |  |  |  |  |  |  | while (my $n = $iter->next) { | 
| 389 | 0 |  |  |  |  | 0 | $self->_ALP($active_graph, $n, $child, $v); | 
| 390 | 0 |  |  |  |  | 0 | } | 
| 391 | 0 |  |  |  |  | 0 | } | 
| 392 |  |  |  |  |  |  | my @results	= map { Attean::Result->new( bindings => { $o->value => $_ } ) } (values %$v); | 
| 393 |  |  |  |  |  |  | return Attean::ListIterator->new(variables => [$o->value], values => \@results, item_type => 'Attean::API::Result'); | 
| 394 | 3 |  |  |  |  | 10 | } elsif ($s->does('Attean::API::Variable') and $o->does('Attean::API::Variable')) { | 
|  | 6 |  |  |  |  | 155 |  | 
| 395 | 3 |  |  |  |  | 111 | my $nodes	= $self->model->graph_nodes( $active_graph ); | 
| 396 |  |  |  |  |  |  | my @results; | 
| 397 | 1 |  |  |  |  | 72 | while (my $t = $nodes->next) { | 
| 398 | 1 |  |  |  |  | 33 | my $tr		= Attean::Result->new( bindings => { $s->value => $t } ); | 
| 399 | 1 |  |  |  |  | 4 | my $p		= Attean::Algebra::Path->new( subject => $t, path => $path, object => $o ); | 
| 400 | 3 |  |  |  |  | 53 | my $iter	= $self->evaluate($p, $active_graph); | 
| 401 | 3 |  |  |  |  | 98 | while (my $r = $iter->next) { | 
| 402 | 3 |  |  |  |  | 30 | push(@results, $r->join($tr)); | 
| 403 | 3 |  |  |  |  | 336 | } | 
| 404 | 6 |  |  |  |  | 20 | } | 
| 405 |  |  |  |  |  |  | my %vars	= map { $_ => 1 } ($s->value, $o->value); | 
| 406 |  |  |  |  |  |  | return Attean::ListIterator->new(variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result'); | 
| 407 | 1 |  |  |  |  | 6 | } elsif ($s->does('Attean::API::Variable') and $o->does('Attean::API::TermOrTriple')) { | 
|  | 2 |  |  |  |  | 7 |  | 
| 408 | 1 |  |  |  |  | 21 | my $pp	= Attean::Algebra::InversePath->new( children => [$child] ); | 
| 409 |  |  |  |  |  |  | my $p	= Attean::Algebra::Path->new( subject => $o, path => $pp, object => $s ); | 
| 410 | 0 |  |  |  |  | 0 | return $self->evaluate($p, $active_graph); | 
| 411 | 0 |  |  |  |  | 0 | } else { # Term ZeroOrMorePath(path) Term | 
| 412 | 0 |  |  |  |  | 0 | my $v	= {}; | 
| 413 |  |  |  |  |  |  | $self->_ALP($active_graph, $s, $child, $v); | 
| 414 | 0 |  |  |  |  | 0 | my @results; | 
| 415 | 0 |  |  |  |  | 0 | foreach my $v (values %$v) { | 
| 416 | 0 |  |  |  |  | 0 | return Attean::ListIterator->new(variables => [], values => [Attean::Result->new()], item_type => 'Attean::API::Result') | 
| 417 | 0 |  |  |  |  | 0 | if ($v->equals($o)); | 
| 418 | 0 | 0 |  |  |  | 0 | } | 
| 419 |  |  |  |  |  |  | return Attean::ListIterator->new(variables => [], values => [], item_type => 'Attean::API::Result'); | 
| 420 |  |  |  |  |  |  | } | 
| 421 | 0 |  |  |  |  | 0 | } elsif ($path->isa('Attean::Algebra::ZeroOrOnePath')) { | 
| 422 |  |  |  |  |  |  | my $path	= Attean::Algebra::Path->new( subject => $s, path => $child, object => $o ); | 
| 423 |  |  |  |  |  |  | my @iters; | 
| 424 | 5 |  |  |  |  | 88 | my %seen; | 
| 425 | 5 |  |  |  |  | 10 | push(@iters, $self->evaluate( $path, $active_graph )->grep(sub { return not($seen{shift->as_string}++); })); | 
| 426 |  |  |  |  |  |  | push(@iters, $self->_zeroLengthPath($s, $o, $active_graph)); | 
| 427 | 5 |  |  | 1 |  | 14 | my %vars; | 
|  | 1 |  |  |  |  | 4 |  | 
| 428 | 5 |  |  |  |  | 163 | foreach my $iter (@iters) { | 
| 429 | 5 |  |  |  |  | 453 | $vars{$_}++ for (@{ $iter->variables }); | 
| 430 | 5 |  |  |  |  | 11 | } | 
| 431 | 10 |  |  |  |  | 37 | return Attean::IteratorSequence->new( iterators => \@iters, item_type => 'Attean::API::Result', variables => [keys %vars] ); | 
|  | 10 |  |  |  |  | 148 |  | 
| 432 |  |  |  |  |  |  | } | 
| 433 | 5 |  |  |  |  | 94 | die "Unimplemented path type: $path"; | 
| 434 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Project')) { | 
| 435 | 0 |  |  |  |  | 0 | my $iter	= $self->evaluate( $child, $active_graph ); | 
| 436 |  |  |  |  |  |  | my @vars	= map { $_->value } @{ $algebra->variables }; | 
| 437 | 3 |  |  |  |  | 92 | return $iter->map(sub { | 
| 438 | 3 |  |  |  |  | 98 | my $r	= shift; | 
|  | 3 |  |  |  |  | 12 |  | 
|  | 3 |  |  |  |  | 12 |  | 
| 439 |  |  |  |  |  |  | my $b	= { map { my $t	= $r->value($_); $t	? ($_ => $t) : () } @vars }; | 
| 440 | 9 |  |  | 9 |  | 15 | return Attean::Result->new( bindings => $b ); | 
| 441 | 9 | 50 |  |  |  | 14 | }, undef, variables => \@vars); #->debug('Project result'); | 
|  | 9 |  |  |  |  | 20 |  | 
|  | 9 |  |  |  |  | 45 |  | 
| 442 | 9 |  |  |  |  | 151 | } elsif ($algebra->isa('Attean::Algebra::Slice')) { | 
| 443 | 3 |  |  |  |  | 19 | my $iter	= $self->evaluate( $child, $active_graph ); | 
| 444 |  |  |  |  |  |  | $iter		= $iter->offset($algebra->offset) if ($algebra->offset > 0); | 
| 445 | 3 |  |  |  |  | 10 | $iter		= $iter->limit($algebra->limit) if ($algebra->limit >= 0); | 
| 446 | 3 | 100 |  |  |  | 113 | return $iter; | 
| 447 | 3 | 100 |  |  |  | 18 | } elsif ($algebra->isa('Attean::Algebra::Union')) { | 
| 448 | 3 |  |  |  |  | 71 | my ($lhs, $rhs)	= map { $self->evaluate($_, $active_graph) } @children; | 
| 449 |  |  |  |  |  |  | return Attean::IteratorSequence->new( | 
| 450 | 0 |  |  |  |  | 0 | iterators => [$lhs, $rhs], | 
|  | 0 |  |  |  |  | 0 |  | 
| 451 | 0 |  |  |  |  | 0 | item_type => 'Attean::API::Result', | 
| 452 |  |  |  |  |  |  | variables => [$algebra->in_scope_variables] | 
| 453 |  |  |  |  |  |  | ); | 
| 454 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Ask')) { | 
| 455 |  |  |  |  |  |  | my $iter	= $self->evaluate($child, $active_graph); | 
| 456 |  |  |  |  |  |  | my $result	= $iter->next; | 
| 457 | 0 |  |  |  |  | 0 | return Attean::ListIterator->new(values => [$result ? Attean::Literal->true : Attean::Literal->false], item_type => 'Attean::API::Term'); | 
| 458 | 0 |  |  |  |  | 0 | } elsif ($algebra->isa('Attean::Algebra::Construct')) { | 
| 459 | 0 | 0 |  |  |  | 0 | my $iter		= $self->evaluate($child, $active_graph); | 
| 460 |  |  |  |  |  |  | my $patterns	= $algebra->triples; | 
| 461 | 1 |  |  |  |  | 4 | use Data::Dumper; | 
| 462 | 1 |  |  |  |  | 35 | my %seen; | 
| 463 | 3 |  |  | 3 |  | 12328 | return Attean::CodeIterator->new( | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 3786 |  | 
| 464 | 1 |  |  |  |  | 2 | generator => sub { | 
| 465 |  |  |  |  |  |  | my $r	= $iter->next; | 
| 466 |  |  |  |  |  |  | return unless ($r); | 
| 467 | 2 |  |  | 2 |  | 6 | my %mapping	= map { my $t = $r->value($_); $t ? ("?$_" => $t) : (); } ($r->variables); | 
| 468 | 2 | 100 |  |  |  | 6 | my $mapper	= Attean::TermMap->rewrite_map(\%mapping); | 
| 469 | 1 | 50 |  |  |  | 3 | my @triples; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 9 |  | 
| 470 | 1 |  |  |  |  | 12 | PATTERN: foreach my $p (@$patterns) { | 
| 471 | 1 |  |  |  |  | 44 | my @terms	= map { | 
| 472 | 1 |  |  |  |  | 3 | ($_->does('Attean::API::TriplePattern')) | 
| 473 |  |  |  |  |  |  | ? $_->as_triple | 
| 474 | 1 | 50 |  |  |  | 5 | : $_ | 
|  | 3 |  |  |  |  | 43 |  | 
| 475 |  |  |  |  |  |  | } $p->apply_map($mapper)->values; | 
| 476 |  |  |  |  |  |  | unless (all { $_->does('Attean::API::TermOrTriple') } @terms) { | 
| 477 |  |  |  |  |  |  | next PATTERN; | 
| 478 | 1 | 50 |  |  |  | 25 | } | 
|  | 3 |  |  |  |  | 25 |  | 
| 479 | 0 |  |  |  |  | 0 | push(@triples, Attean::Triple->new(@terms)); | 
| 480 |  |  |  |  |  |  | } | 
| 481 | 1 |  |  |  |  | 37 | return @triples; | 
| 482 |  |  |  |  |  |  | }, | 
| 483 | 1 |  |  |  |  | 31 | item_type => 'Attean::API::Triple' | 
| 484 |  |  |  |  |  |  | )->grep(sub { return not($seen{shift->as_string}++); }); | 
| 485 |  |  |  |  |  |  | } elsif ($algebra->isa('Attean::Algebra::Table')) { | 
| 486 | 1 |  |  | 1 |  | 23 | my $vars	= [map { $_->value } @{ $algebra->variables }]; | 
|  | 1 |  |  |  |  | 5 |  | 
| 487 |  |  |  |  |  |  | return Attean::ListIterator->new(variables => $vars, values => $algebra->rows, item_type => 'Attean::API::Result'); | 
| 488 | 0 |  |  |  |  | 0 | } | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 489 | 0 |  |  |  |  | 0 | die "Unimplemented algebra evaluation for: $algebra"; | 
| 490 |  |  |  |  |  |  | } | 
| 491 | 0 |  |  |  |  | 0 |  | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | =item C<< evaluate_pattern( $pattern, $active_graph, \@new_vars, \%blanks ) >> | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | Returns an L<Attean::API::Iterator> object with results produced by evaluating | 
| 496 |  |  |  |  |  |  | the triple- or quad-pattern C<< $pattern >> against the evaluator's | 
| 497 |  |  |  |  |  |  | C<< model >>, using the supplied C<< $active_graph >>. | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | If the C<< ground_blanks >> option is false, replaces blank nodes in the | 
| 500 |  |  |  |  |  |  | pattern with fresh variables before evaluation, and populates C<< %blanks >> | 
| 501 |  |  |  |  |  |  | with pairs ($variable_name => $variable_node). Each new variable is also | 
| 502 |  |  |  |  |  |  | appended to C<< @new_vars >> as it is created. | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | =cut | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | my $self			= shift; | 
| 507 |  |  |  |  |  |  | my $t				= shift; | 
| 508 |  |  |  |  |  |  | my $active_graph	= shift || Carp::confess "No active-graph passed to Attean::SimpleQueryEvaluator->evaluate"; | 
| 509 | 24 |  |  | 24 | 1 | 43 | my $new_vars		= shift; | 
| 510 | 24 |  |  |  |  | 34 | my $blanks			= shift; | 
| 511 | 24 |  | 33 |  |  | 51 | my $q				= $t->as_quad_pattern($active_graph); | 
| 512 | 24 |  |  |  |  | 27 | my @values; | 
| 513 | 24 |  |  |  |  | 28 | foreach my $v ($q->values) { | 
| 514 | 24 |  |  |  |  | 84 | if (not($self->ground_blanks) and $v->does('Attean::API::Blank')) { | 
| 515 | 24 |  |  |  |  | 812 | unless (exists $blanks->{$v->value}) { | 
| 516 | 24 |  |  |  |  | 68 | $blanks->{$v->value}	= Attean::Variable->new(); | 
| 517 | 96 | 100 | 100 |  |  | 1351 | push(@$new_vars, $blanks->{$v->value}->value); | 
| 518 | 2 | 100 |  |  |  | 56 | } | 
| 519 | 1 |  |  |  |  | 15 | push(@values, $blanks->{$v->value}); | 
| 520 | 1 |  |  |  |  | 54 | } else { | 
| 521 |  |  |  |  |  |  | push(@values, $v); | 
| 522 | 2 |  |  |  |  | 6 | } | 
| 523 |  |  |  |  |  |  | } | 
| 524 | 94 |  |  |  |  | 2041 | return $self->model->get_bindings( @values ); | 
| 525 |  |  |  |  |  |  | } | 
| 526 |  |  |  |  |  |  |  | 
| 527 | 24 |  |  |  |  | 112 | my $self	= shift; | 
| 528 |  |  |  |  |  |  | my $graph	= shift; | 
| 529 |  |  |  |  |  |  | my $term	= shift; | 
| 530 |  |  |  |  |  |  | my $path	= shift; | 
| 531 | 6 |  |  | 6 |  | 10 | my $v		= shift; | 
| 532 | 6 |  |  |  |  | 7 | return if (exists $v->{ $term->as_string }); | 
| 533 | 6 |  |  |  |  | 7 | $v->{ $term->as_string }	= $term; | 
| 534 | 6 |  |  |  |  | 8 |  | 
| 535 | 6 |  |  |  |  | 8 | my $iter	= $self->_eval($graph, $term, $path); | 
| 536 | 6 | 50 |  |  |  | 16 | while (my $n = $iter->next) { | 
| 537 | 6 |  |  |  |  | 326 | $self->_ALP($graph, $n, $path, $v); | 
| 538 |  |  |  |  |  |  | } | 
| 539 | 6 |  |  |  |  | 294 | } | 
| 540 | 6 |  |  |  |  | 179 |  | 
| 541 | 3 |  |  |  |  | 11 | my $self	= shift; | 
| 542 |  |  |  |  |  |  | my $graph	= shift; | 
| 543 |  |  |  |  |  |  | my $term	= shift; | 
| 544 |  |  |  |  |  |  | my $path	= shift; | 
| 545 |  |  |  |  |  |  | my $pp		= Attean::Algebra::Path->new( subject => $term, path => $path, object => variable('o') ); | 
| 546 | 6 |  |  | 6 |  | 9 | my $iter	= $self->evaluate($pp, $graph); | 
| 547 | 6 |  |  |  |  | 8 | my $terms	= $iter->map(sub { shift->value('o') }, 'Attean::API::Term'); | 
| 548 | 6 |  |  |  |  | 18 | my %seen; | 
| 549 | 6 |  |  |  |  | 9 | return $terms->grep(sub { not $seen{ shift->as_string }++ }); | 
| 550 | 6 |  |  |  |  | 18 | } | 
| 551 | 6 |  |  |  |  | 19 |  | 
| 552 | 6 |  |  | 3 |  | 226 | my $self	= shift; | 
|  | 3 |  |  |  |  | 13 |  | 
| 553 | 6 |  |  |  |  | 167 | my $s		= shift; | 
| 554 | 6 |  |  | 3 |  | 39 | my $o		= shift; | 
|  | 3 |  |  |  |  | 11 |  | 
| 555 |  |  |  |  |  |  | my $graph	= shift; | 
| 556 |  |  |  |  |  |  | my $s_term	= ($s->does('Attean::API::TermOrTriple')); | 
| 557 |  |  |  |  |  |  | my $o_term	= ($o->does('Attean::API::TermOrTriple')); | 
| 558 | 5 |  |  | 5 |  | 9 | if ($s_term and $o_term) { | 
| 559 | 5 |  |  |  |  | 8 | my @r; | 
| 560 | 5 |  |  |  |  | 6 | push(@r, Attean::Result->new()) if ($s->equals($o)); | 
| 561 | 5 |  |  |  |  | 7 | return Attean::ListIterator->new(variables => [], values => \@r, item_type => 'Attean::API::Result'); | 
| 562 | 5 |  |  |  |  | 11 | } elsif ($s_term) { | 
| 563 | 5 |  |  |  |  | 71 | my $name	= $o->value; | 
| 564 | 5 | 100 | 100 |  |  | 78 | my $r		= Attean::Result->new( bindings => { $name => $s } ); | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 565 | 2 |  |  |  |  | 4 | return Attean::ListIterator->new(variables => [$name], values => [$r], item_type => 'Attean::API::Result'); | 
| 566 | 2 | 100 |  |  |  | 6 | } elsif ($o_term) { | 
| 567 | 2 |  |  |  |  | 54 | my $name	= $s->value; | 
| 568 |  |  |  |  |  |  | my $r		= Attean::Result->new( bindings => { $name => $o } ); | 
| 569 | 1 |  |  |  |  | 4 | return Attean::ListIterator->new(variables => [$name], values => [$r], item_type => 'Attean::API::Result'); | 
| 570 | 1 |  |  |  |  | 21 | } else { | 
| 571 | 1 |  |  |  |  | 36 | my @vars	= map { $_->value } ($s, $o); | 
| 572 |  |  |  |  |  |  | my $nodes	= $self->model->graph_nodes( $graph ); | 
| 573 | 1 |  |  |  |  | 4 | return $nodes->map( | 
| 574 | 1 |  |  |  |  | 27 | sub { | 
| 575 | 1 |  |  |  |  | 40 | my $term	= shift; | 
| 576 |  |  |  |  |  |  | Attean::Result->new( bindings => { map { $_ => $term } @vars } ); | 
| 577 | 1 |  |  |  |  | 3 | }, | 
|  | 2 |  |  |  |  | 7 |  | 
| 578 | 1 |  |  |  |  | 8 | 'Attean::API::Result', | 
| 579 |  |  |  |  |  |  | variables => \@vars | 
| 580 |  |  |  |  |  |  | ); | 
| 581 | 5 |  |  | 5 |  | 8 | } | 
| 582 | 5 |  |  |  |  | 10 | } | 
|  | 10 |  |  |  |  | 99 |  | 
| 583 |  |  |  |  |  |  | } | 
| 584 | 1 |  |  |  |  | 38 |  | 
| 585 |  |  |  |  |  |  | use Moo; | 
| 586 |  |  |  |  |  |  | use Attean::RDF; | 
| 587 |  |  |  |  |  |  | use Scalar::Util qw(blessed); | 
| 588 |  |  |  |  |  |  | use Types::Standard qw(InstanceOf); | 
| 589 |  |  |  |  |  |  | use URI::Escape qw(uri_escape_utf8); | 
| 590 |  |  |  |  |  |  | use Encode qw(encode); | 
| 591 |  |  |  |  |  |  | use POSIX qw(ceil floor); | 
| 592 | 3 |  |  | 3 |  | 2557 | use Digest; | 
|  | 3 |  |  |  |  | 9 |  | 
|  | 3 |  |  |  |  | 13 |  | 
| 593 | 3 |  |  | 3 |  | 934 | use UUID::Tiny ':std'; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 23 |  | 
| 594 | 3 |  |  | 3 |  | 2186 | use List::MoreUtils qw(zip); | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 105 |  | 
| 595 | 3 |  |  | 3 |  | 15 | use DateTime::Format::W3CDTF; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 14 |  | 
| 596 | 3 |  |  | 3 |  | 1232 | use I18N::LangTags; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 126 |  | 
| 597 | 3 |  |  | 3 |  | 18 | use namespace::clean; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 108 |  | 
| 598 | 3 |  |  | 3 |  | 17 |  | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 30 |  | 
| 599 | 3 |  |  | 3 |  | 1700 | has 'evaluator'	=> (is => 'ro', isa => InstanceOf['Attean::SimpleQueryEvaluator']); | 
|  | 3 |  |  |  |  | 1507 |  | 
|  | 3 |  |  |  |  | 84 |  | 
| 600 | 3 |  |  | 3 |  | 19 | my $self			= shift; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 577 |  | 
| 601 | 3 |  |  | 3 |  | 21 | my $expr			= shift; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 21 |  | 
| 602 | 3 |  |  | 3 |  | 2694 | my $row				= shift; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 62 |  | 
| 603 | 3 |  |  | 3 |  | 15 | my $active_graph	= shift; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 79 |  | 
| 604 | 3 |  |  | 3 |  | 13 | my $row_cache		= shift || {}; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 16 |  | 
| 605 |  |  |  |  |  |  | my $impl			= $self->impl($expr, $active_graph); | 
| 606 |  |  |  |  |  |  | return eval { $impl->($row, row_cache => $row_cache) }; | 
| 607 |  |  |  |  |  |  | } | 
| 608 | 53 |  |  | 53 | 0 | 1922 |  | 
| 609 | 53 |  |  |  |  | 68 | my $self			= shift; | 
| 610 | 53 |  |  |  |  | 68 | my $expr			= shift; | 
| 611 | 53 |  |  |  |  | 64 | my $active_graph	= shift; | 
| 612 | 53 |  | 100 |  |  | 152 | my $op		= $expr->operator; | 
| 613 | 53 |  |  |  |  | 114 | my $true	= Attean::Literal->true; | 
| 614 | 53 |  |  |  |  | 82 | my $false	= Attean::Literal->false; | 
|  | 53 |  |  |  |  | 92 |  | 
| 615 |  |  |  |  |  |  | if ($expr->isa('Attean::ExistsExpression')) { | 
| 616 |  |  |  |  |  |  | my $pattern		= $expr->pattern; | 
| 617 |  |  |  |  |  |  | return sub { | 
| 618 | 127 |  |  | 127 | 0 | 148 | my $r			= shift; | 
| 619 | 127 |  |  |  |  | 142 | my $table		= Attean::Algebra::Table->new( variables => [map { variable($_) } $r->variables], rows => [$r] ); | 
| 620 | 127 |  |  |  |  | 155 | my $join		= Attean::Algebra::Join->new( children => [$table, $pattern] ); | 
| 621 | 127 |  |  |  |  | 217 | # TODO: substitute variables at top-level of EXISTS pattern | 
| 622 | 127 |  |  |  |  | 273 | my $iter	= $self->evaluator->evaluate($join, $active_graph); | 
| 623 | 127 |  |  |  |  | 219 | return ($iter->next) ? $true : $false; | 
| 624 | 127 | 50 |  |  |  | 613 | }; | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 625 | 0 |  |  |  |  | 0 | } elsif ($expr->isa('Attean::ValueExpression')) { | 
| 626 |  |  |  |  |  |  | my $node	= $expr->value; | 
| 627 | 0 |  |  | 0 |  | 0 | if ($node->does('Attean::API::Variable')) { | 
| 628 | 0 |  |  |  |  | 0 | return sub { return shift->value($node->value); }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 629 | 0 |  |  |  |  | 0 | } else { | 
| 630 |  |  |  |  |  |  | return sub { return $node }; | 
| 631 | 0 |  |  |  |  | 0 | } | 
| 632 | 0 | 0 |  |  |  | 0 | } elsif ($expr->isa('Attean::UnaryExpression')) { | 
| 633 | 0 |  |  |  |  | 0 | my ($child)	= @{ $expr->children }; | 
| 634 |  |  |  |  |  |  | my $impl	= $self->impl($child, $active_graph); | 
| 635 | 88 |  |  |  |  | 151 | if ($op eq '!') { | 
| 636 | 88 | 100 |  |  |  | 182 | return sub { | 
| 637 | 29 |  |  | 29 |  | 440 | my $term	= $impl->(@_); | 
|  | 29 |  |  |  |  | 69 |  | 
| 638 |  |  |  |  |  |  | return ($term->ebv) ? $false : $true; | 
| 639 | 59 |  |  | 55 |  | 1355 | } | 
|  | 55 |  |  |  |  | 97 |  | 
| 640 |  |  |  |  |  |  | } elsif ($op eq '-' or $op eq '+') { | 
| 641 |  |  |  |  |  |  | return sub { | 
| 642 | 0 |  |  |  |  | 0 | my $term	= $impl->(@_); | 
|  | 0 |  |  |  |  | 0 |  | 
| 643 | 0 |  |  |  |  | 0 | die "TypeError $op" unless (blessed($term) and $term->does('Attean::API::NumericLiteral')); | 
| 644 | 0 | 0 | 0 |  |  | 0 | my $v	= $term->numeric_value; | 
|  |  | 0 |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | return Attean::Literal->new( value => eval "$op$v", datatype => $term->datatype ); | 
| 646 | 0 |  |  | 0 |  | 0 | }; | 
| 647 | 0 | 0 |  |  |  | 0 | } | 
| 648 |  |  |  |  |  |  | die "Unimplemented UnaryExpression evaluation: " . $expr->operator; | 
| 649 | 0 |  |  |  |  | 0 | } elsif ($expr->isa('Attean::BinaryExpression')) { | 
| 650 |  |  |  |  |  |  | my ($lhs, $rhs)	= @{ $expr->children }; | 
| 651 | 0 |  |  | 0 |  | 0 | my ($lhsi, $rhsi)	= map { $self->impl($_, $active_graph) } ($lhs, $rhs); | 
| 652 | 0 | 0 | 0 |  |  | 0 | if ($op eq '&&') { | 
| 653 | 0 |  |  |  |  | 0 | return sub { | 
| 654 | 0 |  |  |  |  | 0 | my ($r, %args)	= @_; | 
| 655 | 0 |  |  |  |  | 0 | my $lbv	= eval { $lhsi->($r, %args) }; | 
| 656 |  |  |  |  |  |  | my $rbv	= eval { $rhsi->($r, %args) }; | 
| 657 | 0 |  |  |  |  | 0 | die "TypeError $op" unless ($lbv or $rbv); | 
| 658 |  |  |  |  |  |  | return $false if (not($lbv) and not($rbv->ebv)); | 
| 659 | 35 |  |  |  |  | 45 | return $false if (not($rbv) and not($lbv->ebv)); | 
|  | 35 |  |  |  |  | 71 |  | 
| 660 | 35 |  |  |  |  | 61 | die "TypeError $op" unless ($lbv and $rbv); | 
|  | 70 |  |  |  |  | 131 |  | 
| 661 | 35 | 100 |  |  |  | 101 | return ($lbv->ebv && $rbv->ebv) ? $true : $false; | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 662 |  |  |  |  |  |  | } | 
| 663 | 9 |  |  | 9 |  | 20 | } elsif ($op eq '||') { | 
| 664 | 9 |  |  |  |  | 15 | return sub { | 
|  | 9 |  |  |  |  | 23 |  | 
| 665 | 9 |  |  |  |  | 506 | my ($r, %args)	= @_; | 
|  | 9 |  |  |  |  | 20 |  | 
| 666 | 9 | 100 | 100 |  |  | 556 | my $lbv	= eval { $lhsi->($r, %args) }; | 
| 667 | 8 | 100 | 100 |  |  | 22 | return $true if ($lbv and $lbv->ebv); | 
| 668 | 7 | 100 | 100 |  |  | 17 | my $rbv	= eval { $rhsi->($r, %args) }; | 
| 669 | 6 | 100 | 100 |  |  | 49 | die "TypeError $op" unless ($rbv); | 
| 670 | 4 | 100 | 100 |  |  | 13 | return $true if ($rbv->ebv); | 
| 671 |  |  |  |  |  |  | return $false if ($lbv); | 
| 672 | 9 |  |  |  |  | 37 | die "TypeError $op"; | 
| 673 |  |  |  |  |  |  | } | 
| 674 | 9 |  |  | 9 |  | 21 | } elsif ($op =~ m#^(?:[-+*/])$#) { # numeric operators: - + * / | 
| 675 | 9 |  |  |  |  | 13 | return sub { | 
|  | 9 |  |  |  |  | 24 |  | 
| 676 | 9 | 100 | 100 |  |  | 517 | my ($r, %args)	= @_; | 
| 677 | 6 |  |  |  |  | 12 | ($lhs, $rhs)	= map { $_->($r, %args) } ($lhsi, $rhsi); | 
|  | 6 |  |  |  |  | 14 |  | 
| 678 | 6 | 100 |  |  |  | 446 | for ($lhs, $rhs) { die "TypeError $op" unless (blessed($_) and $_->does('Attean::API::NumericLiteral')); } | 
| 679 | 4 | 100 |  |  |  | 11 | my $lv	= $lhs->numeric_value; | 
| 680 | 2 | 100 |  |  |  | 12 | my $rv	= $rhs->numeric_value; | 
| 681 | 1 |  |  |  |  | 17 | return Attean::Literal->new( value => eval "$lv $op $rv", datatype => $lhs->binary_promotion_type($rhs, $op) ); | 
| 682 |  |  |  |  |  |  | }; | 
| 683 | 9 |  |  |  |  | 34 | } elsif ($op =~ /^!?=$/) { | 
| 684 |  |  |  |  |  |  | return sub { | 
| 685 | 16 |  |  | 16 |  | 35 | my ($r, %args)	= @_; | 
| 686 | 16 |  |  |  |  | 27 | ($lhs, $rhs)	= map { $_->($r, %args) } ($lhsi, $rhsi); | 
|  | 32 |  |  |  |  | 59 |  | 
| 687 | 16 | 100 | 66 |  |  | 29 | for ($lhs, $rhs) { die "TypeError $op" unless (blessed($_) and $_->does('Attean::API::TermOrTriple')); } | 
|  | 32 |  |  |  |  | 383 |  | 
| 688 | 15 |  |  |  |  | 204 | my $ok; | 
| 689 | 15 |  |  |  |  | 43 | if ($lhs->does('Attean::API::Binding')) { | 
| 690 | 15 |  |  |  |  | 551 | $ok	= $lhs->equals($rhs); | 
| 691 | 17 |  |  |  |  | 77 | } else { | 
| 692 |  |  |  |  |  |  | $ok	= $lhs->equals($rhs); | 
| 693 |  |  |  |  |  |  | } | 
| 694 | 0 |  |  | 0 |  | 0 | $ok		= not($ok) if ($op eq '!='); | 
| 695 | 0 |  |  |  |  | 0 | return $ok ? $true : $false; | 
|  | 0 |  |  |  |  | 0 |  | 
| 696 | 0 | 0 | 0 |  |  | 0 | } | 
|  | 0 |  |  |  |  | 0 |  | 
| 697 | 0 |  |  |  |  | 0 | } elsif ($op =~ /^[<>]=?$/) { | 
| 698 | 0 | 0 |  |  |  | 0 | return sub { | 
| 699 | 0 |  |  |  |  | 0 | my ($r, %args)	= @_; | 
| 700 |  |  |  |  |  |  | ($lhs, $rhs)	= map { $_->($r, %args) } ($lhsi, $rhsi); | 
| 701 | 0 |  |  |  |  | 0 | for ($lhs, $rhs) { | 
| 702 |  |  |  |  |  |  | die "TypeError $op" unless $_->does('Attean::API::TermOrTriple'); | 
| 703 | 0 | 0 |  |  |  | 0 | die "TypeError $op" if ($_->does('Attean::API::IRI')); # comparison of IRIs is only defined for `ORDER BY`, not for general expressions | 
| 704 | 0 | 0 |  |  |  | 0 | } | 
| 705 |  |  |  |  |  |  |  | 
| 706 | 0 |  |  |  |  | 0 | my $c	= ($lhs->compare($rhs)); | 
| 707 |  |  |  |  |  |  | return $true if (($c < 0 and ($op =~ /<=?/)) or ($c > 0 and ($op =~ />=?/)) or ($c == 0 and ($op =~ /=/))); | 
| 708 | 0 |  |  | 0 |  | 0 | return $false; | 
| 709 | 0 |  |  |  |  | 0 | } | 
|  | 0 |  |  |  |  | 0 |  | 
| 710 | 0 |  |  |  |  | 0 | } | 
| 711 | 0 | 0 |  |  |  | 0 | die "Unexpected operator evaluation: $op"; | 
| 712 | 0 | 0 |  |  |  | 0 | } elsif ($expr->isa('Attean::FunctionExpression')) { | 
| 713 |  |  |  |  |  |  | my $func		= $expr->operator; | 
| 714 |  |  |  |  |  |  | my @children	= map { $self->impl($_, $active_graph) } @{ $expr->children }; | 
| 715 | 0 |  |  |  |  | 0 | my %type_roles		= qw(URI IRI IRI IRI BLANK Blank LITERAL Literal NUMERIC NumericLiteral TRIPLE Triple); | 
| 716 | 0 | 0 | 0 |  |  | 0 | my %type_classes	= qw(URI Attean::IRI IRI Attean::IRI STR Attean::Literal); | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 717 | 0 |  |  |  |  | 0 | return sub { | 
| 718 |  |  |  |  |  |  | my ($r, %args)	= @_; | 
| 719 | 0 |  |  |  |  | 0 | my $row_cache	= $args{row_cache} || {}; | 
| 720 | 0 |  |  |  |  | 0 |  | 
| 721 |  |  |  |  |  |  | if ($func eq 'IF') { | 
| 722 | 0 |  |  |  |  | 0 | my $term	= $children[0]->( $r, %args ); | 
| 723 | 0 |  |  |  |  | 0 | return ($term->ebv) ? $children[1]->( $r, %args ) : $children[2]->( $r, %args ); | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 724 | 0 |  |  |  |  | 0 | } elsif ($func eq 'IN' or $func eq 'NOTIN') { | 
| 725 | 0 |  |  |  |  | 0 | ($true, $false)	= ($false, $true) if ($func eq 'NOTIN'); | 
| 726 |  |  |  |  |  |  | my $child	= shift(@children); | 
| 727 | 0 |  |  | 0 |  | 0 | my $term	= $child->( $r, %args ); | 
| 728 | 0 |  | 0 |  |  | 0 | foreach my $c (@children) { | 
| 729 |  |  |  |  |  |  | if (my $value = eval { $c->( $r, %args ) }) { | 
| 730 | 0 | 0 | 0 |  |  | 0 | return $true if ($term->equals($value)); | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 731 | 0 |  |  |  |  | 0 | } | 
| 732 | 0 | 0 |  |  |  | 0 | } | 
| 733 |  |  |  |  |  |  | return $false; | 
| 734 | 0 | 0 |  |  |  | 0 | } elsif ($func eq 'COALESCE') { | 
| 735 | 0 |  |  |  |  | 0 | foreach my $c (@children) { | 
| 736 | 0 |  |  |  |  | 0 | my $t	= eval { $c->( $r, %args ) }; | 
| 737 | 0 |  |  |  |  | 0 | next if ($@); | 
| 738 | 0 | 0 |  |  |  | 0 | return $t if $t; | 
|  | 0 |  |  |  |  | 0 |  | 
| 739 | 0 | 0 |  |  |  | 0 | } | 
| 740 |  |  |  |  |  |  | return; | 
| 741 |  |  |  |  |  |  | } | 
| 742 | 0 |  |  |  |  | 0 |  | 
| 743 |  |  |  |  |  |  | my @operands	= map { $_->( $r, %args ) } @children; | 
| 744 | 0 |  |  |  |  | 0 | if ($func =~ /^(STR)$/) { | 
| 745 | 0 |  |  |  |  | 0 | return $type_classes{$1}->new($operands[0]->value); | 
|  | 0 |  |  |  |  | 0 |  | 
| 746 | 0 | 0 |  |  |  | 0 | } elsif ($func =~ /^(SUBJECT|PREDICATE|OBJECT)$/) { | 
| 747 | 0 | 0 |  |  |  | 0 | my $pos	= lc($func); | 
| 748 |  |  |  |  |  |  | my $term	= $operands[0]->$pos(); | 
| 749 | 0 |  |  |  |  | 0 | return $term; | 
| 750 |  |  |  |  |  |  | } elsif ($func =~ /^([UI]RI)$/) { | 
| 751 |  |  |  |  |  |  | my @base	= $expr->has_base ? (base => $expr->base) : (); | 
| 752 | 0 |  |  |  |  | 0 | return $type_classes{$1}->new(value => $operands[0]->value, @base); | 
|  | 0 |  |  |  |  | 0 |  | 
| 753 | 0 | 0 | 0 |  |  | 0 | } elsif ($func eq 'BNODE') { | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 754 | 0 |  |  |  |  | 0 | if (scalar(@operands)) { | 
| 755 |  |  |  |  |  |  | my $name	= $operands[0]->value; | 
| 756 | 0 |  |  |  |  | 0 | if (my $b = $row_cache->{bnodes}{$name}) { | 
| 757 | 0 |  |  |  |  | 0 | return $b; | 
| 758 | 0 |  |  |  |  | 0 | } else { | 
| 759 |  |  |  |  |  |  | my $b	= Attean::Blank->new(); | 
| 760 | 0 | 0 |  |  |  | 0 | $row_cache->{bnodes}{$name}	= $b; | 
| 761 | 0 |  |  |  |  | 0 | return $b; | 
| 762 |  |  |  |  |  |  | } | 
| 763 | 0 | 0 |  |  |  | 0 | } | 
| 764 | 0 |  |  |  |  | 0 | return Attean::Blank->new(); | 
| 765 | 0 | 0 |  |  |  | 0 | } elsif ($func eq 'LANG') { | 
| 766 | 0 |  |  |  |  | 0 | die "TypeError: LANG" unless ($operands[0]->does('Attean::API::Literal')); | 
| 767 |  |  |  |  |  |  | return Attean::Literal->new($operands[0]->language // ''); | 
| 768 | 0 |  |  |  |  | 0 | } elsif ($func eq 'LANGMATCHES') { | 
| 769 | 0 |  |  |  |  | 0 | my ($lang, $match)	= map { $_->value } @operands; | 
| 770 | 0 |  |  |  |  | 0 | if ($match eq '*') { | 
| 771 |  |  |  |  |  |  | # """A language-range of "*" matches any non-empty language-tag string.""" | 
| 772 |  |  |  |  |  |  | return ($lang ? $true : $false); | 
| 773 | 0 |  |  |  |  | 0 | } else { | 
| 774 |  |  |  |  |  |  | return (I18N::LangTags::is_dialect_of( $lang, $match )) ? $true : $false; | 
| 775 | 0 | 0 |  |  |  | 0 | } | 
| 776 | 0 |  | 0 |  |  | 0 | } elsif ($func eq 'DATATYPE') { | 
| 777 |  |  |  |  |  |  | return $operands[0]->datatype; | 
| 778 | 0 |  |  |  |  | 0 | } elsif ($func eq 'BOUND') { | 
|  | 0 |  |  |  |  | 0 |  | 
| 779 | 0 | 0 |  |  |  | 0 | return $operands[0] ? $true : $false; | 
| 780 |  |  |  |  |  |  | } elsif ($func eq 'RAND') { | 
| 781 | 0 | 0 |  |  |  | 0 | return Attean::Literal->new( value => rand(), datatype => 'http://www.w3.org/2001/XMLSchema#double' ); | 
| 782 |  |  |  |  |  |  | } elsif ($func eq 'ABS') { | 
| 783 | 0 | 0 |  |  |  | 0 | return Attean::Literal->new( value => abs($operands[0]->value), $operands[0]->construct_args ); | 
| 784 |  |  |  |  |  |  | } elsif ($func =~ /^(?:CEIL|FLOOR)$/) { | 
| 785 |  |  |  |  |  |  | my $v	= $operands[0]->value; | 
| 786 | 0 |  |  |  |  | 0 | return Attean::Literal->new( value => (($func eq 'CEIL') ? ceil($v) : floor($v)), $operands[0]->construct_args ); | 
| 787 |  |  |  |  |  |  | } elsif ($func eq 'ROUND') { | 
| 788 | 0 | 0 |  |  |  | 0 | return Attean::Literal->new( value => sprintf('%.0f', (0.000000000000001 + $operands[0]->numeric_value)), $operands[0]->construct_args ); | 
| 789 |  |  |  |  |  |  | } elsif ($func eq 'CONCAT') { | 
| 790 | 0 |  |  |  |  | 0 | my $all_lang	= 1; | 
| 791 |  |  |  |  |  |  | my $all_str		= 1; | 
| 792 | 0 |  |  |  |  | 0 | my $lang; | 
| 793 |  |  |  |  |  |  | foreach my $n (@operands) { | 
| 794 | 0 |  |  |  |  | 0 | die "CONCAT called with a non-literal argument" unless ($n->does('Attean::API::Literal')); | 
| 795 | 0 | 0 |  |  |  | 0 | if ($n->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string') { | 
| 796 |  |  |  |  |  |  | die "CONCAT called with a datatyped-literal other than xsd:string"; | 
| 797 | 0 |  |  |  |  | 0 | } elsif ($n->language) { | 
| 798 |  |  |  |  |  |  | $all_str	= 0; | 
| 799 | 0 |  |  |  |  | 0 | if (defined($lang) and $lang ne $n->language) { | 
| 800 | 0 |  |  |  |  | 0 | $all_lang	= 0; | 
| 801 | 0 |  |  |  |  | 0 | } else { | 
| 802 | 0 |  |  |  |  | 0 | $lang	= $n->language; | 
| 803 | 0 | 0 |  |  |  | 0 | } | 
| 804 | 0 | 0 |  |  |  | 0 | } else { | 
|  |  | 0 |  |  |  |  |  | 
| 805 | 0 |  |  |  |  | 0 | $all_lang	= 0; | 
| 806 |  |  |  |  |  |  | $all_str	= 0; | 
| 807 | 0 |  |  |  |  | 0 | } | 
| 808 | 0 | 0 | 0 |  |  | 0 | } | 
| 809 | 0 |  |  |  |  | 0 | my %strtype; | 
| 810 |  |  |  |  |  |  | if ($all_lang and $lang) { | 
| 811 | 0 |  |  |  |  | 0 | $strtype{language}	= $lang; | 
| 812 |  |  |  |  |  |  | } elsif ($all_str) { | 
| 813 |  |  |  |  |  |  | $strtype{datatype}	= 'http://www.w3.org/2001/XMLSchema#string' | 
| 814 | 0 |  |  |  |  | 0 | } | 
| 815 | 0 |  |  |  |  | 0 | return Attean::Literal->new( value => join('', map { $_->value } @operands), %strtype ); | 
| 816 |  |  |  |  |  |  | } elsif ($func eq 'SUBSTR') { | 
| 817 |  |  |  |  |  |  | my $str		= shift(@operands); | 
| 818 | 0 |  |  |  |  | 0 | my @args	= map { $_->numeric_value } @operands; | 
| 819 | 0 | 0 | 0 |  |  | 0 | my $v		= scalar(@args == 1) ? substr($str->value, $args[0]-1) : substr($str->value, $args[0]-1, $args[1]); | 
|  |  | 0 |  |  |  |  |  | 
| 820 | 0 |  |  |  |  | 0 | return Attean::Literal->new( value => $v, $str->construct_args ); | 
| 821 |  |  |  |  |  |  | } elsif ($func eq 'STRLEN') { | 
| 822 | 0 |  |  |  |  | 0 | return Attean::Literal->integer(length($operands[0]->value)); | 
| 823 |  |  |  |  |  |  | } elsif ($func eq 'REPLACE') { | 
| 824 | 0 |  |  |  |  | 0 | my ($node, $pat, $rep)	= @operands; | 
|  | 0 |  |  |  |  | 0 |  | 
| 825 |  |  |  |  |  |  | die "TypeError: REPLACE called without a literal arg1 term" unless (blessed($node) and $node->does('Attean::API::Literal')); | 
| 826 | 0 |  |  |  |  | 0 | die "TypeError: REPLACE called without a literal arg2 term" unless (blessed($pat) and $pat->does('Attean::API::Literal')); | 
| 827 | 0 |  |  |  |  | 0 | die "TypeError: REPLACE called without a literal arg3 term" unless (blessed($rep) and $rep->does('Attean::API::Literal')); | 
|  | 0 |  |  |  |  | 0 |  | 
| 828 | 0 | 0 |  |  |  | 0 | die "TypeError: REPLACE called with a datatyped (non-xsd:string) literal" if ($node->datatype and $node->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string'); | 
| 829 | 0 |  |  |  |  | 0 | my ($value, $pattern, $replace)	= map { $_->value } @operands; | 
| 830 |  |  |  |  |  |  | die "EvaluationError: REPLACE called with unsafe ?{} match pattern" if (index($pattern, '(?{') != -1 or index($pattern, '(??{') != -1); | 
| 831 | 0 |  |  |  |  | 0 | die "EvaluationError: REPLACE called with unsafe ?{} replace pattern" if (index($replace, '(?{') != -1 or index($replace, '(??{') != -1); | 
| 832 |  |  |  |  |  |  |  | 
| 833 | 0 |  |  |  |  | 0 | $replace	=~ s/\\/\\\\/g; | 
| 834 | 0 | 0 | 0 |  |  | 0 | $replace	=~ s/\$(\d+)/\$$1/g; | 
| 835 | 0 | 0 | 0 |  |  | 0 | $replace	=~ s/"/\\"/g; | 
| 836 | 0 | 0 | 0 |  |  | 0 | $replace	= qq["$replace"]; | 
| 837 | 0 | 0 | 0 |  |  | 0 | no warnings 'uninitialized'; | 
| 838 | 0 |  |  |  |  | 0 | $value	=~ s/$pattern/"$replace"/eeg; | 
|  | 0 |  |  |  |  | 0 |  | 
| 839 | 0 | 0 | 0 |  |  | 0 | return Attean::Literal->new(value => $value, $node->construct_args); | 
| 840 | 0 | 0 | 0 |  |  | 0 | } elsif ($func =~ /^[UL]CASE$/) { | 
| 841 |  |  |  |  |  |  | return Attean::Literal->new( value => ($func eq 'UCASE' ? uc($operands[0]->value) : lc($operands[0]->value) ), $operands[0]->construct_args ); | 
| 842 | 0 |  |  |  |  | 0 | } elsif ($func eq 'ENCODE_FOR_URI') { | 
| 843 | 0 |  |  |  |  | 0 | return Attean::Literal->new( uri_escape_utf8($operands[0]->value) ); | 
| 844 | 0 |  |  |  |  | 0 | } elsif ($func eq 'CONTAINS') { | 
| 845 | 0 |  |  |  |  | 0 | my ($node, $pat)	= @operands; | 
| 846 | 3 |  |  | 3 |  | 9209 | my ($lit, $plit)	= map { $_->value } @operands; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 8799 |  | 
| 847 | 0 |  |  |  |  | 0 | die "TypeError: CONTAINS" if ($node->language and $pat->language and $node->language ne $pat->language); | 
|  | 0 |  |  |  |  | 0 |  | 
| 848 | 0 |  |  |  |  | 0 | return (index($lit, $plit) >= 0) ? $true : $false; | 
| 849 |  |  |  |  |  |  | } elsif ($func eq 'STRSTARTS' or $func eq 'STRENDS') { | 
| 850 | 0 | 0 |  |  |  | 0 | my ($lit, $plit)	= map { $_->value } @operands; | 
| 851 |  |  |  |  |  |  | if ($func eq 'STRENDS') { | 
| 852 | 0 |  |  |  |  | 0 | my $pos		= length($lit) - length($plit); | 
| 853 |  |  |  |  |  |  | return (rindex($lit, $plit) == $pos) ? $true : $false; | 
| 854 | 0 |  |  |  |  | 0 | } else { | 
| 855 | 0 |  |  |  |  | 0 | return (index($lit, $plit) == 0) ? $true : $false; | 
|  | 0 |  |  |  |  | 0 |  | 
| 856 | 0 | 0 | 0 |  |  | 0 | } | 
|  |  |  | 0 |  |  |  |  | 
| 857 | 0 | 0 |  |  |  | 0 | } elsif ($func eq 'STRBEFORE' or $func eq 'STRAFTER') { | 
| 858 |  |  |  |  |  |  | my ($node, $substr)	= @operands; | 
| 859 | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 860 | 0 | 0 |  |  |  | 0 | die "$func called without a literal arg1 term" unless (blessed($node) and $node->does('Attean::API::Literal')); | 
| 861 | 0 |  |  |  |  | 0 | die "$func called without a literal arg2 term" unless (blessed($substr) and $substr->does('Attean::API::Literal')); | 
| 862 | 0 | 0 |  |  |  | 0 | die "$func called with a datatyped (non-xsd:string) literal" if ($node->datatype and $node->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string'); | 
| 863 |  |  |  |  |  |  |  | 
| 864 | 0 | 0 |  |  |  | 0 | my $lhs_simple	= (not($node->language) and ($node->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string')); | 
| 865 |  |  |  |  |  |  | my $rhs_simple	= (not($substr->language) and ($substr->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string')); | 
| 866 |  |  |  |  |  |  | if ($lhs_simple and $rhs_simple) { | 
| 867 | 0 |  |  |  |  | 0 | # ok | 
| 868 |  |  |  |  |  |  | } elsif ($node->language and $substr->language and $node->language eq $substr->language) { | 
| 869 | 0 | 0 | 0 |  |  | 0 | # ok | 
| 870 | 0 | 0 | 0 |  |  | 0 | } elsif ($node->language and $rhs_simple) { | 
| 871 | 0 | 0 | 0 |  |  | 0 | # ok | 
| 872 |  |  |  |  |  |  | } else { | 
| 873 | 0 |  | 0 |  |  | 0 | die "$func called with literals that are not argument compatible"; | 
| 874 | 0 |  | 0 |  |  | 0 | } | 
| 875 | 0 | 0 | 0 |  |  | 0 |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 876 |  |  |  |  |  |  | my $value	= $node->value; | 
| 877 |  |  |  |  |  |  | my $match	= $substr->value; | 
| 878 |  |  |  |  |  |  | my $i		= index($value, $match, 0); | 
| 879 |  |  |  |  |  |  | if ($i < 0) { | 
| 880 |  |  |  |  |  |  | return Attean::Literal->new(''); | 
| 881 |  |  |  |  |  |  | } else { | 
| 882 | 0 |  |  |  |  | 0 | if ($func eq 'STRBEFORE') { | 
| 883 |  |  |  |  |  |  | return Attean::Literal->new(value => substr($value, 0, $i), $node->construct_args); | 
| 884 |  |  |  |  |  |  | } else { | 
| 885 | 0 |  |  |  |  | 0 | return Attean::Literal->new(value => substr($value, $i+length($match)), $node->construct_args); | 
| 886 | 0 |  |  |  |  | 0 | } | 
| 887 | 0 |  |  |  |  | 0 | } | 
| 888 | 0 | 0 |  |  |  | 0 | } elsif ($func =~ /^(?:YEAR|MONTH|DAY|HOURS|MINUTES)$/) { | 
| 889 | 0 |  |  |  |  | 0 | my $method	= lc($func =~ s/^(HOUR|MINUTE)S$/$1/r); | 
| 890 |  |  |  |  |  |  | my $dt		= $operands[0]->datetime; | 
| 891 | 0 | 0 |  |  |  | 0 | return Attean::Literal->integer($dt->$method()); | 
| 892 | 0 |  |  |  |  | 0 | } elsif ($func eq 'SECONDS') { | 
| 893 |  |  |  |  |  |  | my $dt	= $operands[0]->datetime; | 
| 894 | 0 |  |  |  |  | 0 | return Attean::Literal->decimal($dt->second()); | 
| 895 |  |  |  |  |  |  | } elsif ($func eq 'TZ' or $func eq 'TIMEZONE') { | 
| 896 |  |  |  |  |  |  | my $dt	= $operands[0]->datetime; | 
| 897 |  |  |  |  |  |  | my $tz	= $dt->time_zone; | 
| 898 | 0 |  |  |  |  | 0 | if ($tz->is_floating) { | 
| 899 | 0 |  |  |  |  | 0 | return Attean::Literal->new('') if ($func eq 'TZ'); | 
| 900 | 0 |  |  |  |  | 0 | die "TIMEZONE called with a dateTime without a timezone"; | 
| 901 |  |  |  |  |  |  | } | 
| 902 | 0 |  |  |  |  | 0 | return Attean::Literal->new('Z') if ($func eq 'TZ' and $tz->is_utc); | 
| 903 | 0 |  |  |  |  | 0 | if ($tz) { | 
| 904 |  |  |  |  |  |  | my $offset	= $tz->offset_for_datetime( $dt ); | 
| 905 | 0 |  |  |  |  | 0 | my $hours	= 0; | 
| 906 | 0 |  |  |  |  | 0 | my $minutes	= 0; | 
| 907 | 0 | 0 |  |  |  | 0 | my $minus	= ($func eq 'TZ') ? '+' : ''; | 
| 908 | 0 | 0 |  |  |  | 0 | if ($offset < 0) { | 
| 909 | 0 |  |  |  |  | 0 | $minus	= '-'; | 
| 910 |  |  |  |  |  |  | $offset	= -$offset; | 
| 911 | 0 | 0 | 0 |  |  | 0 | } | 
| 912 | 0 | 0 |  |  |  | 0 |  | 
| 913 | 0 |  |  |  |  | 0 | my $duration	= "${minus}PT"; | 
| 914 | 0 |  |  |  |  | 0 | if ($offset >= 60*60) { | 
| 915 | 0 |  |  |  |  | 0 | my $h	= int($offset / (60*60)); | 
| 916 | 0 | 0 |  |  |  | 0 | $duration	.= "${h}H" if ($h > 0); | 
| 917 | 0 | 0 |  |  |  | 0 | $hours	= int($offset / (60*60)); | 
| 918 | 0 |  |  |  |  | 0 | $offset	= $offset % (60*60); | 
| 919 | 0 |  |  |  |  | 0 | } | 
| 920 |  |  |  |  |  |  | if ($offset >= 60) { | 
| 921 |  |  |  |  |  |  | my $m	= int($offset / 60); | 
| 922 | 0 |  |  |  |  | 0 | $duration	.= "${m}M" if ($m > 0); | 
| 923 | 0 | 0 |  |  |  | 0 | $minutes	= int($offset / 60); | 
| 924 | 0 |  |  |  |  | 0 | $offset	= $offset % 60; | 
| 925 | 0 | 0 |  |  |  | 0 | } | 
| 926 | 0 |  |  |  |  | 0 | my $seconds	= int($offset); | 
| 927 | 0 |  |  |  |  | 0 | my $s		= int($offset); | 
| 928 |  |  |  |  |  |  | $duration	.= "${s}S" if ($s > 0 or $duration eq 'PT'); | 
| 929 | 0 | 0 |  |  |  | 0 |  | 
| 930 | 0 |  |  |  |  | 0 | return ($func eq 'TZ') | 
| 931 | 0 | 0 |  |  |  | 0 | ? Attean::Literal->new(sprintf('%s%02d:%02d', $minus, $hours, $minutes)) | 
| 932 | 0 |  |  |  |  | 0 | : Attean::Literal->new( value => $duration, datatype => "http://www.w3.org/2001/XMLSchema#dayTimeDuration"); | 
| 933 | 0 |  |  |  |  | 0 | } else { | 
| 934 |  |  |  |  |  |  | return Attean::Literal->new('') if ($func eq 'TZ'); | 
| 935 | 0 |  |  |  |  | 0 | die "TIMEZONE called without a valid dateTime"; | 
| 936 | 0 |  |  |  |  | 0 | } | 
| 937 | 0 | 0 | 0 |  |  | 0 | } elsif ($func eq 'NOW') { | 
| 938 |  |  |  |  |  |  | my $value	= DateTime::Format::W3CDTF->new->format_datetime( DateTime->now ); | 
| 939 | 0 | 0 |  |  |  | 0 | return Attean::Literal->new( value => $value, datatype => 'http://www.w3.org/2001/XMLSchema#dateTime' ); | 
| 940 |  |  |  |  |  |  | } elsif ($func =~ /^(?:STR)?UUID$/) { | 
| 941 |  |  |  |  |  |  | return Attean::Literal->new(uc(uuid_to_string(create_uuid()))) if ($func eq 'STRUUID'); | 
| 942 |  |  |  |  |  |  | return Attean::IRI->new('urn:uuid:' . uc(uuid_to_string(create_uuid()))); | 
| 943 | 0 | 0 |  |  |  | 0 | } elsif ($func =~ /^(MD5|SHA1|SHA256|SHA384|SHA512)$/) { | 
| 944 | 0 |  |  |  |  | 0 | my $hash	= $func =~ s/SHA/SHA-/r; | 
| 945 |  |  |  |  |  |  | my $digest	= eval { Digest->new($hash)->add(encode('UTF-8', $operands[0]->value, Encode::FB_CROAK))->hexdigest }; | 
| 946 |  |  |  |  |  |  | return Attean::Literal->new($digest); | 
| 947 | 0 |  |  |  |  | 0 | } elsif ($func eq 'STRLANG') { | 
| 948 | 0 |  |  |  |  | 0 | my ($str, $lang)	= @operands; | 
| 949 |  |  |  |  |  |  | my @values	= map { $_->value } @operands; | 
| 950 | 0 | 0 |  |  |  | 0 | die "TypeError: STRLANG must be called with two plain literals" unless (blessed($str) and $str->does('Attean::API::Literal') and blessed($lang) and $lang->does('Attean::API::Literal')); | 
| 951 | 0 |  |  |  |  | 0 | die "TypeError: STRLANG not called with a simple literal" unless ($str->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string' and not($str->language)); | 
| 952 |  |  |  |  |  |  | return Attean::Literal->new( value => $values[0], language => $values[1] ); | 
| 953 | 0 |  |  |  |  | 0 | } elsif ($func eq 'STRDT') { | 
| 954 | 0 |  |  |  |  | 0 | die "TypeError: STRDT" unless ($operands[0]->does('Attean::API::Literal') and not($operands[0]->language)); | 
|  | 0 |  |  |  |  | 0 |  | 
| 955 | 0 |  |  |  |  | 0 | if (my $dt = $operands[0]->datatype) { | 
| 956 |  |  |  |  |  |  | die "TypeError: STRDT" unless ($dt->value eq 'http://www.w3.org/2001/XMLSchema#string'); | 
| 957 | 0 |  |  |  |  | 0 | } | 
| 958 | 0 |  |  |  |  | 0 | die "TypeError: STRDT" unless ($operands[1]->does('Attean::API::IRI')); | 
|  | 0 |  |  |  |  | 0 |  | 
| 959 | 0 | 0 | 0 |  |  | 0 | my @values	= map { $_->value } @operands; | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 960 | 0 | 0 | 0 |  |  | 0 | return Attean::Literal->new( value => $values[0], datatype => $values[1] ); | 
| 961 | 0 |  |  |  |  | 0 | } elsif ($func eq 'SAMETERM') { | 
| 962 |  |  |  |  |  |  | my ($a, $b)	= @operands; | 
| 963 | 0 | 0 | 0 |  |  | 0 | die "TypeError: SAMETERM" unless (blessed($operands[0]) and blessed($operands[1])); | 
| 964 | 0 | 0 |  |  |  | 0 | if ($a->compare($b)) { | 
| 965 | 0 | 0 |  |  |  | 0 | return $false; | 
| 966 |  |  |  |  |  |  | } | 
| 967 | 0 | 0 |  |  |  | 0 | if ($a->does('Attean::API::Binding')) { | 
| 968 | 0 |  |  |  |  | 0 | my $ok	= ($a->sameTerms($b)); | 
|  | 0 |  |  |  |  | 0 |  | 
| 969 | 0 |  |  |  |  | 0 | return $ok ? $true : $false; | 
| 970 |  |  |  |  |  |  | } else { | 
| 971 | 0 |  |  |  |  | 0 | my $ok	= ($a->value eq $b->value); | 
| 972 | 0 | 0 | 0 |  |  | 0 | return $ok ? $true : $false; | 
| 973 | 0 | 0 |  |  |  | 0 | } | 
| 974 | 0 |  |  |  |  | 0 | } elsif ($func =~ /^IS([UI]RI|BLANK|LITERAL|NUMERIC|TRIPLE)$/) { | 
| 975 |  |  |  |  |  |  | return $operands[0]->does("Attean::API::$type_roles{$1}") ? $true : $false; | 
| 976 | 0 | 0 |  |  |  | 0 | } elsif ($func eq 'REGEX') { | 
| 977 | 0 |  |  |  |  | 0 | my ($value, $pattern)	= map { $_->value } @operands; | 
| 978 | 0 | 0 |  |  |  | 0 | return ($value =~ /$pattern/) ? $true : $false; | 
| 979 |  |  |  |  |  |  | } elsif ($func eq 'INVOKE') { | 
| 980 | 0 |  |  |  |  | 0 | my $furi		= shift(@operands)->value; | 
| 981 | 0 | 0 |  |  |  | 0 | my $func		= Attean->get_global_function($furi); | 
| 982 |  |  |  |  |  |  | unless (ref($func)) { | 
| 983 |  |  |  |  |  |  | die "No extension registered for <$furi>"; | 
| 984 | 0 | 0 |  |  |  | 0 | } | 
| 985 |  |  |  |  |  |  | return $func->(@operands); | 
| 986 | 0 |  |  |  |  | 0 | } | 
|  | 0 |  |  |  |  | 0 |  | 
| 987 | 0 | 0 |  |  |  | 0 | die "Unimplemented FunctionExpression evaluation: " . $expr->operator; | 
| 988 |  |  |  |  |  |  | }; | 
| 989 | 0 |  |  |  |  | 0 | } elsif ($expr->isa('Attean::AggregateExpression')) { | 
| 990 | 0 |  |  |  |  | 0 | my $agg		= $expr->operator; | 
| 991 | 0 | 0 |  |  |  | 0 | my ($child)	= @{ $expr->children }; | 
| 992 | 0 |  |  |  |  | 0 | if ($agg eq 'COUNT') { | 
| 993 |  |  |  |  |  |  | if ($child) { | 
| 994 | 0 |  |  |  |  | 0 | my $impl	= $self->impl($child, $active_graph); | 
| 995 |  |  |  |  |  |  | return sub { | 
| 996 | 0 |  |  |  |  | 0 | my ($rows, %args)	= @_; | 
| 997 | 0 |  |  |  |  | 0 | my @terms	= grep { blessed($_) } map { $impl->($_, %args) } @{ $rows }; | 
| 998 |  |  |  |  |  |  | if ($expr->distinct) { | 
| 999 | 0 |  |  |  |  | 0 | my %seen; | 
| 1000 | 0 |  |  |  |  | 0 | @terms	= grep { not($seen{$_->as_string}++) } @terms; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1001 | 0 | 0 |  |  |  | 0 | } | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1002 | 0 | 0 |  |  |  | 0 | return Attean::Literal->integer(scalar(@terms)); | 
| 1003 | 0 |  |  |  |  | 0 | }; | 
| 1004 |  |  |  |  |  |  | } else { | 
| 1005 | 0 |  |  | 0 |  | 0 | return sub { | 
| 1006 | 0 |  |  |  |  | 0 | my ($rows, %args)	= @_; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1007 | 0 | 0 |  |  |  | 0 | return Attean::Literal->integer(scalar(@$rows)); | 
| 1008 | 0 |  |  |  |  | 0 | }; | 
| 1009 | 0 |  |  |  |  | 0 | } | 
|  | 0 |  |  |  |  | 0 |  | 
| 1010 |  |  |  |  |  |  | } elsif ($agg =~ /^(?:SAMPLE|MIN|MAX|SUM|AVG|GROUP_CONCAT)$/) { | 
| 1011 | 0 |  |  |  |  | 0 | my $impl	= $self->impl($child, $active_graph); | 
| 1012 | 0 |  |  |  |  | 0 | if ($agg eq 'SAMPLE') { | 
| 1013 |  |  |  |  |  |  | return sub { | 
| 1014 |  |  |  |  |  |  | my ($rows, %args)	= @_; | 
| 1015 | 0 |  |  | 0 |  | 0 | return $impl->( shift(@$rows), %args ) | 
| 1016 | 0 |  |  |  |  | 0 | }; | 
| 1017 | 0 |  |  |  |  | 0 | } elsif ($agg eq 'MIN' or $agg eq 'MAX') { | 
| 1018 |  |  |  |  |  |  | my $expect	= ($agg eq 'MIN') ? 1 : -1; | 
| 1019 |  |  |  |  |  |  | return sub { | 
| 1020 | 0 |  |  |  |  | 0 | my ($rows, %args)	= @_; | 
| 1021 | 0 | 0 | 0 |  |  | 0 | my $extrema; | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1022 |  |  |  |  |  |  | foreach my $r (@$rows) { | 
| 1023 | 0 |  |  | 0 |  | 0 | my $t	= $impl->( $r, %args ); | 
| 1024 | 0 |  |  |  |  | 0 | return if (not($t) and $agg eq 'MIN');	# unbound is always minimal | 
| 1025 | 0 |  |  |  |  | 0 | next if (not($t));						# unbound need not be considered for MAX | 
| 1026 |  |  |  |  |  |  | $extrema	= $t if (not($extrema) or $extrema->compare($t) == $expect); | 
| 1027 | 0 | 0 |  |  |  | 0 | } | 
| 1028 |  |  |  |  |  |  | return $extrema; | 
| 1029 | 0 |  |  | 0 |  | 0 | }; | 
| 1030 | 0 |  |  |  |  | 0 | } elsif ($agg eq 'SUM' or $agg eq 'AVG') { | 
| 1031 | 0 |  |  |  |  | 0 | return sub { | 
| 1032 | 0 |  |  |  |  | 0 | my ($rows, %args)	= @_; | 
| 1033 | 0 | 0 | 0 |  |  | 0 | my $count	= 0; | 
| 1034 | 0 | 0 |  |  |  | 0 | my $sum		= Attean::Literal->integer(0); | 
| 1035 | 0 | 0 | 0 |  |  | 0 | my %seen; | 
| 1036 |  |  |  |  |  |  | foreach my $r (@$rows) { | 
| 1037 | 0 |  |  |  |  | 0 | my $term	= $impl->( $r, %args ); | 
| 1038 | 0 |  |  |  |  | 0 | if ($expr->distinct) { | 
| 1039 |  |  |  |  |  |  | next if ($seen{ $term->as_string }++); | 
| 1040 |  |  |  |  |  |  | } | 
| 1041 | 0 |  |  | 0 |  | 0 | if ($term->does('Attean::API::NumericLiteral')) { | 
| 1042 | 0 |  |  |  |  | 0 | $count++; | 
| 1043 | 0 |  |  |  |  | 0 | $sum	= Attean::Literal->new( value => ($sum->numeric_value + $term->numeric_value), datatype => $sum->binary_promotion_type($term, '+') ); | 
| 1044 | 0 |  |  |  |  | 0 | } else { | 
| 1045 | 0 |  |  |  |  | 0 | die "TypeError: AVG"; | 
| 1046 | 0 |  |  |  |  | 0 | } | 
| 1047 | 0 | 0 |  |  |  | 0 | } | 
| 1048 | 0 | 0 |  |  |  | 0 | if ($agg eq 'AVG') { | 
| 1049 |  |  |  |  |  |  | $sum	= not($count) ? undef : Attean::Literal->new( value => ($sum->numeric_value / $count), datatype => $sum->binary_promotion_type(Attean::Literal->integer($count), '/') ); | 
| 1050 | 0 | 0 |  |  |  | 0 | } | 
| 1051 | 0 |  |  |  |  | 0 | return $sum; | 
| 1052 | 0 |  |  |  |  | 0 | }; | 
| 1053 |  |  |  |  |  |  | } elsif ($agg eq 'GROUP_CONCAT') { | 
| 1054 | 0 |  |  |  |  | 0 | my $sep	= $expr->scalar_vars->{ 'seperator' } // ' '; | 
| 1055 |  |  |  |  |  |  | return sub { | 
| 1056 |  |  |  |  |  |  | my ($rows, %args)	= @_; | 
| 1057 | 0 | 0 |  |  |  | 0 | my %seen; | 
| 1058 | 0 | 0 |  |  |  | 0 | my @strings; | 
| 1059 |  |  |  |  |  |  | foreach my $r (@$rows) { | 
| 1060 | 0 |  |  |  |  | 0 | my $term	= eval { $impl->( $r, %args ) }; | 
| 1061 | 0 |  |  |  |  | 0 | if ($expr->distinct) { | 
| 1062 |  |  |  |  |  |  | next if ($seen{ blessed($term) ? $term->as_string : '' }++); | 
| 1063 | 0 |  | 0 |  |  | 0 | } | 
| 1064 |  |  |  |  |  |  | push(@strings, $term->value // ''); | 
| 1065 | 0 |  |  | 0 |  | 0 | } | 
| 1066 | 0 |  |  |  |  | 0 | return Attean::Literal->new(join($sep, sort @strings)); | 
| 1067 |  |  |  |  |  |  | }; | 
| 1068 | 0 |  |  |  |  | 0 | } | 
| 1069 | 0 |  |  |  |  | 0 | } elsif ($agg eq 'CUSTOM') { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1070 | 0 | 0 |  |  |  | 0 | my $iri	= $expr->custom_iri; | 
| 1071 | 0 | 0 |  |  |  | 0 | my $data	= Attean->get_global_aggregate($iri); | 
|  |  | 0 |  |  |  |  |  | 
| 1072 |  |  |  |  |  |  | unless ($data) { | 
| 1073 | 0 |  | 0 |  |  | 0 | die "No extension aggregate registered for <$iri>"; | 
| 1074 |  |  |  |  |  |  | } | 
| 1075 | 0 |  |  |  |  | 0 | my $start	= $data->{'start'}; | 
| 1076 | 0 |  |  |  |  | 0 | my $process	= $data->{'process'}; | 
| 1077 |  |  |  |  |  |  | my $finalize	= $data->{'finalize'}; | 
| 1078 |  |  |  |  |  |  |  | 
| 1079 | 0 |  |  |  |  | 0 | my $impl	= $self->impl($child, $active_graph); | 
| 1080 | 0 |  |  |  |  | 0 | return sub { | 
| 1081 | 0 | 0 |  |  |  | 0 | my ($rows, %args)	= @_; | 
| 1082 | 0 |  |  |  |  | 0 | my $thunk	= $start->(); | 
| 1083 |  |  |  |  |  |  | foreach my $r (@$rows) { | 
| 1084 | 0 |  |  |  |  | 0 | my $t	= $impl->( $r, %args ); | 
| 1085 | 0 |  |  |  |  | 0 | $process->($thunk, $t); | 
| 1086 | 0 |  |  |  |  | 0 | } | 
| 1087 |  |  |  |  |  |  | return $finalize->($thunk); | 
| 1088 | 0 |  |  |  |  | 0 | }; | 
| 1089 |  |  |  |  |  |  | } | 
| 1090 | 0 |  |  | 0 |  | 0 | die "Unimplemented AggregateExpression evaluation: " . $expr->operator; | 
| 1091 | 0 |  |  |  |  | 0 | } elsif ($expr->isa('Attean::CastExpression')) { | 
| 1092 | 0 |  |  |  |  | 0 | my ($child)	= @{ $expr->children }; | 
| 1093 | 0 |  |  |  |  | 0 | my $impl	= $self->impl( $child, $active_graph ); | 
| 1094 | 0 |  |  |  |  | 0 | my $type	= $expr->datatype; | 
| 1095 |  |  |  |  |  |  | return sub { | 
| 1096 | 0 |  |  |  |  | 0 | my ($r, %args)	= @_; | 
| 1097 | 0 |  |  |  |  | 0 | my $term	= $impl->($r, %args); | 
| 1098 |  |  |  |  |  |  | # TODO: reformat syntax for xsd:double | 
| 1099 | 0 |  |  |  |  | 0 | my $cast	= Attean::Literal->new( value => $term->value, datatype => $type ); | 
| 1100 |  |  |  |  |  |  | return $cast->canonicalized_term if ($cast->does('Attean::API::CanonicalizingLiteral')); | 
| 1101 | 4 |  |  |  |  | 6 | return $cast; | 
|  | 4 |  |  |  |  | 38 |  | 
| 1102 | 4 |  |  |  |  | 19 | } | 
| 1103 | 4 |  |  |  |  | 11 | } else { | 
| 1104 |  |  |  |  |  |  | Carp::confess "No impl for expression " . $expr->as_string; | 
| 1105 | 4 |  |  | 4 |  | 10 | } | 
| 1106 | 4 |  |  |  |  | 11 | } | 
| 1107 |  |  |  |  |  |  | } | 
| 1108 | 4 |  |  |  |  | 83 |  | 
| 1109 | 4 | 50 |  |  |  | 327 | 1; | 
| 1110 | 0 |  |  |  |  |  |  | 
| 1111 |  |  |  |  |  |  |  | 
| 1112 | 4 |  |  |  |  | 17 | =back | 
| 1113 | 0 |  |  |  |  |  |  | 
| 1114 |  |  |  |  |  |  | =head1 BUGS | 
| 1115 |  |  |  |  |  |  |  | 
| 1116 |  |  |  |  |  |  | Please report any bugs or feature requests to through the GitHub web interface | 
| 1117 |  |  |  |  |  |  | at L<https://github.com/kasei/attean/issues>. | 
| 1118 |  |  |  |  |  |  |  | 
| 1119 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 1120 |  |  |  |  |  |  |  | 
| 1121 |  |  |  |  |  |  |  | 
| 1122 |  |  |  |  |  |  |  | 
| 1123 |  |  |  |  |  |  | =head1 AUTHOR | 
| 1124 |  |  |  |  |  |  |  | 
| 1125 |  |  |  |  |  |  | Gregory Todd Williams  C<< <gwilliams@cpan.org> >> | 
| 1126 |  |  |  |  |  |  |  | 
| 1127 |  |  |  |  |  |  | =head1 COPYRIGHT | 
| 1128 |  |  |  |  |  |  |  | 
| 1129 |  |  |  |  |  |  | Copyright (c) 2014--2022 Gregory Todd Williams. | 
| 1130 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it under | 
| 1131 |  |  |  |  |  |  | the same terms as Perl itself. | 
| 1132 |  |  |  |  |  |  |  | 
| 1133 |  |  |  |  |  |  | =cut |