File Coverage

blib/lib/RDF/Query/Plan/Aggregate.pm
Criterion Covered Total %
statement 253 296 85.4
branch 89 134 66.4
condition 16 48 33.3
subroutine 18 22 81.8
pod 11 11 100.0
total 387 511 75.7


line stmt bran cond sub pod time code
1             # RDF::Query::Plan::Aggregate
2             # -----------------------------------------------------------------------------
3              
4             =head1 NAME
5              
6             RDF::Query::Plan::Aggregate - Executable query plan for Aggregates.
7              
8             =head1 VERSION
9              
10             This document describes RDF::Query::Plan::Aggregate version 2.915_01.
11              
12             =head1 METHODS
13              
14             Beyond the methods documented below, this class inherits methods from the
15             L<RDF::Query::Plan> class.
16              
17             =over 4
18              
19             =cut
20              
21             package RDF::Query::Plan::Aggregate;
22              
23 35     35   186 use strict;
  35         78  
  35         892  
24 35     35   188 use warnings;
  35         80  
  35         982  
25 35     35   192 use base qw(RDF::Query::Plan);
  35         85  
  35         3287  
26 35     35   190 use Scalar::Util qw(blessed);
  35         76  
  35         1800  
27              
28 35     35   201 use RDF::Query::Error qw(:try);
  35         89  
  35         257  
29 35     35   4430 use RDF::Query::Node qw(literal);
  35         76  
  35         2422  
