Attean

 view release on metacpan or  search on metacpan

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

use v5.14;
use warnings;

=head1 NAME

Attean::Expression - SPARQL Expressions

=head1 VERSION

This document describes Attean::Expression version 0.038

=head1 SYNOPSIS

  use v5.14;
  use Attean;

  my $binding = Attean::Result->new();
  my $value = Attean::ValueExpression->new( value => Attean::Literal->integer(2) );
  my $plus = Attean::BinaryExpression->new( children => [$value, $value], operator => '+' );
  my $result = $plus->evaluate($binding);
  say $result->numeric_value; # 4

=head1 DESCRIPTION

This is a utility package that defines all the Attean SPARQL expression classes
consisting of logical, numeric, and function operators, constant terms, and
variables. Expressions may be evaluated in the context of a
L<Attean::API::Result> object, and either return a L<Attean::API::Term> object
or throw a type error exception.

The expression classes are:

=over 4

=cut

use Attean::API::Expression;

=item * L<Attean::ValueExpression>

=cut

package Attean::ValueExpression 0.038 {
	use Moo;
	use Types::Standard qw(ConsumerOf);
	use AtteanX::SPARQL::Constants;
	use AtteanX::SPARQL::Token;
	use namespace::clean;

	with 'Attean::API::SPARQLSerializable';
	with 'Attean::API::Expression';

	has 'value' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariableOrTriplePattern']);

	sub arity { return 0 }
	sub BUILDARGS {
		my $class	= shift;
		return $class->SUPER::BUILDARGS(@_, operator => '_value');
	}
	
	sub tree_attributes { return qw(operator) }

	sub is_stable {
		return 1;
	}
	
	sub as_string {
		my $self	= shift;
		my $str		= $self->value->ntriples_string;
		if ($str =~ m[^"(true|false)"\^\^<http://www[.]w3[.]org/2001/XMLSchema#boolean>$]) {
			return $1;
		} elsif ($str =~ m[^"(\d+)"\^\^<http://www[.]w3[.]org/2001/XMLSchema#integer>$]) {
			return $1
		}
		return $str;
	}
	
	sub in_scope_variables {
		my $self	= shift;
		if ($self->value->does('Attean::API::Variable')) {
			return $self->value->value;
		}
		return;
	}
	
	sub sparql_tokens {
		my $self	= shift;
		return $self->value->sparql_tokens;
	}

	sub unaggregated_variables {
		my $self	= shift;
		if ($self->value->does('Attean::API::Variable')) {
			return $self->value;
		}
		return;
	}
}

=item * L<Attean::UnaryExpression>

=cut

package Attean::UnaryExpression 0.038 {
	use Moo;
	use Types::Standard qw(Enum);
	use namespace::clean;

	with 'Attean::API::UnaryExpression', 'Attean::API::Expression', 'Attean::API::UnaryQueryTree';

	my %map	= ('NOT' => '!');
	around 'BUILDARGS' => sub {
		my $orig	= shift;
		my $class	= shift;
		my $args	= $class->$orig(@_);
		my $op		= $args->{operator};
		$args->{operator}	= $map{uc($op)} if (exists $map{uc($op)});
		return $args;

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

	has 'variable'		=> (is => 'ro', isa => ConsumerOf['Attean::API::Variable'], required => 1);
	has 'order' 		=> (is => 'ro', isa => ArrayRef, required => 1, default => sub { [] });

	with 'Attean::API::AggregateExpression';
	with 'Attean::API::SPARQLSerializable';

	sub tree_attributes { return qw(operator scalar_vars variable) }

	sub is_stable {
		my $self	= shift;
		foreach my $expr (@{ $self->groups }, values %{ $self->aggregates }) {
			return 0 unless ($expr->is_stable);
		}
		return 1;
	}

	sub sparql_tokens {
		my $self	= shift;
		my $distinct	= AtteanX::SPARQL::Token->keyword('DISTINCT');
		my $func	= AtteanX::SPARQL::Token->keyword($self->operator);
		my $lparen	= AtteanX::SPARQL::Token->lparen;
		my $rparen	= AtteanX::SPARQL::Token->rparen;
		my $comma	= AtteanX::SPARQL::Token->comma;

		my @tokens;
		push(@tokens, $func);
		push(@tokens, $lparen);
		if ($self->distinct) {
			push(@tokens, $distinct);
		}
		foreach my $t (@{ $self->children }) {
			push(@tokens, $t->sparql_tokens->elements);
			push(@tokens, $comma);
		}
		if (scalar(@tokens) > 2) {
			pop(@tokens);	# remove the last comma
		}
		my $vars	= $self->scalar_vars;
		my @keys	= keys %$vars;
		if (scalar(@keys)) {
			die "TODO: Implement SPARQL serialization for aggregate scalar vars";
		}
		push(@tokens, $rparen);
		return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
	}
}

package Attean::CastExpression 0.038 {
	use Moo;
	use Types::Standard qw(Enum ConsumerOf);
	use AtteanX::SPARQL::Constants;
	use AtteanX::SPARQL::Token;
	use namespace::clean;

	with 'Attean::API::SPARQLSerializable';
	with 'Attean::API::UnaryExpression', 'Attean::API::Expression', 'Attean::API::UnaryQueryTree';

	has 'datatype'	=> (is => 'ro', isa => ConsumerOf['Attean::API::IRI']);
	sub BUILDARGS {
		my $class	= shift;
		return $class->SUPER::BUILDARGS(@_, operator => '_cast');
	}
	sub BUILD {
		my $self	= shift;
		state $type	= Enum[map { "http://www.w3.org/2001/XMLSchema#$_" } qw(integer decimal float double string boolean dateTime)];
		$type->assert_valid($self->datatype->value);
	}
	
	sub tree_attributes { return qw(operator datatype) }

	sub is_stable {
		my $self	= shift;
		foreach my $c (@{ $self->children }) {
			return 0 unless ($c->is_stable);
		}
		return 1;
	}

	sub sparql_tokens {
		my $self	= shift;
		my $dt		= AtteanX::SPARQL::Token->fast_constructor( IRI, -1, -1, -1, -1, [$self->datatype->value] ),
		my $lparen	= AtteanX::SPARQL::Token->lparen;
		my $rparen	= AtteanX::SPARQL::Token->rparen;
		my $comma	= AtteanX::SPARQL::Token->comma;

		my @tokens;
		push(@tokens, $dt, $lparen);
		foreach my $t (@{ $self->children }) {
			push(@tokens, $t->sparql_tokens->elements);
			push(@tokens, $comma);
		}
		if (scalar(@tokens) > 2) {
			pop(@tokens);	# remove the last comma
		}
		push(@tokens, $rparen);
		return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
	}
}

package Attean::ExistsExpression 0.038 {
	use Moo;
	use AtteanX::SPARQL::Constants;
	use AtteanX::SPARQL::Token;
	use Types::Standard qw(ConsumerOf);
	use namespace::clean;

	with 'Attean::API::SPARQLSerializable';
	with 'Attean::API::Expression';

	sub arity { return 0 }
	sub BUILDARGS {
		my $class	= shift;
		return $class->SUPER::BUILDARGS(@_, operator => '_exists');
	}
	has 'pattern' => (is => 'ro', isa => ConsumerOf['Attean::API::Algebra']);
	sub as_string {
		my $self	= shift;
		my $sparql	= $self->pattern->as_sparql;
		$sparql		=~ s/\s+/ /g;
		return "EXISTS { $sparql }";
	}

	sub tree_attributes { return qw(operator pattern) }

	sub is_stable {
		my $self	= shift;
		# TODO: need deep analysis of exists pattern to tell if this is stable
		# (there might be an unstable filter expression deep inside the pattern)
		return 0;
	}

	sub sparql_tokens {
		my $self	= shift;
		my $exists	= AtteanX::SPARQL::Token->keyword('EXISTS');
		my $lbrace	= AtteanX::SPARQL::Token->lbrace;
		my $rbrace	= AtteanX::SPARQL::Token->rbrace;
		my $child	= $self->pattern;
		
		my @tokens;
		push(@tokens, $exists, $lbrace);
		push(@tokens, $child->sparql_tokens->elements);
		push(@tokens, $rbrace);
		return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
	}

	sub unaggregated_variables {
		my $self	= shift;
		return map { Attean::Variable->new($_) } $self->pattern->in_scope_variables;
	}
}

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

	with 'Attean::API::Expression';
	sub arity { return 0 }
	sub BUILDARGS {
		my $class	= shift;
		return $class->SUPER::BUILDARGS(@_, operator => '_existsplan');
	}
	has 'plan' => (is => 'ro', isa => ConsumerOf['Attean::API::BindingSubstitutionPlan']);
	sub as_string {
		my $self	= shift;
		# TODO: implement as_string for EXISTS patterns
		return "Attean::ExistsPlanExpression { ... }";
	}
	sub as_sparql {
		my $self	= shift;
		my %args	= @_;
		my $level	= $args{level} // 0;
		my $sp		= $args{indent} // '    ';
		my $indent	= $sp x $level;

		# TODO: implement as_string for EXISTS patterns
		return "EXISTS { " . $self->pattern->as_sparql( level => $level+1, indent => $sp ) . " }";
	}

	sub tree_attributes { return qw(operator plan) }

	sub is_stable {
		my $self	= shift;
		# TODO: need deep analysis of exists pattern to tell if this is stable
		# (there might be an unstable filter expression deep inside the pattern)
		return 0;
	}

	sub unaggregated_variables {
		my $self	= shift;
		die "unaggregated_variables cannot be called on Attean::ExistsPlanExpression";
	}
}


1;

__END__

=back

=head1 BUGS

Please report any bugs or feature requests to through the GitHub web interface
at L<https://github.com/kasei/attean/issues>.

=head1 SEE ALSO



=head1 AUTHOR

Gregory Todd Williams  C<< <gwilliams@cpan.org> >>

=head1 COPYRIGHT

Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut



( run in 1.115 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )