view release on metacpan or search on metacpan
- (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');
}
}