Attean

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

 - (Update) Fix handling of BOUND and error-causing INVOKE expressions in
   Attean::Plan.
 - (Update) Impove error reporting in
   Attean::API::MutableModel->load_urls_into_graph.
 - (Update) Improve Attean::API::CanonicalizingLiteral to have strict and
   non-strict c14n variants.

0.033  2022-10-02

 - (Addition) Add new Attean::SPARQLClient protocol implementation.
 - (Update) Fixed handling of endpoint URLs containing query parameters.
 - (Update) Protocol HTTP requests can now be signed by specifying a
   'request_signer'.
 - (Update) Update SERVICE evaluation classes to use Attean::SPARQLClient.

0.032  2022-08-14

 - (Update) Fix for bug caused by newly added TermOrVariableOrTriplePattern
   role.

0.031  2022-08-04

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


package Attean::Algebra::Service 0.035 {
	use AtteanX::SPARQL::Constants;
	use AtteanX::SPARQL::Token;
	use Moo;
	use Types::Standard qw(ConsumerOf Bool);
	use namespace::clean;

	with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree', 'Attean::API::UnionScopeVariables';

	has 'endpoint' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariable'], required => 1);
	has 'silent' => (is => 'ro', isa => Bool, default => 0);
	
	sub algebra_as_string {
		my $self	= shift;
		my $endpoint	= $self->endpoint->as_sparql;
		chomp($endpoint);
		return sprintf('Service %s', $endpoint);
	}

	sub tree_attributes { return qw(endpoint) };

	sub sparql_tokens {
		my $self	= shift;
		my $service	= AtteanX::SPARQL::Token->keyword('SERVICE');
		my $l		= AtteanX::SPARQL::Token->lbrace;
		my $r		= AtteanX::SPARQL::Token->rbrace;
		my ($child)	= @{ $self->children };
		
		my @tokens;
		push(@tokens, $service);
		if ($self->silent) {
			push(@tokens, AtteanX::SPARQL::Token->keyword('SILENT'));
		}
		push(@tokens, $self->endpoint->sparql_tokens->elements);
		push(@tokens, $l);
		push(@tokens, $child->sparql_subtokens->elements);
		push(@tokens, $r);
		return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
	}
}

=item * L<Attean::Algebra::Path>

=cut

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

				values => \@sorted,
				variables => $iter_variables,
				item_type => $iter->item_type
			);
		}
	}
}

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

Evaluates a SPARQL query against a remote endpoint.

=cut