30              
31             ######################################################################
32              
33             our ($VERSION);
34             BEGIN {
35 35     35   141049 $VERSION = '2.915_01';
36             }
37              
38             ######################################################################
39              
40             =item C<< new ( $pattern, \@group_by, expressions => [ [ $alias, $op, \%options, @attributes ], ... ] ) >>
41              
42             =cut
43              
44             sub new {
45 16     16 1 51 my $class = shift;
46 16         20 my $plan = shift;
47 16         22 my $groupby = shift;
48 16         43 my %args = @_;
49 16 50       22 my @ops = @{ $args{ 'expressions' } || [] };
  16         59  
50 16         60 my $self = $class->SUPER::new( $plan, $groupby, \@ops );
51             $self->[0]{referenced_variables} = [
52             RDF::Query::_uniq(
53             $plan->referenced_variables,
54             map {
55 16 0       75 ($_->isa('RDF::Query::Node::Variable'))
  7 50       42  
56             ? $_->name
57             : $_->isa('RDF::Query::Node')
58             ? ()
59             : $_->referenced_variables
60             } @$groupby)
61             ];
62 16         94 return $self;
63             }
64              
65             =item C<< execute ( $execution_context ) >>
66              
67             =cut
68              
69             sub execute ($) {
70 16     16 1 32 my $self = shift;
71 16         21 my $context = shift;
72 16         58 $self->[0]{delegate} = $context->delegate;
73 16 50       58 if ($self->state == $self->OPEN) {
74 0         0 throw RDF::Query::Error::ExecutionError -text => "AGGREGATE plan can't be executed while already open";
75             }
76 16         32 my $plan = $self->[1];
77 16         62 $plan->execute( $context );
78            
79 16         65 my $l = Log::Log4perl->get_logger("rdf.query.plan.aggregate");
80 16 50       775 if ($plan->state == $self->OPEN) {
81 16         62 my $query = $context->query;
82 16         54 my $bridge = $context->model;
83            
84 16         33 my %seen;
85             my %groups;
86 0         0 my %group_data;
87 16         51 my @groupby = $self->groupby;
88 16         32 my @ops = @{ $self->[3] };
  16         43  
89 16         38 local($RDF::Query::Node::Literal::LAZY_COMPARISONS) = 1;
90            
91 16         69 ROW: while (my $row = $plan->next) {
92 75         280 $l->debug("aggregate on $row");
93 75         8482 my @group;
94 75         176 foreach my $g (@groupby) {
95 27         105 my $v = $query->var_or_expr_value( $row, $g, $context );
96 27 50       243 if ($g->isa('RDF::Query::Expression::Alias')) {
97 0         0 $row->{ $g->name } = $v;
98             }
99 27         73 push(@group, $v);
100             }
101            
102             # my @group = map { $query->var_or_expr_value( $row, $_ ) } @groupby;
103 75 50       150 my $group = join('<<<', map { blessed($_) ? $_->as_string : '' } map { blessed($_) ? $query->var_or_expr_value( $row, $_, $context ) : '' } @group);
  27 50       126  
  27         137  
104            
105 75         332 push( @{ $group_data{ 'rows' }{ $group } }, $row );
  75         228  
106 75         201 $group_data{ 'groups' }{ $group } = \@group;
107 75         353 foreach my $i (0 .. $#groupby) {
108 27         46 my $g = $groupby[$i];
109 27         174 $group_data{ 'groupby_sample' }{ $group } = $row;
110             }
111             }
112            
113 16         32 my @groups = values %{ $group_data{'groups'} };
  16         57  
114 16 50       51 if (scalar(@groups) == 0) {
115 0         0 $group_data{'rows'}{''} = [];
116 0         0 $group_data{'groups'}{''} = [];
117             }
118            
119 16         25 my @rows;
120 16         30 GROUP: foreach my $group (keys %{ $group_data{ 'rows' } }) {
  16         55  
121 27         106 $l->debug( "group: $group" );
122 27         178 my %options;
123             my %aggregates;
124 0         0 my %passthrough_data;
125 27         41 my @group = @{ $group_data{ 'groups' }{ $group } };
  27         88  
126            
127 27         70 my $row_sample = $group_data{ 'groupby_sample' }{ $group };
128 27         49 foreach my $g (@groupby) {
129 18 50 33     189 if ($g->isa('RDF::Query::Expression::Alias') or $g->isa('RDF::Query::Node::Variable')) {
    0          
130 18         64 my $name = $g->name;
131 18         119 $passthrough_data{ $name } = $row_sample->{ $name };
132             } elsif ($g->isa('RDF::Query::Expression')) {
133 0         0 my @names = $g->referenced_variables;
134 0         0 foreach my $name (@names) {
135 0         0 $passthrough_data{ $name } = $row_sample->{ $name };
136             }
137             } else {
138 0         0 my $name = $g->sse;
139 0         0 $passthrough_data{ $name } = $row_sample->{ $name };
140             }
141             }
142            
143 27         54 my @operation_data = (map { [ @{ $_ }, \%aggregates ] } @ops);
  29         39  
  29         118  
144 27         58 foreach my $data (@operation_data) {
145 29         220 my $aggregate_data = pop(@$data);
146 29         109 my ($alias, $op, $opts, @cols) = @$data;
147 29         75 $options{ $alias } = $opts;
148 29         54 my $distinct = ($op =~ /^(.*)-DISTINCT$/);
149 29         43 $op =~ s/-DISTINCT$//;
150 29         37 my $col = $cols[0];
151 29         35 my %agg_group_seen;
152             try {
153 29     29   995 foreach my $row (@{ $group_data{ 'rows' }{ $group } }) {
  29         91  
154 88 50       314 my @proj_rows = map { (blessed($col)) ? $query->var_or_expr_value( $row, $col, $context ) : '*' } @cols;
  88         512  
155 88 100       646 if ($distinct) {
156 4 100       16 next if ($agg_group_seen{ join('<<<', @proj_rows) }++);
157             }
158            
159 86         383 $l->debug( "- row: $row" );
160             # $groups{ $group } ||= { map { $_ => $row->{ $_ } } @groupby };
161 86 100       3544 if ($op eq 'COUNT') {
    100          
    100          
    100          
    100          
    100          
    50          
162 14         37 $l->debug("- aggregate op: COUNT");
163 14         86 my $should_inc = 0;
164 14 50 33     68 if (not(blessed($col)) and $col eq '*') {
165 0         0 $should_inc = 1;
166             } else {
167 14         44 my $value = $query->var_or_expr_value( $row, $col, $context );
168 14 100       90 $should_inc = (defined $value) ? 1 : 0;
169             }
170            
171 14         40 $aggregate_data->{ $alias }{ $group }[0] = $op;
172 14         42 $aggregate_data->{ $alias }{ $group }[1] += $should_inc;
173             } elsif ($op eq 'SUM') {
174 8         23 $l->debug("- aggregate op: SUM");
175 8         63 my $value = $query->var_or_expr_value( $row, $col, $context );
176 8         48 my $type = _node_type( $value );
177 8         26 $aggregate_data->{ $alias }{ $group }[0] = $op;
178            
179 8         12 my $strict = 1;
180 8 50 33     47 unless ($value->isa('RDF::Query::Node::Literal') and $value->is_numeric_type) {
181 0         0 throw RDF::Query::Error::TypeError -text => "Cannot compute SUM aggregate with a non-numeric term: " . $value->as_ntriples;
182             }
183            
184             # if ($value->isa('RDF::Query::Node::Literal')) {
185 8         33 my $v = $value->numeric_value;
186 8 100       17 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  8         31  
187 4 50 0     20 if ($type ne $aggregate_data->{ $alias }{ $group }[2] and not($value->isa('RDF::Query::Node::Literal') and $value->is_numeric_type and blessed($aggregate_data->{ $alias }{ $group }[1]) and $aggregate_data->{ $alias }{ $group }[1]->isa('RDF::Query::Node::Literal') and $aggregate_data->{ $alias }{ $group }[1]->is_numeric_type)) {
      33        
188 0 0       0 if ($context->strict_errors) {
189 0         0 delete $aggregate_data->{ $alias }{ $group };
190 0         0 throw RDF::Query::Error::ComparisonError -text => "Cannot compute SUM aggregate over nodes of multiple, non-numeric types";
191             } else {
192 0         0 $strict = 0;
193             }
194             }
195            
196 4         10 $aggregate_data->{ $alias }{ $group }[1] += $v;
197 4         24 $aggregate_data->{ $alias }{ $group }[2] = RDF::Query::Expression::Binary->promote_type('+', $type, $aggregate_data->{ $alias }{ $group }[2]);
198             } else {
199 4         11 $aggregate_data->{ $alias }{ $group }[1] = $v;
200 4         18 $aggregate_data->{ $alias }{ $group }[2] = $type;
201             }
202             # }
203             } elsif ($op eq 'MAX') {
204 25         69 $l->debug("- aggregate op: MAX");
205 25         192 my $value = $query->var_or_expr_value( $row, $col, $context );
206 25         149 my $type = _node_type( $value );
207 25         82 $aggregate_data->{ $alias }{ $group }[0] = $op;
208            
209 25         39 my $strict = 1;
210 25 100       30 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  25         82  
211 14 100 0     72 if ($type ne $aggregate_data->{ $alias }{ $group }[2] and not($value->isa('RDF::Query::Node::Literal') and $value->is_numeric_type and blessed($aggregate_data->{ $alias }{ $group }[1]) and $aggregate_data->{ $alias }{ $group }[1]->isa('RDF::Query::Node::Literal') and $aggregate_data->{ $alias }{ $group }[1]->is_numeric_type)) {
      66        
212 2 50       33 if ($context->strict_errors) {
213 0         0 delete $aggregate_data->{ $alias }{ $group };
214 0         0 throw RDF::Query::Error::ComparisonError -text => "Cannot compute MAX aggregate over nodes of multiple, non-numeric types";
215             } else {
216 2         4 $strict = 0;
217             }
218             }
219            
220 14 100       28 if ($strict) {
221 12 100       51 if ($value > $aggregate_data->{ $alias }{ $group }[1]) {
222 4         10 $aggregate_data->{ $alias }{ $group }[1] = $value;
223 4         23 $aggregate_data->{ $alias }{ $group }[2] = $type;
224             }
225             } else {
226 2 100       7 if ("$value" gt "$aggregate_data->{ $alias }{ $group }[1]") {
227 1         24 $aggregate_data->{ $alias }{ $group }[1] = $value;
228 1         5 $aggregate_data->{ $alias }{ $group }[2] = $type;
229             }
230             }
231             } else {
232 11         27 $aggregate_data->{ $alias }{ $group }[1] = $value;
233 11         48 $aggregate_data->{ $alias }{ $group }[2] = $type;
234             }
235             } elsif ($op eq 'MIN') {
236 25         69 $l->debug("- aggregate op: MIN");
237 25         191 my $value = $query->var_or_expr_value( $row, $col, $context );
238 25         150 my $type = _node_type( $value );
239 25         71 $aggregate_data->{ $alias }{ $group }[0] = $op;
240            
241 25         35 my $strict = 1;
242 25 100       30 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  25         72  
243 19 100 0     83 if ($type ne $aggregate_data->{ $alias }{ $group }[2] and not($value->isa('RDF::Query::Node::Literal') and $value->is_numeric_type and blessed($aggregate_data->{ $alias }{ $group }[1]) and $aggregate_data->{ $alias }{ $group }[1]->isa('RDF::Query::Node::Literal') and $aggregate_data->{ $alias }{ $group }[1]->is_numeric_type)) {
      66        
244 3 50       49 if ($context->strict_errors) {
245 0         0 delete $aggregate_data->{ $alias }{ $group };
246 0         0 throw RDF::Query::Error::ComparisonError -text => "Cannot compute MIN aggregate over nodes of multiple, non-numeric types";
247             } else {
248 3         6 $strict = 0;
249             }
250             }
251            
252 19 100       38 if ($strict) {
253 16 100       66 if ($value < $aggregate_data->{ $alias }{ $group }[1]) {
254 5         12 $aggregate_data->{ $alias }{ $group }[1] = $value;
255 5         21 $aggregate_data->{ $alias }{ $group }[2] = $type;
256             }
257             } else {
258 3 50       9 if ("$value" lt "$aggregate_data->{ $alias }{ $group }[1]") {
259 0         0 $aggregate_data->{ $alias }{ $group }[1] = $value;
260 0         0 $aggregate_data->{ $alias }{ $group }[2] = $type;
261             }
262             }
263             } else {
264 6         18 $aggregate_data->{ $alias }{ $group }[1] = $value;
265 6         22 $aggregate_data->{ $alias }{ $group }[2] = $type;
266             }
267             } elsif ($op eq 'SAMPLE') {
268             ### this is just the MIN code from above, without the strict comparison checking
269 5         18 $l->debug("- aggregate op: SAMPLE");
270 5         38 my $value = $query->var_or_expr_value( $row, $col, $context );
271 5         30 my $type = _node_type( $value );
272 5         14 $aggregate_data->{ $alias }{ $group }[0] = $op;
273            
274 5 100       7 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  5         17  
275 4 100       13 if ("$value" lt "$aggregate_data->{ $alias }{ $group }[1]") {
276 1         23 $aggregate_data->{ $alias }{ $group }[1] = $value;
277 1         5 $aggregate_data->{ $alias }{ $group }[2] = $type;
278             }
279             } else {
280 1         2 $aggregate_data->{ $alias }{ $group }[1] = $value;
281 1         4 $aggregate_data->{ $alias }{ $group }[2] = $type;
282             }
283             } elsif ($op eq 'AVG') {
284 4         12 $l->debug("- aggregate op: AVG");
285 4         30 my $value = $query->var_or_expr_value( $row, $col, $context );
286 4         27 my $type = _node_type( $value );
287             # warn "AVG\t$group\t" . $value->as_string . "\n";
288 4         11 $aggregate_data->{ $alias }{ $group }[0] = $op;
289            
290 4 50 33     41 unless (blessed($value) and $value->isa('RDF::Query::Node::Literal') and $value->is_numeric_type) {
      33        
291 0         0 delete $aggregate_data->{ $alias }{ $group };
292 0         0 throw RDF::Query::Error::ComparisonError -text => "Cannot compute AVG aggregate over non-numeric nodes";
293             }
294            
295 4 50 33     42 if (blessed($value) and $value->isa('RDF::Query::Node::Literal') and $value->is_numeric_type) {
      33        
296 4         9 $aggregate_data->{ $alias }{ $group }[1]++;
297 4         12 $aggregate_data->{ $alias }{ $group }[2] += $value->numeric_value;
298 4 100       17 if ($aggregate_data->{ $alias }{ $group }[3]) {
299 3         15 $aggregate_data->{ $alias }{ $group }[3] = RDF::Query::Expression::Binary->promote_type('+', $type, $aggregate_data->{ $alias }{ $group }[3]);
300             } else {
301 1         5 $aggregate_data->{ $alias }{ $group }[3] = $type;
302             }
303             }
304             } elsif ($op eq 'GROUP_CONCAT') {
305 5         18 $l->debug("- aggregate op: GROUP_CONCAT");
306 5         35 $aggregate_data->{ $alias }{ $group }[0] = $op;
307            
308 5         15 my $str = RDF::Query::Node::Resource->new('sparql:str');
309            
310             my @values = map {
311 5         70 my $expr = RDF::Query::Expression::Function->new( $str, $query->var_or_expr_value( $row, $_, $context ) );
  5         19  
312 5         20 my $val = $expr->evaluate( $context->query, $row );
313 5 50       28 blessed($val) ? $val->literal_value : '';
314             } @cols;
315            
316             # warn "adding '$string' to group_concat aggregate";
317 5         7 push( @{ $aggregate_data->{ $alias }{ $group }[1] }, @values );
  5         24  
318             } else {
319 0         0 throw RDF::Query::Error -text => "Unknown aggregate operator $op";
320             }
321             }
322             } catch RDF::Query::Error::ComparisonError with {
323 0     0   0 delete $aggregate_data->{ $alias }{ $group };
324             } catch RDF::Query::Error::TypeError with {
325 0     0   0 delete $aggregate_data->{ $alias }{ $group };
326             }
327 29         551 }
328            
329 27         1693 my %row = %passthrough_data;
330 27         65 foreach my $agg (keys %aggregates) {
331 29 50       105 if (defined($aggregates{$agg}{$group})) {
332 29         68 my $op = $aggregates{ $agg }{ $group }[0];
333 29 100       134 if ($op eq 'AVG') {
    100          
    100          
334 1         4 my $value = ($aggregates{ $agg }{ $group }[2] / $aggregates{ $agg }{ $group }[1]);
335 1         3 my $type = $aggregates{ $agg }{ $group }[3];
336 1 50       6 if ($type eq 'http://www.w3.org/2001/XMLSchema#integer') {
337 0         0 $type = 'http://www.w3.org/2001/XMLSchema#decimal';
338             }
339 1 50 33     11 $row{ $agg } = (blessed($value) and $value->isa('RDF::Trine::Node')) ? $value : RDF::Trine::Node::Literal->new( $value, undef, $type, 1 );
340             } elsif ($op eq 'GROUP_CONCAT') {
341 1 50       5 my $j = (exists $options{$agg}{seperator}) ? $options{$agg}{seperator} : ' ';
342 1         4 $row{ $agg } = RDF::Query::Node::Literal->new( join($j, @{ $aggregates{ $agg }{ $group }[1] }) );
  1         6  
343             } elsif ($op =~ /COUNT/) {
344 5         12 my $value = $aggregates{ $agg }{ $group }[1];
345 5 50 33     38 $row{ $agg } = (blessed($value) and $value->isa('RDF::Trine::Node')) ? $value : RDF::Trine::Node::Literal->new( $value, undef, 'http://www.w3.org/2001/XMLSchema#integer', 1 );
346             } else {
347 22 50       67 if (defined($aggregates{$agg}{$group})) {
348 22         42 my $value = $aggregates{ $agg }{ $group }[1];
349 22 100 66     201 $row{ $agg } = (blessed($value) and $value->isa('RDF::Trine::Node')) ? $value : RDF::Trine::Node::Literal->new( $value, undef, $aggregates{ $agg }{ $group }[2], 1 );
350             }
351             }
352             }
353             }
354            
355 27         567 my $vars = RDF::Query::VariableBindings->new( \%row );
356 27         115 $l->debug("aggregate row: $vars");
357 27         1106 push(@rows, $vars);
358             }
359            
360 16         58 $self->[0]{rows} = \@rows;
361 16         84 $self->state( $self->OPEN );
362             } else {
363 0         0 warn "could not execute plan in distinct";
364             }
365 16         220 $self;
366             }
367              
368             =item C<< next >>
369              
370             =cut
371              
372             sub next {
373 43     43 1 61 my $self = shift;
374 43 50       138 unless ($self->state == $self->OPEN) {
375 0         0 throw RDF::Query::Error::ExecutionError -text => "next() cannot be called on an un-open AGGREGATE";
376             }
377 43         58 my $bindings = shift(@{ $self->[0]{rows} });
  43         110  
378 43 50       142 if (my $d = $self->delegate) {
379 0         0 $d->log_result( $self, $bindings );
380             }
381 43         104 return $bindings;
382             }
383              
384             =item C<< close >>
385              
386             =cut
387              
388             sub close {
389 16     16 1 26 my $self = shift;
390 16 50       46 unless ($self->state == $self->OPEN) {
391 0         0 throw RDF::Query::Error::ExecutionError -text => "close() cannot be called on an un-open AGGREGATE";
392             }
393 16         37 delete $self->[0]{rows};
394 16 50       47 if (defined($self->[1])) {
395 16         69 $self->[1]->close();
396             }
397 16         54 $self->SUPER::close();
398             }
399              
400             =item C<< pattern >>
401              
402             Returns the query plan that will be used to produce the aggregated data.
403              
404             =cut
405              
406             sub pattern {
407 31     31 1 42 my $self = shift;
408 31         136 return $self->[1];
409             }
410              
411             =item C<< groupby >>
412              
413             Returns the grouping arguments that will be used to produce the aggregated data.
414              
415             =cut
416              
417             sub groupby {
418 45     45 1 69 my $self = shift;
419 45 50       53 return @{ $self->[2] || [] };
  45         164  
420             }
421              
422             =item C<< plan_node_name >>
423              
424             Returns the string name of this plan node, suitable for use in serialization.
425              
426             =cut
427              
428             sub plan_node_name {
429 0     0 1 0 return 'aggregate';
430             }
431              
432             =item C<< plan_node_data >>
433              
434             Returns the data for this plan node that corresponds to the values described by
435             the signature returned by C<< plan_prototype >>.
436              
437             =cut
438              
439             sub plan_node_data {
440 0     0 1 0 my $self = shift;
441 0         0 return ($self->pattern, $self->groupby);
442             }
443              
444             =item C<< sse ( $context, $indent ) >>
445              
446             =cut
447              
448             sub sse {
449 15     15 1 25 my $self = shift;
450 15         20 my $context = shift;
451 15         27 my $indent = shift;
452 15         19 my $more = ' ';
453 15         45 my $psse = $self->pattern->sse( $context, "${indent}${more}" );
454 15         55 my @group = map { $_->sse($context, "${indent}${more}") } $self->groupby;
  6         21  
455 15         74 my $gsse = join(' ', @group);
456 15         24 my @ops;
457 15         17 foreach my $p (@{ $self->[3] }) {
  15         47  
458 17         54 my ($alias, $op, $options, @cols) = @$p;
459 17 50       39 my $cols = '(' . join(' ', map { ref($_) ? $_->sse($context, "${indent}${more}") : '*' } @cols) . ')';
  17         82  
460 17         165 my @opts_keys = keys %$options;
461 17 50       37 if (@opts_keys) {
462 0         0 my $opt_string = '(' . join(' ', map { $_, qq["$options->{$_}"] } @opts_keys) . ')';
  0         0  
463 0         0 push(@ops, qq[("$alias" "$op" $cols $opt_string)]);
464             } else {
465 17         83 push(@ops, qq[("$alias" "$op" $cols)]);
466             }
467             }
468 15         33 my $osse = join(' ', @ops);
469 15         152 return sprintf(
470             "(aggregate\n${indent}${more}%s\n${indent}${more}(%s)\n${indent}${more}(%s))",
471             $psse,
472             $gsse,
473             $osse,
474             );
475             }
476              
477             # =item C<< plan_prototype >>
478             #
479             # Returns a list of scalar identifiers for the type of the content (children)
480             # nodes of this plan node. See L<RDF::Query::Plan> for a list of the allowable
481             # identifiers.
482             #
483             # =cut
484             #
485             # sub plan_prototype {
486             # my $self = shift;
487             # return qw(P \E *\ssW);
488             # }
489             #
490             # =item C<< plan_node_data >>
491             #
492             # Returns the data for this plan node that corresponds to the values described by
493             # the signature returned by C<< plan_prototype >>.
494             #
495             # =cut
496             #
497             # sub plan_node_data {
498             # my $self = shift;
499             # my @group = $self->groupby;
500             # my @ops = @{ $self->[3] };
501             # return ($self->pattern, \@group, map { [@$_] } @ops);
502             # }
503              
504             =item C<< distinct >>
505              
506             Returns true if the pattern is guaranteed to return distinct results.
507              
508             =cut
509              
510             sub distinct {
511 16     16 1 24 my $self = shift;
512 16         44 return $self->pattern->distinct;
513             }
514              
515             =item C<< ordered >>
516              
517             Returns true if the pattern is guaranteed to return ordered results.
518              
519             =cut
520              
521             sub ordered {
522 14     14 1 23 my $self = shift;
523 14         30 my $sort = [ $self->groupby ];
524 14         97 return []; # XXX aggregates are actually sorted, so figure out what should go here...
525             }
526              
527             sub _node_type {
528 67     67   87 my $node = shift;
529 67 50       203 if (blessed($node)) {
530 67 50       210 if ($node->isa('RDF::Query::Node::Literal')) {
    0          
    0          
531 67 100       177 if (my $type = $node->literal_datatype) {
532 34         171 return $type;
533             } else {
534 33         168 return 'literal';
535             }
536             } elsif ($node->isa('RDF::Query::Node::Resource')) {
537 0           return 'resource';
538             } elsif ($node->isa('RDF::Query::Node::Blank')) {
539 0           return 'blank';
540             } else {
541 0           return '';
542             }
543             } else {
544 0           return '';
545             }
546             }
547              
548             1;
549              
550             __END__
551              
552             =back
553              
554             =head1 AUTHOR
555              
556             Gregory Todd Williams <gwilliams@cpan.org>
557              
558             =cut