Attean

 view release on metacpan or  search on metacpan

lib/Attean/Plan.pm  view on Meta::CPAN

					}
				}
				
				foreach my $rows (@buckets) {
					foreach my $r (@$rows) {
						if (my $j = $l->join($r)) {
							$seen++;
							if ($left) {
								# TODO: filter with expression
								push(@results, $j);
							} else {
								push(@results, $j);
							}
						}
					}
				}
				if ($left and not($seen)) {
					push(@results, $l);
				}
			}
			return Attean::ListIterator->new(
				item_type => 'Attean::API::Result',
				variables => $iter_variables,
				values => \@results
			);
		}
	}
}

=item * L<Attean::Plan::Construct>

=cut

package Attean::Plan::Construct 0.038 {
	use Moo;
	use List::Util qw(all);
	use Types::Standard qw(Str ArrayRef ConsumerOf InstanceOf);
	use namespace::clean;
	has 'triples' => (is => 'ro', 'isa' => ArrayRef[ConsumerOf['Attean::API::TripleOrQuadPattern']], required => 1);

	with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';

	sub plan_as_string {
		my $self	= shift;
		my $triples	= $self->triples;
		return sprintf('Construct { %s }', join(' . ', map { $_->as_string } @$triples));
	}

	sub BUILDARGS {
		# TODO: this code is repeated in several plan classes; figure out a way to share it.
		my $class		= shift;
		my %args		= @_;
		my %vars		= map { $_ => 1 } map { @{ $_->in_scope_variables } } @{ $args{ children } };
		my @vars		= keys %vars;
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= \@vars;

		return $class->SUPER::BUILDARGS(%args);
	}

	sub impl {
		my $self	= shift;
		my $model	= shift;
		my @children	= map { $_->impl($model) } @{ $self->children };
		return $self->_impl($model, @children);
	}
	
	sub substitute_impl {
		my $self	= shift;
		my $model	= shift;
		my $b		= shift;
		unless (all { $_->does('Attean::API::BindingSubstitutionPlan') } @{ $self->children }) {
			die "Plan children do not all consume BindingSubstitutionPlan role:\n" . $self->as_string;
		}
		
		warn "TODO: fix substitute_impl to substitute construct triples";
		my @children	= map { $_->substitute_impl($model, $b) } @{ $self->children };
		return $self->_impl($model, @children);
	}
	
	# replace blank nodes in all the triple patterns with fresh ones
	sub refresh_triples {
		my $self	= shift;
		my @t;
		my %mapping;
		foreach my $t (@_) {
			foreach my $term ($t->values) {
				if ($term->does('Attean::API::Blank')) {
					$mapping{$term->as_string}	= Attean::Blank->new();
				}
			}
		}
		my $mapper	= Attean::TermMap->rewrite_map(\%mapping);
		foreach my $t (@_) {
			push(@t, $t->apply_map($mapper));
		}
		return @t;
	}
	
	sub _impl {
		my $self		= shift;
		my $model		= shift;
		my $child		= shift;
		
		my @triples		= @{ $self->triples };
		
		return sub {
			my $iter	= $child->();
			my @buffer;
			my %seen;
			return Attean::CodeIterator->new(
				item_type => 'Attean::API::Triple',
				generator => sub {
					if (scalar(@buffer)) {
						return shift(@buffer);
					}
					while (my $row = $iter->next) {
						foreach my $tp ($self->refresh_triples(@triples)) {

lib/Attean/Plan.pm  view on Meta::CPAN

									$iter	= $current->();
								} else {
									undef $iter;
								}
							}
						}
					},
				);
			} else {
				return Attean::ListIterator->new( item_type => 'Attean::API::Result', variables => [], values => [], );
			}
		};
	}
}

=item * L<Attean::Plan::Extend>

Evaluates a sub-plan, and extends each result by evaluating a set of
expressions, binding the produced values to new variables.

=cut