package Attean::Plan::Service 0.035 {
	use Moo;
	use Types::Standard qw(ConsumerOf Bool Str InstanceOf);
	use Encode qw(encode);
	use Scalar::Util qw(blessed);
	use URI::Escape;
	use Attean::SPARQLClient;
	use namespace::clean;

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

	has 'endpoint' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariable'], required => 1);
	has 'silent' => (is => 'ro', isa => Bool, default => 0);
	has 'sparql' => (is => 'ro', isa => Str, required => 1);
	has 'user_agent' => (is => 'rw', isa => InstanceOf['LWP::UserAgent']);
	has 'request_signer' => (is => 'rw');
	has 'client' => (is => 'rw', required => 0);

	sub plan_as_string {
		my $self	= shift;
		my $sparql	= $self->sparql;
		$sparql		=~ s/\s+/ /g;
		return sprintf('Service <%s> %s', $self->endpoint->as_string, $sparql);
	}
	
	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);
		};
	}
}

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

				return Attean::Plan::Table->new( variables => $vars, rows => $rows, distinct => 0, ordered => [] );
			} else {
				my $iter	= Attean::ListIterator->new(
					item_type => 'Attean::API::Result',
					variables => \@vars,
					values => $rows
				);
				return Attean::Plan::Iterator->new( iterator => $iter, distinct => 0, ordered => [] );
			}
		} elsif ($algebra->isa('Attean::Algebra::Service')) {
			my $endpoint	= $algebra->endpoint;
			my $silent		= $algebra->silent;
			my $sparql		= sprintf('SELECT * WHERE { %s }', $child->as_sparql);
			my @vars		= $child->in_scope_variables;
			my $plan		= Attean::Plan::Service->new(
				request_signer => $self->request_signer,
				endpoint => $endpoint,
				silent => $silent,
				sparql => $sparql,
				distinct => 0,
				in_scope_variables => \@vars,
				ordered => []
			);
			return $plan;
		} elsif ($algebra->isa('Attean::Algebra::Slice')) {
			my $limit	= $algebra->limit;
			my $offset	= $algebra->offset;

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

Attean::SPARQLClient - RDF blank nodes

=head1 VERSION

This document describes Attean::SPARQLClient version 0.035

=head1 SYNOPSIS

  use v5.14;
  use Attean;
  my $client = Attean::SPARQLClient->new(endpoint => 'http://example.org/sparql');
  my $results = $client->query('SELECT * WHERE { ?s ?p ?o }');
  while (my $r = $results->next) {
    say $r->as_string;
  }

=head1 DESCRIPTION

The Attean::SPARQLClient class provides an API to execute SPARQL queries
against a remote SPARQL Protocol endpoint.

=head1 ATTRIBUTES

The following attributes exist:

=over 4

=item C<< endpoint >>

A URL of the remote service implementing the SPARQL 1.1 Protocol. This value
is a L<Attean::API::IRI>, but can be coerced from a string.

=item C<< silent >>

=item << user_agent >>

=item C<< request_signer >>

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


package Attean::SPARQLClient 0.035 {
	use Moo;
	use Types::Standard qw(ConsumerOf Bool Str InstanceOf);
	use Encode qw(encode);
	use Scalar::Util qw(blessed);
	use URI::Escape;
	use Attean::RDF qw(iri);
	use namespace::clean;

	has 'endpoint' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI'], coerce => sub { iri(shift) }, required => 1);
	has 'silent' => (is => 'ro', isa => Bool, default => 0);
	has 'user_agent' => (is => 'rw', isa => InstanceOf['LWP::UserAgent'], default => sub { my $ua = LWP::UserAgent->new(); $ua->agent("Attean/$Attean::VERSION " . $ua->_agent); $ua });
	has 'request_signer' => (is => 'rw');

=item C<< query_request( $sparql ) >>

Returns an HTTP::Request object for the given SPARQL query string.

=cut

	sub query_request {
		my $self	= shift;
		my $sparql	= shift;
		my $endpoint	= $self->endpoint->value;
		my $uri			= URI->new($endpoint);
		my %params		= $uri->query_form;
		$params{'query'} = $sparql;
		$uri->query_form(%params);
		my $url			= $uri->as_string;
		my $req			= HTTP::Request->new('GET', $url);
		if (my $signer = $self->request_signer) {
			$signer->sign($req);
		}
		return $req;
	}

=item C<< query( $sparql ) >>

Executes the given SPARQL query string at the remote endpoint. If execution is
successful, returns an Attean::API::Iterator object with the results. If
execution fails but the client C<< silent >> flag is true, returns an empty
iterator. Otherwise raises an error via C<< die >>.

=cut

	sub query {
		my $self	= shift;
		my $sparql	= shift;
		my $req		= $self->query_request($sparql);

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

							$c	= 1;
						}
					}
					$c		*= -1 if ($dirs[$i] == 0);
					last unless ($c == 0);
				}
				$c
			} map { my $r = $_; [$r, [map { $expr_eval->evaluate_expression( $_, $r, $active_graph, {} ) } @exprs]] } @rows;
			return Attean::ListIterator->new( values => \@sorted, item_type => $iter->item_type, variables => $iter->variables);
		} elsif ($algebra->isa('Attean::Algebra::Service')) {
			my $endpoint	= $algebra->endpoint->value;
			my ($pattern)	= @{ $algebra->children };
			my $sparql		= Attean::Algebra::Project->new( variables => [ map { variable($_) } $pattern->in_scope_variables ], children => [ $pattern ] )->as_sparql;
			my $silent		= $algebra->silent;
			my $client		= $self->new_service_client($endpoint, $silent);
			return $client->query($sparql);
		} elsif ($algebra->isa('Attean::Algebra::Graph')) {
			my $graph	= $algebra->graph;
			return $self->evaluate($child, $graph) if ($graph->does('Attean::API::Term'));
			
			my @iters;
			my $graphs	= $self->model->get_graphs();
			my %vars;
			while (my $g = $graphs->next) {
				next if ($g->value eq $self->default_graph->value);

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

				sub {
					my $term	= shift;
					Attean::Result->new( bindings => { map { $_ => $term } @vars } );
				},
				'Attean::API::Result',
				variables => \@vars
			);
		}
	}
	
