The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;
no warnings 'redefine';
use Test::More;
use Test::Exception;
use Scalar::Util qw(blessed);
use RDF::Trine qw(literal);

use lib qw(. t);
require "models.pl";

use RDF::Query;

################################################################################
# Log::Log4perl::init( \q[
# 	log4perl.category.rdf.query.plan.exists          = TRACE, Screen
# 	
# 	log4perl.appender.Screen         = Log::Log4perl::Appender::Screen
# 	log4perl.appender.Screen.stderr  = 0
# 	log4perl.appender.Screen.layout = Log::Log4perl::Layout::SimpleLayout
# ] );
################################################################################

my @files	= map { "data/$_" } qw(t-sparql11-aggregates-1.rdf foaf.xrdf about.xrdf);
my @models	= test_models( @files );
my $tests	= (scalar(@models) * 91);
plan tests => $tests;

foreach my $model (@models) {
	print "\n#################################\n";
	print "### Using model: $model\n\n";

	
	{
		print "# SELECT SUM aggregate with GROUP BY and HAVING\n";
		my $query	= new RDF::Query ( <<"END", { lang => 'sparql11' } );
	PREFIX : <http://books.example/>
	SELECT (SUM(?lprice) AS ?totalPrice)
	WHERE {
	  ?org :affiliates ?auth .
	  ?auth :writesBook ?book .
	  ?book :price ?lprice .
	}
	GROUP BY ?org
	HAVING (SUM(?lprice) > 10)
END
		warn RDF::Query->error unless ($query);
		my ($plan, $ctx)	= $query->prepare( $model );
		my $pattern			= $query->pattern;
		my $stream	= $query->execute_plan( $plan, $ctx );
		isa_ok( $stream, 'RDF::Trine::Iterator' );
		my $count	= 0;
		while (my $row = $stream->next) {
			$count++;
			my $tp	= $row->{totalPrice};
			isa_ok( $tp, 'RDF::Trine::Node::Literal', 'got ?totalPrice value' );
			is( $tp->literal_value, '21', 'expected literal value' );
			is( $tp->literal_datatype, 'http://www.w3.org/2001/XMLSchema#integer', 'expected integer datatype' );
		}
		is( $count, 1, 'expected result count with aggregation' );
	}

	{
		print "# SELECT GROUPED Variable with GROUP BY and HAVING\n";
		my $query	= new RDF::Query ( <<"END", { lang => 'sparql11' } );
	PREFIX : <http://books.example/>
	SELECT ?org
	WHERE {
	  ?org :affiliates ?auth .
	  ?auth :writesBook ?book .
	  ?book :price ?lprice .
	}
	GROUP BY ?org
	HAVING (SUM(?lprice) > 10)
END
		warn RDF::Query->error unless ($query);
		my ($plan, $ctx)	= $query->prepare( $model );
		my $pattern			= $query->pattern;
		my $stream	= $query->execute_plan( $plan, $ctx );
		isa_ok( $stream, 'RDF::Trine::Iterator' );
		my $count	= 0;
		while (my $row = $stream->next) {
			$count++;
			my $org	= $row->{org};
			isa_ok( $org, 'RDF::Trine::Node::Resource', 'got ?org value' );
		}
		is( $count, 1, 'expected result count with aggregation' );
	}

	{
		print "# SELECT MIN with GROUP BY\n";
		my $query	= new RDF::Query ( <<"END", { lang => 'sparql11' } );
	PREFIX : <http://books.example/>
	SELECT ?auth (MIN(?lprice) AS ?min)
	WHERE {
	  ?org :affiliates ?auth .
	  ?auth :writesBook ?book .
	  ?book :price ?lprice .
	}
	GROUP BY ?auth
END
		warn RDF::Query->error unless ($query);
		my ($plan, $ctx)	= $query->prepare( $model );
		my $pattern			= $query->pattern;
		my $stream	= $query->execute_plan( $plan, $ctx );
		isa_ok( $stream, 'RDF::Trine::Iterator' );
		my $count	= 0;
		my %expect	= (
			'http://books.example/auth1'	=> 5,
			'http://books.example/auth2'	=> 7,
			'http://books.example/auth3'	=> 7,
		);
		while (my $row = $stream->next) {
			$count++;
			my $auth	= $row->{auth};
			my $val		= $row->{min};
			isa_ok( $auth, 'RDF::Trine::Node::Resource', 'got ?auth value' );
			isa_ok( $val, 'RDF::Trine::Node::Literal', 'got ?min value' );
			my $expect	= $expect{ $auth->uri_value };
			cmp_ok( $val->literal_value, '==', $expect, 'expected MIN value' );
		}
		is( $count, 3, 'expected result count with aggregation' );
	}

	{
		print "# SELECT MAX with GROUP BY\n";
		my $query	= new RDF::Query ( <<"END", { lang => 'sparql11' } );
	PREFIX : <http://books.example/>
	SELECT ?auth (MAX(?lprice) AS ?max)
	WHERE {
	  ?org :affiliates ?auth .
	  ?auth :writesBook ?book .
	  ?book :price ?lprice .
	}
	GROUP BY ?auth
END
		warn RDF::Query->error unless ($query);
		my ($plan, $ctx)	= $query->prepare( $model );
		my $pattern			= $query->pattern;
		my $stream	= $query->execute_plan( $plan, $ctx );
		isa_ok( $stream, 'RDF::Trine::Iterator' );
		my $count	= 0;
		my %expect	= (
			'http://books.example/auth1'	=> 9,
			'http://books.example/auth2'	=> 7,
			'http://books.example/auth3'	=> 7,
		);
		while (my $row = $stream->next) {
			$count++;
			my $auth	= $row->{auth};
			my $val		= $row->{max};
			isa_ok( $auth, 'RDF::Trine::Node::Resource', 'got ?auth value' );
			isa_ok( $val, 'RDF::Trine::Node::Literal', 'got ?max value' );
			my $expect	= $expect{ $auth->uri_value };
			cmp_ok( $val->literal_value, '==', $expect, 'expected MAX value' );
		}
		is( $count, 3, 'expected result count with aggregation' );
	}

	{
		print "# SELECT MAX with GROUP BY and ORDER BY DESC\n";
		my $query	= new RDF::Query ( <<"END", { lang => 'sparql11' } );
	PREFIX : <http://books.example/>
	SELECT ?auth (MAX(?lprice) AS ?max)
	WHERE {
	  ?org :affiliates ?auth .
	  ?auth :writesBook ?book .
	  ?book :price ?lprice .
	}
	GROUP BY ?auth
	ORDER BY DESC(?max)
END
		warn RDF::Query->error unless ($query);
		my ($plan, $ctx)	= $query->prepare( $model );
		my $pattern			= $query->pattern;
		my $stream	= $query->execute_plan( $plan, $ctx );
		isa_ok( $stream, 'RDF::Trine::Iterator' );
		my $count	= 0;
		my @expect	= (9, 7, 7);
		while (my $row = $stream->next) {
			$count++;
			my $val		= $row->{max};
			isa_ok( $val, 'RDF::Trine::Node::Literal', 'got ?max value' );
			my $expect	= shift(@expect);
			cmp_ok( $val->literal_value, '==', $expect, 'expected DESC MAX value' );
		}
		is( $count, 3, 'expected result count with aggregation' );
	}

	{
		print "# SELECT MAX with GROUP BY and ORDER BY ASC\n";
		my $query	= new RDF::Query ( <<"END", { lang => 'sparql11' } );
	PREFIX : <http://books.example/>
	SELECT ?auth (MAX(?lprice) AS ?max)
	WHERE {
	  ?org :affiliates ?auth .
	  ?auth :writesBook ?book .
	  ?book :price ?lprice .
	}
	GROUP BY ?auth
	ORDER BY ASC(?max)
END
		warn RDF::Query->error unless ($query);
		my ($plan, $ctx)	= $query->prepare( $model );
		my $pattern			= $query->pattern;
		my $stream	= $query->execute_plan( $plan, $ctx );
		isa_ok( $stream, 'RDF::Trine::Iterator' );
		my $count	= 0;
		my @expect	= (7, 7, 9);
		while (my $row = $stream->next) {
			$count++;
			my $val		= $row->{max};
			isa_ok( $val, 'RDF::Trine::Node::Literal', 'got ?max value' );
			my $expect	= shift(@expect);
			cmp_ok( $val->literal_value, '==', $expect, 'expected ASC MAX value' );
		}
		is( $count, 3, 'expected result count with aggregation' );
	}
	
	{
		print "# SELECT COUNT(VAR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX exif: <http://www.kanzaki.com/ns/exif#>
			PREFIX foaf: <http://xmlns.com/foaf/0.1/>
			SELECT (COUNT(?aperture) AS ?count)
			WHERE {
				?image a foaf:Image ; exif:fNumber ?aperture
			}
END
		isa_ok( $query, 'RDF::Query' ) or warn RDF::Query->error;
		
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $expect	= RDF::Query::VariableBindings->new({ count => literal('4', undef, 'http://www.w3.org/2001/XMLSchema#integer') });
			is_deeply( $row, $expect, 'value for count apertures' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT COUNT(DISTINCT VAR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX exif: <http://www.kanzaki.com/ns/exif#>
			PREFIX foaf: <http://xmlns.com/foaf/0.1/>
			SELECT (COUNT(DISTINCT ?aperture) AS ?count)
			WHERE {
				?image a foaf:Image ; exif:fNumber ?aperture
			}
END
		isa_ok( $query, 'RDF::Query' );
		
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $expect	= RDF::Query::VariableBindings->new({ count => literal('2', undef, 'http://www.w3.org/2001/XMLSchema#integer') });
			is_deeply( $row, $expect, 'value for count distinct apertures' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT MIN(STR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX foaf: <http://xmlns.com/foaf/0.1/>
			SELECT (MIN(?mbox) AS ?min)
			WHERE {
				[ a foaf:Person ; foaf:mbox_sha1sum ?mbox ]
			}
END
		isa_ok( $query, 'RDF::Query' );
		
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $expect	= RDF::Query::VariableBindings->new({ min => literal('19fc9d0234848371668cf10a1b71ac9bd4236806') });
			is_deeply( $row, $expect, 'value for min mbox_sha1sum' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT COUNT(VAR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX foaf: <http://xmlns.com/foaf/0.1/>
			SELECT (COUNT(?nick) AS ?count)
			WHERE {
				?p a foaf:Person .
				OPTIONAL {
					?p foaf:nick ?nick
				}
			}
END
		isa_ok( $query, 'RDF::Query' );
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $expect	= RDF::Query::VariableBindings->new({ count => RDF::Query::Node::Literal->new('3', undef, 'http://www.w3.org/2001/XMLSchema#integer') });
			is_deeply( $row, $expect, 'COUNT() on sometimes unbound variable' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT COUNT(VAR) with GROUP BY\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX foaf: <http://xmlns.com/foaf/0.1/>
			SELECT ?name (COUNT(?nick) AS ?count)
			WHERE {
				?p a foaf:Person ;
					foaf:name ?name;
					foaf:nick ?nick .
			}
			GROUP BY ?name
END
		isa_ok( $query, 'RDF::Query' );
		my $stream	= $query->execute( $model );
		my $count	= 0;
		
		my %expect	= ( 'Gregory Todd Williams' => 2, 'Gary P' => 1 );
		while (my $row = $stream->next) {
			my $name	= $row->{name}->literal_value;
			my $expect	= $expect{ $name };
			cmp_ok( $row->{count}->literal_value, '==', $expect, 'expected COUNT() value for variable GROUP' );
			$count++;
		}
		is( $count, 2, 'two aggreate groups' );
	}
	
	{
		print "# SELECT AVG(STR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX exif: <http://www.kanzaki.com/ns/exif#>
			SELECT (AVG(?f) AS ?avg)
			WHERE {
				?image exif:fNumber ?f
			}
END
		isa_ok( $query, 'RDF::Query' );
		
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $value	= (values %$row)[0];
			isa_ok( $value, 'RDF::Query::Node::Literal' );
			ok( $value->is_numeric_type, 'avg produces a numeric type' );
			is( $value->numeric_value, 6.125, 'expected average value' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT MIN(STR), MAX(STR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX foaf: <http://xmlns.com/foaf/0.1/>
			PREFIX exif: <http://www.kanzaki.com/ns/exif#>
			SELECT (MIN(?e) AS ?min) (MAX(?e) AS ?max)
			WHERE {
				[] foaf:mbox_sha1sum ?e
			}
END
		isa_ok( $query, 'RDF::Query' );
		
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $min	= $row->{min};
			my $max	= $row->{max};
			isa_ok( $min, 'RDF::Query::Node::Literal' );
			isa_ok( $max, 'RDF::Query::Node::Literal' );
			is( $min->literal_value, '19fc9d0234848371668cf10a1b71ac9bd4236806', 'expected MIN plain-literal' );
			is( $max->literal_value, 'f8677979059b73385c9d14cadf7d1e3652b205a8', 'expected MAX plain-literal' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT MIN(STR), MAX(STR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX dc: <http://purl.org/dc/elements/1.1/>
			SELECT (MIN(?d) AS ?min) (MAX(?d) AS ?max)
			WHERE {
				[] dc:date ?d
			}
END
		isa_ok( $query, 'RDF::Query' );
		
		# without strict errors, non-datatyped dates (in human readable form) will
		# be string-compared with dateTime-datatyped W3CDTF literals
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $min	= $row->{min};
			my $max	= $row->{max};
			isa_ok( $min, 'RDF::Query::Node::Literal' );
			isa_ok( $max, 'RDF::Query::Node::Literal' );
 			is( $min->literal_value, '2004-09-06T15:19:20+01:00', 'expected MIN plain-literal' );
 			is( $max->literal_value, 'Sat, 4 Oct 2003 20:02:22 PDT-0700', 'expected MAX plain-literal' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT GROUP_CONCAT(STR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX dc: <http://purl.org/dc/elements/1.1/>
			SELECT (GROUP_CONCAT(?d) AS ?dates)
			WHERE {
				[] dc:date ?d
			}
END
		isa_ok( $query, 'RDF::Query' );
		
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $dates	= $row->{dates};
			isa_ok( $dates, 'RDF::Query::Node::Literal' );
			my $lit	= $dates->literal_value;
			like( $lit, qr/2004-09-06T15:19:20[+]01:00/, 'expected GROUP_CONCAT plain-literal' );
			like( $lit, qr/2005-04-07T18:27:37-04:00/, 'expected GROUP_CONCAT plain-literal' );
			like( $lit, qr/2005-04-07T18:27:50-04:00/, 'expected GROUP_CONCAT plain-literal' );
			like( $lit, qr/2005-04-07T18:27:56-04:00/, 'expected GROUP_CONCAT plain-literal' );
			like( $lit, qr/Sat, 4 Oct 2003 20:02:22 PDT-0700/, 'expected GROUP_CONCAT plain-literal' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
	
	{
		print "# SELECT SAMPLE(STR)\n";
		my $query	= new RDF::Query ( <<"END", undef, undef, 'sparql11' );
			PREFIX dc: <http://purl.org/dc/elements/1.1/>
			SELECT (SAMPLE(?d) AS ?date)
			WHERE {
				[] dc:date ?d
			}
END
		isa_ok( $query, 'RDF::Query' );
		
		my $stream	= $query->execute( $model );
		my $count	= 0;
		while (my $row = $stream->next) {
			my $date	= $row->{date};
			isa_ok( $date, 'RDF::Query::Node::Literal' );
 			is( $date->literal_value, '2004-09-06T15:19:20+01:00', 'expected SAMPLE plain-literal' );
			$count++;
		}
		is( $count, 1, 'one aggreate row' );
	}
}