package Attean::Plan::Extend 0.038 {
	use Moo;
	use Encode;
	use UUID::Tiny ':std';
	use URI::Escape;
	use Data::Dumper;
	use I18N::LangTags;
	use POSIX qw(ceil floor);
	use Digest::SHA;
	use Digest::MD5 qw(md5_hex);
	use Scalar::Util qw(blessed looks_like_number);
	use List::Util qw(uniq all);
	use Types::Standard qw(ConsumerOf ArrayRef InstanceOf HashRef);
	use namespace::clean;

	with 'MooX::Log::Any';
	with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
	has 'expressions' => (is => 'ro', isa => HashRef[ConsumerOf['Attean::API::Expression']], required => 1);
	has 'active_graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 1);
	
	sub plan_as_string {
		my $self	= shift;
		my @strings	= map { sprintf('?%s ← %s', $_, $self->expressions->{$_}->as_string) } keys %{ $self->expressions };
		return sprintf('Extend { %s }', join(', ', @strings));
	}
	sub tree_attributes { return qw(variable expression) };
	
	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my $exprs		= $args{ expressions };
		my @vars		= map { @{ $_->in_scope_variables } } @{ $args{ children } };
		my @evars		= (@vars, keys %$exprs);
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= [@evars];
		return $class->SUPER::BUILDARGS(%args);
	}
	
	sub evaluate_expression {
		my $self	= shift;
		my $model	= shift;
		my $expr	= shift;
		my $r		= shift;
		Carp::confess unless ($expr->can('operator'));
		my $op		= $expr->operator;

		state $true			= Attean::Literal->true;
		state $false		= Attean::Literal->false;
		state $type_roles	= { qw(URI IRI IRI IRI BLANK Blank LITERAL Literal NUMERIC NumericLiteral TRIPLE Triple) };
		state $type_classes	= { qw(URI Attean::IRI IRI Attean::IRI STR Attean::Literal) };
		
		if ($expr->isa('Attean::CastExpression')) {
			my $datatype	= $expr->datatype->value;
			my ($child)	= @{ $expr->children };
			my $term	= $self->evaluate_expression($model, $child, $r);

			if ($datatype =~ m<^http://www.w3.org/2001/XMLSchema#string$>) {
				my $value	= $term->value;
				if ($term->does('Attean::API::IRI')) {
					return Attean::Literal->new(value => $term->value);
				} elsif ($term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#boolean') {
					my $v	= ($value eq 'true' or $value eq '1') ? 'true' : 'false';
					return Attean::Literal->new(value => $v);
				} elsif ($term->does('Attean::API::NumericLiteral')) {
					my $v	= $term->numeric_value();
					if ($v == int($v)) {
						return Attean::Literal->new(value => int($v));
					}
				}
				
				return Attean::Literal->new(value => $value);
			}

			die "TypeError $op" unless (blessed($term) and $term->does('Attean::API::Literal'));
			if ($datatype =~ m<^http://www.w3.org/2001/XMLSchema#(integer|float|double|decimal)>) {
				my $value	= $term->value;
				my $num;
				if ($datatype eq 'http://www.w3.org/2001/XMLSchema#integer') {
					if ($term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#boolean') {
						$value	= ($value eq 'true' or $value eq '1') ? '1' : '0';
					} elsif ($term->does('Attean::API::NumericLiteral')) {
						my $v	= $term->numeric_value();
						$v		=~ s/[.].*$//;
						$value	= int($v);
					} elsif ($value =~ /^[-+]\d+$/) {
						my ($v) = "$value";
						$v		=~ s/[.].*$//;
						$value	= int($v);
					}
					$num	= $value;
				} elsif ($datatype eq 'http://www.w3.org/2001/XMLSchema#decimal') {
					if ($term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#boolean') {
						$value	= ($value eq 'true') ? '1' : '0';
					} elsif ($term->does('Attean::API::NumericLiteral')) {
						$value	= $term->numeric_value;
					} elsif (looks_like_number($value)) {

lib/Attean/Plan.pm  view on Meta::CPAN


=cut

package Attean::Plan::Slice 0.038 {
	use Moo;
	use Types::Standard qw(Int);
	use namespace::clean;
	
	with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
	with 'Attean::API::UnionScopeVariablesPlan';

	has 'limit' => (is => 'ro', isa => Int, default => -1);
	has 'offset' => (is => 'ro', isa => Int, default => 0);

	sub plan_as_string {
		my $self	= shift;
		my @str;
		push(@str, "Limit=" . $self->limit) if ($self->limit >= 0);
		push(@str, "Offset=" . $self->offset) if ($self->offset > 0);
		return sprintf('Slice { %s }', join(' ', @str));
	}
	
	sub impl {
		my $self	= shift;
		my $model	= shift;
		my ($impl)	= map { $_->impl($model) } @{ $self->children };
		my $offset	= $self->offset;
		my $limit	= $self->limit;
		return sub {
			my $iter	= $impl->();
			$iter		= $iter->offset($offset) if ($offset > 0);
			$iter		= $iter->limit($limit) if ($limit >= 0);
			return $iter;
		};
	}
}

=item * L<Attean::Plan::Project>

Evaluates a sub-plan and returns projected results by only keeping a fixed-set
of variable bindings in each result.

=cut

package Attean::Plan::Project 0.038 {
	use Moo;
	with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
	use Types::Standard qw(ArrayRef ConsumerOf);
	has 'variables' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']], required => 1);

	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my @vars		= map { $_->value } @{ $args{variables} };
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= \@vars;

		return $class->SUPER::BUILDARGS(%args);
	}
	
# 	sub BUILD {
# 		my $self	= shift;
# 		my @vars	= map { $_->value } @{ $self->variables };
# 		unless (scalar(@vars)) {
# 			Carp::confess "No vars in project?";
# 		}
# 	}
	
	sub plan_as_string {
		my $self	= shift;
		return sprintf('Project { %s }', join(' ', map { '?' . $_->value } @{ $self->variables }));
	}
	sub tree_attributes { return qw(variables) };
	
	sub substitute_impl {
		my $self	= shift;
		my $model	= shift;
		my $bind	= shift;
		my ($impl)	= map { $_->substitute_impl($model, $bind) } @{ $self->children };
		my @vars	= map { $_->value } @{ $self->variables };
		my $iter_variables	= $self->in_scope_variables;

		# TODO: substitute variables in the projection where appropriate
		return sub {
			my $iter	= $impl->();
			return $iter->map(sub {
				my $r	= shift;
				my $b	= { map { my $t	= $r->value($_); $t	? ($_ => $t) : () } @vars };
				return Attean::Result->new( bindings => $b );
			}, $iter->item_type, variables => $iter_variables);
		};
	}
	
	sub impl {
		my $self	= shift;
		my $model	= shift;
		my ($impl)	= map { $_->impl($model) } @{ $self->children };
		my @vars	= map { $_->value } @{ $self->variables };
		my $iter_variables	= $self->in_scope_variables;

		return sub {
			my $iter	= $impl->();
			return $iter->map(sub {
				my $r	= shift;
				my $b	= { map { my $t	= $r->value($_); $t	? ($_ => $t) : () } @vars };
				return Attean::Result->new( bindings => $b );
			}, $iter->item_type, variables => $iter_variables);
		};
	}
}

=item * L<Attean::Plan::OrderBy>

Evaluates a sub-plan and returns the results after fully materializing and
sorting is applied.

=cut

lib/Attean/Plan.pm  view on Meta::CPAN

	sub tree_attributes { return qw(endpoint) };
	sub impl {
		my $self	= shift;
		my $model	= shift;

		my $endpoint	= $self->endpoint->value;
		my $sparql		= $self->sparql;
		my $silent		= $self->silent;
		my %args		= (
			endpoint		=> $endpoint,
			silent			=> $silent,
			request_signer	=> $self->request_signer,
		);
		$args{user_agent}	= $self->user_agent if ($self->user_agent);
		my $client		= $self->client || Attean::SPARQLClient->new(%args);
		return sub {
			return $client->query($sparql);
		};
	}
}

=item * L<Attean::Plan::Table>

Returns a constant set of results.

=cut

package Attean::Plan::Table 0.038 {
	use Moo;
	use Types::Standard qw(ArrayRef ConsumerOf);
	use namespace::clean;

	with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';

	has variables => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']]);
	has rows => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Result']]);

	sub tree_attributes { return qw(variables rows) };
	sub plan_as_string {
		my $self	= shift;
		my $level	= shift;
		my $indent	= '  ' x ($level + 1);
		my $vars	= join(', ', map { "?$_" } @{ $self->in_scope_variables });
		my $s		= "Table (" . $vars . ")";
		foreach my $row (@{ $self->rows }) {
			$s	.= "\n-${indent} " . $row->as_string;
		}
		return $s;
	}
	
	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my @vars		= map { $_->value } @{ $args{variables} };
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= \@vars;

		return $class->SUPER::BUILDARGS(%args);
	}
	
	sub impl {
		my $self	= shift;
		my $model	= shift;
		my $rows	= $self->rows;
		my $iter_variables	= $self->in_scope_variables;

		return sub {
			return Attean::ListIterator->new(
				item_type => 'Attean::API::Result',
				variables => $iter_variables,
				values => $rows
			);
		};
	}
}

=item * L<Attean::Plan::Iterator>

Returns a constant set of results.

Be aware that if the iterator being wrapped is not repeatable (consuming the
L<Attean::API::RepeatableIterator> role), then this plan may only be evaluated
once.

A size estimate may be given if it is available. If the iterator is an
L<Attean::ListIterator>, the size of that iterator will be used.

=cut

package Attean::Plan::Iterator 0.038 {
	use Moo;
	use Types::Standard qw(ArrayRef ConsumerOf Int);
	use namespace::clean;

	with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';

	has iterator		=> (is => 'ro', isa => ConsumerOf['Attean::API::ResultIterator']);
	has size_estimate	=> (is => 'lazy', isa => Int, predicate => 1);

	sub _build_size_estimate {
		my $self = shift;
		my $iter = $self->iterator;
		if ($iter->isa('Attean::ListIterator')) {
			return $iter->size;
		}
	}


	sub tree_attributes { return qw(iterator) };
	sub plan_as_string {
		my $self	= shift;
		my $level	= shift;
		my $indent	= '  ' x ($level + 1);
		my $string = 'Iterator (';
		$string	.= join(', ', map { "?$_" } @{ $self->in_scope_variables });
		if ($self->has_size_estimate) {
			$string .= ' with ' . $self->size_estimate . ' elements';
		}
		$string	.= ')';
		return $string;
	}
	
	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my $vars		= $args{iterator}->variables;
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= $vars;

		return $class->SUPER::BUILDARGS(%args);
	}
	
	sub impl {
		my $self	= shift;
		my $model	= shift;
		my $iter	= $self->iterator;

		return sub {
			if ($iter->does('Attean::API::RepeatableIterator')) {
				$iter->reset;
			}
			return $iter;
		};
	}
}

=item * L<Attean::Plan::ALPPath>

=cut

package Attean::Plan::ALPPath 0.038 {
	use Moo;
	use Attean::TreeRewriter;
	use Types::Standard qw(ArrayRef ConsumerOf);
	use namespace::clean;

	has 'subject'		=> (is => 'ro', required => 1);
	has 'object'		=> (is => 'ro', required => 1);
	has 'graph'			=> (is => 'ro', required => 1);
	has 'step_begin'	=> (is => 'ro', required => 1);
	has 'step_end'		=> (is => 'ro', required => 1);
	has 'skip'			=> (is => 'ro', required => 1, default => 0);
# 	has 'children'		=> (is => 'ro', isa => ConsumerOf['Attean::API::BindingSubstitutionPlan'], required => 1);
	
	with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::NullaryQueryTree';

	sub tree_attributes { return qw(subject object graph) };
	with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
	
	sub plan_as_string {
		my $self	= shift;
		my @strings;
		push(@strings, sprintf('%s ← %s', map { $_->ntriples_string } ($self->subject, $self->step_begin)));
		push(@strings, sprintf('%s ← %s', map { $_->ntriples_string } ($self->object, $self->step_end)));
		return sprintf('ALPPath %s', join(', ', @strings));
	}
	
	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my @vars		= map { $_->value } grep { $_->does('Attean::API::Variable') } (@args{qw(subject object)});
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= \@vars;

		return $class->SUPER::BUILDARGS(%args);
	}
	
	sub alp {
		my $model	= shift;
		my $graph	= shift;
		my $skip	= shift;
		my $x		= shift;
		my $path	= shift;
		my $v		= shift;
		my $start	= shift;
		my $end		= shift;
		my $bind	= shift;
		if (exists $v->{$x->as_string}) {
			return;
		}
		my $binding	= Attean::Result->new( bindings => { $start => $x } )->join($bind);
		unless ($binding) {
			return;
		}
		
		if ($skip) {
			$skip--;
		} else {
			$v->{$x->as_string}	= $x;
		}
		
		my $impl	= $path->substitute_impl($model, $binding);
		my $iter	= $impl->();
		while (my $row = $iter->next()) {
			my $n	= $row->value($end);
			alp($model, $graph, $skip, $n, $path, $v, $start, $end, $bind);
		}
	}
	
	sub substitute_impl {
		my $self	= shift;
		my $model	= shift;
		my $bind	= shift;
		my $path	= $self->children->[0];
		my $subject	= $self->subject;
		my $object	= $self->object;
		my $graph	= $self->graph;
		my $start	= $self->step_begin->value;
		my $end		= $self->step_end->value;
		my $skip	= $self->skip;
		my $iter_variables	= $self->in_scope_variables;

		for ($subject, $object) {
			if ($_->does('Attean::API::Variable')) {
				my $name	= $_->value;
				if (my $node = $bind->value($name)) {
					$_	= $node;
				}
			}
		}
		
		my $s_var	= $subject->does('Attean::API::Variable');
		my $o_var	= $object->does('Attean::API::Variable');
		if ($s_var and $o_var) {
			return sub {

lib/Attean/Plan.pm  view on Meta::CPAN

					values => \@rows,
				);
			};
		} elsif ($o_var) {
			return sub {
				my %seen;
				alp($model, $graph, $skip, $subject, $path, \%seen, $start, $end, $bind);
				my @rows	= map { Attean::Result->new( bindings => { $object->value => $_ } ) } (values %seen);
				return Attean::ListIterator->new(
					item_type => 'Attean::API::Result',
					variables => $iter_variables,
					values => \@rows,
				);
			};
		} elsif ($s_var) {
			die "ALP for FB should never occur in a plan (should be inversed during planning)";
		} else {
			return sub {
				my %seen;
				alp($model, $graph, $skip, $subject, $path, \%seen, $start, $end, $bind);
				if (exists $seen{ $object->as_string }) {
					return Attean::ListIterator->new(
						item_type => 'Attean::API::Result',
						variables => $iter_variables,
						values => [Attean::Result->new()]
					);
				} else {
					return Attean::ListIterator->new(
						item_type => 'Attean::API::Result',
						variables => $iter_variables,
						values => []
					);
				}
			};
		}
	}
}