=item C<< new_service_client( $endpoint, $silent ) >>

Returns a new Attean::SPARQLClient for use in evaluating queries using
the SPARQL Protocol against the given endpoint.

=cut

	sub new_service_client {
		my $self		= shift;
		my $endpoint	= shift;
		my $silent		= shift;
		my $client		= Attean::SPARQLClient->new(
			endpoint => $endpoint,
			silent => $silent,
			user_agent => $self->user_agent,
			request_signer => $self->request_signer,
		);
		return $client;
	}
}

package Attean::SimpleQueryEvaluator::ExpressionEvaluator 0.035 {
	use Moo;

lib/AtteanX/Parser/SPARQL.pm  view on Meta::CPAN

			$ggp	= Attean::Algebra::BGP->new();
		}
		my ($var, $expr)	= @args;
		my %in_scope	= map { $_ => 1 } $ggp->in_scope_variables;
		if (exists $in_scope{ $var->value }) {
			croak "Syntax error: BIND used with variable already in scope";
		}
		my $bind	= $class->new( children => [$ggp], variable => $var, expression => $expr );
		$self->_add_patterns( $bind );
	} elsif ($class eq 'Attean::Algebra::Service') {
		my ($endpoint, $pattern, $silent)	= @args;
		if ($endpoint->does('Attean::API::Variable')) {
			# SERVICE ?var
			croak "SERVICE ?var not implemented";
		} else {
			# SERVICE <endpoint>
			# no-op
			my $service	= Attean::Algebra::Service->new( children => [$pattern], endpoint => $endpoint, silent => $silent );
			$self->_add_patterns( $service );
		}
	} elsif ($class =~ /Attean::Algebra::(Union|Graph|Join)$/) {
		# no-op
	} else {
		croak 'Unrecognized GraphPattern: ' . $class;
	}
}

sub _SubSelect_test {

lib/AtteanX/Parser/SPARQL.pm  view on Meta::CPAN

sub _ServiceGraphPattern {
	my $self	= shift;
	$self->_expected_token(KEYWORD, 'SERVICE');
	my $silent	= $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
	$self->__close_bgp_with_filters;
	if ($self->_test_token(VAR)) {
		$self->_Var;
	} else {
		$self->_IRIref;
	}
	my ($endpoint)	= splice( @{ $self->{_stack} } );
	$self->_GroupGraphPattern;
	my $ggp	= $self->_remove_pattern;
	
	my $opt		= ['Attean::Algebra::Service', $endpoint, $ggp, ($silent ? 1 : 0)];
	$self->_add_stack( $opt );
}

# [23] OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern
# sub _OptionalGraphPattern_test {
# 	my $self	= shift;
# 	return $self->_test_token(KEYWORD, 'OPTIONAL');
# }

sub __close_bgp_with_filters {

lib/Test/Attean/W3CManifestTestSuite.pm  view on Meta::CPAN

		warn "### test     : " . $test->value . "\n";
		warn "# sparql     : $q\n";
		foreach my $data (@data) {
			warn "# data       : " . $data->value . "\n" if (blessed($data));
		}
		warn "# graph data : " . $_->value . "\n" for (@gdata);
		warn "# result     : " . $result->value . "\n";
		warn "# requires   : " . $req->value . "\n" if (blessed($req));
	}
	
	# TODO: set up remote endpoint mock

	warn "constructing model...\n" if ($self->debug);
	my $test_model	= $self->test_model();
	foreach my $data (@data) {
		eval {
			if (blessed($data)) {
				$test_model->load_urls_into_graph($self->default_graph, $data);
			}
		};
		if ($@) {

lib/Test/Attean/W3CManifestTestSuite.pm  view on Meta::CPAN

				$self->record_result('evaluation', 0, $test->value);
			};
			if ($ok) {
			} else {
				print "# failed: " . $test->value . "\n";
			}
		}
	}
}

sub mock_endpoints {
	my $self			= shift;
	my $mock			= shift;
	my $model	= shift;
	my $sdata			= shift;
	
	foreach my $sd (@$sdata) {
		my ($e)		= $model->objects( $sd, iri("${RQ}endpoint") )->elements;
		my @data	= $model->objects( $sd, iri("${RQ}data") )->elements;
		my @gdata	= $model->objects( $sd, iri("${RQ}graphData") )->elements;

		my $endpoint	= $e->value;
		my $test_model	= $self->test_model();
		foreach my $data (@data) {
			if (blessed($data)) {
				$test_model->load_urls_into_graph($self->default_graph, $data);
			}
		}
		foreach my $g (@gdata) {
			my $start = $test_model->size;
			$test_model->load_urls_into_graph($g, $g);
			my $end	= $test_model->size;
			unless ($start < $end) {
				warn "*** Loading file did not result in any new quads: " . $g;
			}
		}
		$mock->register_test_endpoint($endpoint, [$test_model, $self->default_graph]);
	}
}

sub get_actual_results {
	my $self			= shift;
	my $filename		= shift;
	my $model			= shift;
	my $sparql			= shift;
	my $base			= shift;
	my $manifest_model	= shift;

lib/Test/Attean/W3CManifestTestSuite.pm  view on Meta::CPAN

		warn "-------------\n";
	}
	
	my $testns	= 'http://example.com/test-results#';
	my $rmodel	= memory_model();

	my $results;
	if ($self->use_idp_planner) {
		my $default_graphs	= [$self->default_graph];
		my $planner	= Test::Attean::TestIDPQueryPlanner->new();
		$self->mock_endpoints($planner, $manifest_model, $sdata);
		my $plan	= $planner->plan_for_algebra($algebra, $model, $default_graphs);
		if ($self->debug) {
			warn "Walking plan:\n";
			warn $plan->as_string;
		}
		$results	= eval { $plan->evaluate($model) };
		warn $@ if $@;
	} else {
		my $e		= Test::Attean::TestSimpleQueryEvaluator->new( model => $model, default_graph => $self->default_graph );
		$self->mock_endpoints($e, $manifest_model, $sdata);
		$results	= eval { $e->evaluate($algebra, $self->default_graph) };
		warn $@ if $@;
	}
	my $count	= 1;
	
	$results	= $results->materialize;
	my $item	= $results->peek;
	
	my $type	= 'bindings';
	if ($item) {

meta/changes.ttl  view on Meta::CPAN


my:v_0-033
	a					:Version ;
	dc:issued			"2022-10-02"^^xsd:date ;
	:revision			"0.033" ;
	dcterms:replaces	my:v_0-032 ;
	dcs:changeset [
		dcs:item
			[ a dcs:Addition ; rdfs:label "Add new Attean::SPARQLClient protocol implementation." ],
			[ a dcs:Update ; rdfs:label "Update SERVICE evaluation classes to use Attean::SPARQLClient." ],
			[ a dcs:Update ; rdfs:label "Fixed handling of endpoint URLs containing query parameters." ],
			[ a dcs:Update ; rdfs:label "Protocol HTTP requests can now be signed by specifying a 'request_signer'." ]
	]
	.


my:v_0-032
	a					:Version ;
	dc:issued			"2022-08-14"^^xsd:date ;
	:revision			"0.032" ;
	dcterms:replaces	my:v_0-031 ;

t/join_rotating_planner.t  view on Meta::CPAN

	my $store	= MyTestStore->new();
	my $model	= Attean::MutableQuadModel->new( store => $store );
	my $graph	= iri('http://example.org/');
# 	my $t		= triplepattern(variable('s'), iri('p'), literal('1'));
	my $t		= triplepattern(variable('s'), iri('p'), variable('o'));
	my $v		= triplepattern(variable('s'), iri('q'), literal('xyz'));
	my $w		= triplepattern(variable('o'), iri('b'), iri('c'));

	my $bgp1	= Attean::Algebra::BGP->new(triples => [$t]);
	my $bgp2	= Attean::Algebra::BGP->new(triples => [$w]);
	my $service	= Attean::Algebra::Service->new(children => [$bgp2], endpoint => iri('http://endpoint.example.org/sparql'));
	my $bgp3	= Attean::Algebra::BGP->new(triples => [$v]);
	my $join1	= Attean::Algebra::Join->new(children => [$bgp1, $service]);

	# (t ⋈ Service(w)) ⋈ v
	my $join2	= Attean::Algebra::Join->new(children => [$join1, $bgp3]);

	subtest 'before BGP merging' => sub {
		# This tests the various possible plans that can be produced for this
		# algebra, allowing for join commutativity. Without join rotation or
		# coalescing, the resulting plan should have a top-level join, with
		# children being a quad, and another join of a quad and a service.
		# 
		# A possible plan for this algebra:
		# - Hash Join { s }
		# -   Quad { ?s, <q>, "xyz", <http://example.org/> } (distinct)
		# -   Hash Join { o }
		# -     Service <http://endpoint.example.org/sparql> SELECT * WHERE { { ?o <b> <c> . } }
		# -     Quad { ?s, <p>, ?o, <http://example.org/> } (distinct)

		my $p		= Attean::IDPQueryPlanner->new();
		my $plan	= $p->plan_for_algebra($join2, $model, [$graph]);
# 		warn $plan->as_string;

		does_ok($plan, 'Attean::API::Plan::Join');
		my ($lhs, $rhs)	= @{ $plan->children };
		my $join;
		if ($lhs->does('Attean::API::Plan::Join')) {

t/join_rotating_planner.t  view on Meta::CPAN

	};

	foreach my $planner_class (qw(MyPlanner MyPlanner1)) {
		subtest "after BGP merging ($planner_class)" => sub {
			# This test is similar, but requires that the resulting plan has
			# undergone join rotation and quad coalescing, and that the lowest
			# cost plan will be a join with children being a service and a BGP.
			# 
			# A possible plan for this algebra:
			# - NestedLoop Join
			# -   Service <http://endpoint.example.org/sparql> SELECT * WHERE { { ?o <b> <c> . } }
			# -   BGP
			# -     Quad { ?s, <p>, ?o, <http://example.org/> } (distinct)
			# -     Quad { ?s, <q>, "xyz", <http://example.org/> } (distinct)


			# (t ⋈ Service(w)) ⋈ v
			# should yield one of the following after rewriting:
			# - BGP(tv) ⋈ Service(w)
			# - Service(w) ⋈ BGP(tv)
			my $p		= $planner_class->new();

t/serializer-sparql.t  view on Meta::CPAN

	my $i	= $a->sparql_tokens;
	does_ok($i, 'Attean::API::Iterator');
	# GRAPH <graphname> { <s> <p> "1" . }
	expect_token_stream($i, [KEYWORD, IRI, LBRACE, IRI, IRI, STRING1D, DOT, RBRACE]);
	ws_is($a->as_sparql, 'GRAPH <graphname> { <s> <p> "1" . }');
};

subtest 'expected tokens: service tokens' => sub {
	my $bgp	= Attean::Algebra::BGP->new(triples => [triple(iri('s'), iri('p'), literal('1'))]);
	my $g	= iri('http://example.org/sparql');
	my $a	= Attean::Algebra::Service->new( children => [$bgp], endpoint => $g );
	my $i	= $a->sparql_tokens;
	does_ok($i, 'Attean::API::Iterator');
	# SERVICE <http://example.org/sparql> { <s> <p> "1" . }
	expect_token_stream($i, [KEYWORD, IRI, LBRACE, IRI, IRI, STRING1D, DOT, RBRACE]);
	ws_is($a->as_sparql, 'SERVICE <http://example.org/sparql> { <s> <p> "1" . }');
};

subtest 'expected tokens: union tokens' => sub {
	my $lhs	= Attean::Algebra::BGP->new(triples => [triple(iri('s'), iri('p'), literal('1'))]);
	my $rhs	= Attean::Algebra::BGP->new(triples => [triple(iri('s'), iri('p'), literal('2'))]);

t/simple-eval.t  view on Meta::CPAN

	</result>
  </results>
</sparql>
XML
		my $g		= iri('g');
		my $ep		= iri('http://example.org/sparql');
		my $e		= Attean::SimpleQueryEvaluator->new( model => $model, default_graph => $g, user_agent => $ua );
		my $t		= triplepattern(variable('s'), variable('p'), variable('o'));
		my $bgp		= Attean::Algebra::BGP->new( triples => [$t] );
		my $algebra	= Attean::Algebra::Service->new(
			endpoint => $ep,
			children => [$bgp],
		);
		my $iter	= $e->evaluate($algebra, $g);
		my @results	= $iter->elements;
		is(scalar(@results), 2, 'expected result count');
		my @objects	= sort { $a <=> $b } map { $_->value('o')->value } @results;
		is_deeply(\@objects, [3,4], 'expected values');
	}
}



( run in 1.193 second using v1.01-cache-2.11-cpan-49f99fa48dc )