package Attean::Plan::ZeroOrOnePath 0.038 {
	use Moo;
	use Attean::TreeRewriter;
	use Types::Standard qw(ArrayRef ConsumerOf);
	use namespace::clean;

	has 'subject'		=> (is => 'ro', required => 1);
	has 'object'		=> (is => 'ro', required => 1);
	has 'graph'			=> (is => 'ro', required => 1);
	
	with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::NullaryQueryTree';

	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my @vars		= map { $_->value } grep { $_->does('Attean::API::Variable') } (@args{qw(subject object)});
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= \@vars;

		return $class->SUPER::BUILDARGS(%args);
	}
	
	sub tree_attributes { return qw(subject object) };
	with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
	
	sub plan_as_string { return 'ZeroOrOnePath' }
	
	sub substitute_impl {
		my $self	= shift;
		my $model	= shift;
		my $bind	= shift;
		my ($impl)	= map { $_->substitute_impl($model, $bind) } @{ $self->children };
		my $iter_variables	= $self->in_scope_variables;

		my $subject	= $self->subject;
		my $object	= $self->object;
		my $graph	= $self->graph;
		for ($subject, $object) {
			if ($_->does('Attean::API::Variable')) {
				my $name	= $_->value;
				if (my $node = $bind->value($name)) {
					$_	= $node;
				}
			}
		}

		my $s_var	= $subject->does('Attean::API::Variable');
		my $o_var	= $object->does('Attean::API::Variable');
		return sub {
			my @extra;
			if ($s_var and $o_var) {
				my $nodes	= $model->graph_nodes($graph);
				while (my $n = $nodes->next) {
					push(@extra, Attean::Result->new( bindings => { map { $_->value => $n } ($subject, $object) } ));
				}
			} elsif ($s_var) {
				push(@extra, Attean::Result->new( bindings => { $subject->value => $object } ));
			} elsif ($o_var) {
				push(@extra, Attean::Result->new( bindings => { $object->value => $subject } ));
			} else {
				if (0 == $subject->compare($object)) {
					push(@extra, Attean::Result->new( bindings => {} ));
				}
			}
			my $iter	= $impl->();
			my %seen;
			return Attean::CodeIterator->new(
				item_type => 'Attean::API::Result',
				variables => $iter_variables,
				generator => sub {
					while (scalar(@extra)) {
						my $r	= shift(@extra);
						unless ($seen{$r->as_string}++) {
							return $r;
						}
					}
					while (my $r = $iter->next()) {
						return unless ($r);
						if ($seen{$r->as_string}++) {
							next;

lib/Attean/Plan.pm  view on Meta::CPAN

		my ($impl)	= map { $_->impl($model) } @{ $self->children };
		return sub {
			my $iter	= $impl->();
			my $result	= $iter->next;
# 			if ($result) {
# 				warn "EXISTS: " . $result->as_string;
# 			}
			my $term	= $result ? Attean::Literal->true : Attean::Literal->false;
			return Attean::ListIterator->new(values => [$term], item_type => 'Attean::API::Term');
		}
	}
}

=item * L<Attean::Plan::Aggregate>

=cut

package Attean::Plan::Aggregate 0.038 {
	use Moo;
	use Encode;
	use UUID::Tiny ':std';
	use URI::Escape;
	use I18N::LangTags;
	use POSIX qw(ceil floor);
	use Digest::SHA;
	use Digest::MD5 qw(md5_hex);
	use Scalar::Util qw(blessed);
	use List::Util qw(uniq);
	use Types::Standard qw(ConsumerOf InstanceOf HashRef ArrayRef);
	use namespace::clean;

	with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
	has 'aggregates' => (is => 'ro', isa => HashRef[ConsumerOf['Attean::API::Expression']], required => 1);
	has 'groups' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Expression']], required => 1);
	has 'active_graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 1);
	
	sub plan_as_string {
		my $self	= shift;
		my @astrings	= map { sprintf('?%s ← %s', $_, $self->aggregates->{$_}->as_string) } keys %{ $self->aggregates };
		my @gstrings	= map { sprintf('%s', $_->as_string) } @{ $self->groups };
		return sprintf('Aggregate { %s } Groups { %s }', join(', ', @astrings), join(', ', @gstrings));
	}
	sub tree_attributes { return qw(aggregates groups) };
	
	sub BUILD {
		# Ensure that the CT extensions are registered
		AtteanX::Functions::CompositeLists->register();
		AtteanX::Functions::CompositeMaps->register();
	}

	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my $aggs		= $args{ aggregates };
		my @vars		= map { $_->value } grep { $_->does('Attean::API::Variable') } map { $_->value } @{ $args{groups} // [] };
		my @evars		= (@vars, keys %$aggs);
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= [@evars];
		return $class->SUPER::BUILDARGS(%args);
	}
	
	sub evaluate_aggregate {
		my $self	= shift;
		my $model	= shift;
		my $expr	= shift;
		my $rows	= shift;
		my $op		= $expr->operator;
		my ($e)		= @{ $expr->children };
# 		my @children	= map { Attean::Plan::Extend->evaluate_expression($model, $_, $r) } @{ $expr->children };
# 		warn "$op — " . join(' ', map { $_->as_string } @children);
		if ($op eq 'COUNT') {
			my $count	= 0;
			foreach my $r (@$rows) {
				if ($e) {
					my $term	= Attean::Plan::Extend->evaluate_expression($model, $e, $r);
					if ($term) {
						$count++;
					}
				} else {
					# This is the special-case branch for COUNT(*)
					$count++;
				}
			}
			return Attean::Literal->new(value => $count, datatype => 'http://www.w3.org/2001/XMLSchema#integer');
		} elsif ($op eq 'SUM') {
			my @cmp;
			my @terms;
			foreach my $r (@$rows) {
				my $term	= Attean::Plan::Extend->evaluate_expression($model, $e, $r);
				if ($term->does('Attean::API::NumericLiteral')) {
					push(@terms, $term);
				}
			}
			my $lhs	= shift(@terms);
			while (my $rhs = shift(@terms)) {
				my $type	= $lhs->binary_promotion_type($rhs, '+');
				my ($lv, $rv)	= map { $_->numeric_value } ($lhs, $rhs);
				$lhs	= Attean::Literal->new(value => ($lv + $rv), datatype => $type);
			}
			return $lhs;
		} elsif ($op eq 'AVG') {
			my @cmp;
			my $count	= 0;
			my $all_ints	= 1;
			my @terms;
			if (scalar(@$rows) == 0) {
				return Attean::Literal->integer(0);
			}
			foreach my $r (@$rows) {
				my $term	= Attean::Plan::Extend->evaluate_expression($model, $e, $r);
				die unless ($term->does('Attean::API::NumericLiteral'));
				push(@terms, $term);
				$count++;
			}
			my $lhs	= shift(@terms);
			while (my $rhs = shift(@terms)) {
				my $type	= $lhs->binary_promotion_type($rhs, '+');
				my ($lv, $rv)	= map { $_->numeric_value } ($lhs, $rhs);
				$lhs	= Attean::Literal->new(value => ($lv + $rv), datatype => $type);

lib/Attean/Plan.pm  view on Meta::CPAN

					return $iter;
				}
			}
			
			if ($silent) {
				return Attean::ListIterator->new(values => [], item_type => 'Attean::API::Triple');
			} else {
				die "Failed to load url: " . $resp->status_line;
			}
		};
	}
}



=item * L<Attean::Plan::Unfold>

=cut

package Attean::Plan::Unfold 0.032 {
	use Moo;
	use Encode;
	use UUID::Tiny ':std';
	use URI::Escape;
	use Data::Dumper;
	use I18N::LangTags;
	use POSIX qw(ceil floor);
	use Digest::SHA;
	use Digest::MD5 qw(md5_hex);
	use Scalar::Util qw(blessed looks_like_number);
	use List::Util qw(uniq all);
	use Types::Standard qw(ConsumerOf ArrayRef InstanceOf HashRef);
	use namespace::clean;

	with 'MooX::Log::Any';
	with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
	
	has 'variables' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']], required => 1);
	has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 1);
	has 'active_graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 1);
	
	sub plan_as_string {
		my $self	= shift;
		my @vars	= map { $_->as_string } @{ $self->variables };
		my $vars	= '(' . join(', ', @vars) . ')';
		return sprintf('Unfold { %s ← %s }', $vars, $self->expression->as_string);
	}
	sub tree_attributes { return qw(variable expression) };
	
	sub BUILDARGS {
		my $class		= shift;
		my %args		= @_;
		my $exprs		= $args{ expressions };
		my @vars		= map { @{ $_->in_scope_variables } } @{ $args{ children } };
		my @evars		= (@vars, keys %$exprs);
		
		if (exists $args{in_scope_variables}) {
			Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
		}
		$args{in_scope_variables}	= [@evars];
		return $class->SUPER::BUILDARGS(%args);
	}
	
	sub substitute_impl {
		my $self	= shift;
		my $model	= shift;
		my $bind	= shift;
		my $expr	= $self->expression;
		my $vars	= $self->variables;
		my ($impl)	= map { $_->substitute_impl($model, $bind) } @{ $self->children };
		# TODO: substitute variables in the expression
		return $self->_impl($model, $impl, $expr, @$vars);
	}
	
	sub impl {
		my $self	= shift;
		my $model	= shift;
		my $expr	= $self->expression;
		my $vars	= $self->variables;
		my ($impl)	= map { $_->impl($model) } @{ $self->children };
		return $self->_impl($model, $impl, $expr, @$vars);
	}
	
	sub _impl {
		my $self	= shift;
		my $model	= shift;
		my $impl	= shift;
		Carp::confess unless (defined($impl));
		my $expr	= shift;
		my @vars	= @_;
		my $iter_variables	= $self->in_scope_variables;

		my $var			= $vars[0];
		my $index_var	= $vars[1];
		return sub {
			my $iter	= $impl->();
			my @buffer;
			return Attean::CodeIterator->new(
				item_type => 'Attean::API::Result',
				variables => $iter_variables,
				generator => sub {
					if (scalar(@buffer)) {
						return shift(@buffer);
					}
					ROW: while (my $r = $iter->next) {
						my %base	= map { $_ => $r->value($_) } $r->variables;
						my $cdt	= eval { Attean::Plan::Extend->evaluate_expression($model, $expr, $r) };
						warn "UNFOLD expression evaluation error: $@" if ($@);
						unless ($cdt) {
							return $r;
						}
						
						if ($cdt->does('Attean::API::Literal') and $cdt->datatype->value eq $AtteanX::Functions::CompositeLists::LIST_TYPE_IRI) {
							my @values	= AtteanX::Functions::CompositeLists::lex_to_list($cdt);
							foreach my $index (0 .. $#values) {
								my %row	= %base;
								my $term	= $values[$index];
								if (blessed($term)) {
									if ($row{ $var } and $term->as_string ne $row{ $var }->as_string) {
										next ROW;
									}



( run in 0.934 second using v1.01-cache-2.11-cpan-5a3173703d6 )