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.916.
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   193 use strict;
  35         81  
  35         917  
24 35     35   190 use warnings;
  35         78  
  35         993  
25 35     35   199 use base qw(RDF::Query::Plan);
  35         79  
  35         3333  
26 35     35   203 use Scalar::Util qw(blessed);
  35         101  
  35         1771  
27              
28 35     35   216 use RDF::Query::Error qw(:try);
  35         77  
  35         262  
29 35     35   4534 use RDF::Query::Node qw(literal);
  35         79  
  35         2360  
30              
31             ######################################################################
32              
33             our ($VERSION);
34             BEGIN {
35 35     35   143136 $VERSION = '2.916';
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 23 my $class = shift;
46 16         26 my $plan = shift;
47 16         21 my $groupby = shift;
48 16         47 my %args = @_;
49 16 50       18 my @ops = @{ $args{ 'expressions' } || [] };
  16         67  
50 16         92 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       77 ($_->isa('RDF::Query::Node::Variable'))
  7 50       40  
56             ? $_->name
57             : $_->isa('RDF::Query::Node')
58             ? ()
59             : $_->referenced_variables
60             } @$groupby)
61             ];
62 16         81 return $self;
63             }
64              
65             =item C<< execute ( $execution_context ) >>
66              
67             =cut
68              
69             sub execute ($) {
70 16     16 1 23 my $self = shift;
71 16         25 my $context = shift;
72 16         50 $self->[0]{delegate} = $context->delegate;
73 16 50       55 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         34 my $plan = $self->[1];
77 16         66 $plan->execute( $context );
78            
79 16         65 my $l = Log::Log4perl->get_logger("rdf.query.plan.aggregate");
80 16 50       794 if ($plan->state == $self->OPEN) {
81 16         85 my $query = $context->query;
82 16         58 my $bridge = $context->model;
83            
84 16         31 my %seen;
85             my %groups;
86 0         0 my %group_data;
87 16         50 my @groupby = $self->groupby;
88 16         30 my @ops = @{ $self->[3] };
  16         40  
89 16         34 local($RDF::Query::Node::Literal::LAZY_COMPARISONS) = 1;
90            
91 16         66 ROW: while (my $row = $plan->next) {
92 75         246 $l->debug("aggregate on $row");
93 75         8502 my @group;
94 75         158 foreach my $g (@groupby) {
95 27         104 my $v = $query->var_or_expr_value( $row, $g, $context );
96 27 50       250 if ($g->isa('RDF::Query::Expression::Alias')) {
97 0         0 $row->{ $g->name } = $v;
98             }
99 27         70 push(@group, $v);
100             }
101            
102             # my @group = map { $query->var_or_expr_value( $row, $_ ) } @groupby;
103 75 50       147 my $group = join('<<<', map { blessed($_) ? $_->as_string : '' } map { blessed($_) ? $query->var_or_expr_value( $row, $_, $context ) : '' } @group);
  27 50       126  
  27         132  
104            
105 75         338 push( @{ $group_data{ 'rows' }{ $group } }, $row );
  75         266  
106 75         199 $group_data{ 'groups' }{ $group } = \@group;
107 75         396 foreach my $i (0 .. $#groupby) {
108 27         38 my $g = $groupby[$i];
109 27         177 $group_data{ 'groupby_sample' }{ $group } = $row;
110             }
111             }
112            
113 16         30 my @groups = values %{ $group_data{'groups'} };
  16         59  
114 16 50       53 if (scalar(@groups) == 0) {
115 0         0 $group_data{'rows'}{''} = [];
116 0         0 $group_data{'groups'}{''} = [];
117             }
118            
119 16         30 my @rows;
120 16         36 GROUP: foreach my $group (keys %{ $group_data{ 'rows' } }) {
  16         65  
121 27         119 $l->debug( "group: $group" );
122 27         181 my %options;
123             my %aggregates;
124 0         0 my %passthrough_data;
125 27         41 my @group = @{ $group_data{ 'groups' }{ $group } };
  27         91  
126            
127 27         71 my $row_sample = $group_data{ 'groupby_sample' }{ $group };
128 27         56 foreach my $g (@groupby) {
129 18 50 33     168 if ($g->isa('RDF::Query::Expression::Alias') or $g->isa('RDF::Query::Node::Variable')) {
    0          
130 18         52 my $name = $g->name;
131 18         129 $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         48 my @operation_data = (map { [ @{ $_ }, \%aggregates ] } @ops);
  29         42  
  29         130  
144 27         61 foreach my $data (@operation_data) {
145 29         221 my $aggregate_data = pop(@$data);
146 29         102 my ($alias, $op, $opts, @cols) = @$data;
147 29         82 $options{ $alias } = $opts;
148 29         61 my $distinct = ($op =~ /^(.*)-DISTINCT$/);
149 29         56 $op =~ s/-DISTINCT$//;
150 29         37 my $col = $cols[0];
151 29         35 my %agg_group_seen;
152             try {
153 29     29   1000 foreach my $row (@{ $group_data{ 'rows' }{ $group } }) {
  29         92  
154 88 50       348 my @proj_rows = map { (blessed($col)) ? $query->var_or_expr_value( $row, $col, $context ) : '*' } @cols;
  88         498  
155 88 100       662 if ($distinct) {
156 4 100       17 next if ($agg_group_seen{ join('<<<', @proj_rows) }++);
157             }
158            
159 86         389 $l->debug( "- row: $row" );
160             # $groups{ $group } ||= { map { $_ => $row->{ $_ } } @groupby };
161 86 100       3578 if ($op eq 'COUNT') {
    100          
    100          
    100          
    100          
    100          
    50          
162 14         45 $l->debug("- aggregate op: COUNT");
163 14         81 my $should_inc = 0;
164 14 50 33     66 if (not(blessed($col)) and $col eq '*') {
165 0         0 $should_inc = 1;
166             } else {
167 14         47 my $value = $query->var_or_expr_value( $row, $col, $context );
168 14 100       89 $should_inc = (defined $value) ? 1 : 0;
169             }
170            
171 14         43 $aggregate_data->{ $alias }{ $group }[0] = $op;
172 14         45 $aggregate_data->{ $alias }{ $group }[1] += $should_inc;
173             } elsif ($op eq 'SUM') {
174 8         23 $l->debug("- aggregate op: SUM");
175 8         62 my $value = $query->var_or_expr_value( $row, $col, $context );
176 8         49 my $type = _node_type( $value );
177 8         36 $aggregate_data->{ $alias }{ $group }[0] = $op;
178            
179 8         13 my $strict = 1;
180 8 50 33     52 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         35 my $v = $value->numeric_value;
186 8 100       18 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  8         32  
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         11 $aggregate_data->{ $alias }{ $group }[1] += $v;
197 4         26 $aggregate_data->{ $alias }{ $group }[2] = RDF::Query::Expression::Binary->promote_type('+', $type, $aggregate_data->{ $alias }{ $group }[2]);
198             } else {
199 4         10 $aggregate_data->{ $alias }{ $group }[1] = $v;
200 4         18 $aggregate_data->{ $alias }{ $group }[2] = $type;
201             }
202             # }
203             } elsif ($op eq 'MAX') {
204 25         74 $l->debug("- aggregate op: MAX");
205 25         196 my $value = $query->var_or_expr_value( $row, $col, $context );
206 25         159 my $type = _node_type( $value );
207 25         83 $aggregate_data->{ $alias }{ $group }[0] = $op;
208            
209 25         40 my $strict = 1;
210 25 100       29 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  25         79  
211 14 100 0     71 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       24 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         5 $strict = 0;
217             }
218             }
219            
220 14 100       34 if ($strict) {
221 12 100       54 if ($value > $aggregate_data->{ $alias }{ $group }[1]) {
222 1         3 $aggregate_data->{ $alias }{ $group }[1] = $value;
223 1         6 $aggregate_data->{ $alias }{ $group }[2] = $type;
224             }
225             } else {
226 2 100       7 if ("$value" gt "$aggregate_data->{ $alias }{ $group }[1]") {
227 1         26 $aggregate_data->{ $alias }{ $group }[1] = $value;
228 1         6 $aggregate_data->{ $alias }{ $group }[2] = $type;
229             }
230             }
231             } else {
232 11         28 $aggregate_data->{ $alias }{ $group }[1] = $value;
233 11         49 $aggregate_data->{ $alias }{ $group }[2] = $type;
234             }
235             } elsif ($op eq 'MIN') {
236 25         71 $l->debug("- aggregate op: MIN");
237 25         185 my $value = $query->var_or_expr_value( $row, $col, $context );
238 25         161 my $type = _node_type( $value );
239 25         79 $aggregate_data->{ $alias }{ $group }[0] = $op;
240            
241 25         36 my $strict = 1;
242 25 100       32 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  25         70  
243 19 100 0     90 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       57 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         5 $strict = 0;
249             }
250             }
251            
252 19 100       38 if ($strict) {
253 16 100       64 if ($value < $aggregate_data->{ $alias }{ $group }[1]) {
254 6         14 $aggregate_data->{ $alias }{ $group }[1] = $value;
255 6         23 $aggregate_data->{ $alias }{ $group }[2] = $type;
256             }
257             } else {
258 3 50       11 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         14 $aggregate_data->{ $alias }{ $group }[1] = $value;
265 6         25 $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         15 $l->debug("- aggregate op: SAMPLE");
270 5         38 my $value = $query->var_or_expr_value( $row, $col, $context );
271 5         33 my $type = _node_type( $value );
272 5         12 $aggregate_data->{ $alias }{ $group }[0] = $op;
273            
274 5 100       7 if (scalar( @{ $aggregate_data->{ $alias }{ $group } } ) > 1) {
  5         16  
275 4 100       13 if ("$value" lt "$aggregate_data->{ $alias }{ $group }[1]") {
276 2         46 $aggregate_data->{ $alias }{ $group }[1] = $value;
277 2         11 $aggregate_data->{ $alias }{ $group }[2] = $type;
278             }
279             } else {
280 1         3 $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         32 my $value = $query->var_or_expr_value( $row, $col, $context );
286 4         28 my $type = _node_type( $value );
287             # warn "AVG\t$group\t" . $value->as_string . "\n";
288 4         12 $aggregate_data->{ $alias }{ $group }[0] = $op;
289            
290 4 50 33     44 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     46 if (blessed($value) and $value->isa('RDF::Query::Node::Literal') and $value->is_numeric_type) {
      33        
296 4         8 $aggregate_data->{ $alias }{ $group }[1]++;
297 4         15 $aggregate_data->{ $alias }{ $group }[2] += $value->numeric_value;
298 4 100       18 if ($aggregate_data->{ $alias }{ $group }[3]) {
299 3         16 $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         15 $l->debug("- aggregate op: GROUP_CONCAT");
306 5         34 $aggregate_data->{ $alias }{ $group }[0] = $op;
307            
308 5         18 my $str = RDF::Query::Node::Resource->new('sparql:str');
309            
310             my @values = map {
311 5         62 my $expr = RDF::Query::Expression::Function->new( $str, $query->var_or_expr_value( $row, $_, $context ) );
  5         19  
312 5         19 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         6 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         587 }
328            
329 27         1719 my %row = %passthrough_data;
330 27         70 foreach my $agg (keys %aggregates) {
331 29 50       107 if (defined($aggregates{$agg}{$group})) {
332 29         68 my $op = $aggregates{ $agg }{ $group }[0];
333 29 100       137 if ($op eq 'AVG') {
    100          
    100          
334 1         6 my $value = ($aggregates{ $agg }{ $group }[2] / $aggregates{ $agg }{ $group }[1]);
335 1         3 my $type = $aggregates{ $agg }{ $group }[3];
336 1 50       7 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     10 $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       6 my $j = (exists $options{$agg}{seperator}) ? $options{$agg}{seperator} : ' ';
342 1         3 $row{ $agg } = RDF::Query::Node::Literal->new( join($j, @{ $aggregates{ $agg }{ $group }[1] }) );
  1         8  
343             } elsif ($op =~ /COUNT/) {
344 5         13 my $value = $aggregates{ $agg }{ $group }[1];
345 5 50 33     41 $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         44 my $value = $aggregates{ $agg }{ $group }[1];
349 22 100 66     216 $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         599 my $vars = RDF::Query::VariableBindings->new( \%row );
356 27         105 $l->debug("aggregate row: $vars");
357 27         1101 push(@rows, $vars);
358             }
359            
360 16         51 $self->[0]{rows} = \@rows;
361 16         89 $self->state( $self->OPEN );
362             } else {
363 0         0 warn "could not execute plan in distinct";
364             }
365 16         233 $self;
366             }
367              
368             =item C<< next >>
369              
370             =cut
371              
372             sub next {
373 43     43 1 57 my $self = shift;
374 43 50       122 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         59 my $bindings = shift(@{ $self->[0]{rows} });
  43         106  
378 43 50       150 if (my $d = $self->delegate) {
379 0         0 $d->log_result( $self, $bindings );
380             }
381 43         107 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       51 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         43 delete $self->[0]{rows};
394 16 50       47 if (defined($self->[1])) {
395 16         95 $self->[1]->close();
396             }
397 16         67 $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 43 my $self = shift;
408 31         135 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 58 my $self = shift;
419 45 50       56 return @{ $self->[2] || [] };
  45         185  
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 22 my $self = shift;
450 15         20 my $context = shift;
451 15         21 my $indent = shift;
452 15         23 my $more = ' ';
453 15         57 my $psse = $self->pattern->sse( $context, "${indent}${more}" );
454 15         58 my @group = map { $_->sse($context, "${indent}${more}") } $self->groupby;
  6         27  
455 15         78 my $gsse = join(' ', @group);
456 15         29 my @ops;
457 15         28 foreach my $p (@{ $self->[3] }) {
  15         43  
458 17         56 my ($alias, $op, $options, @cols) = @$p;
459 17 50       42 my $cols = '(' . join(' ', map { ref($_) ? $_->sse($context, "${indent}${more}") : '*' } @cols) . ')';
  17         86  
460 17         173 my @opts_keys = keys %$options;
461 17 50       49 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         78 push(@ops, qq[("$alias" "$op" $cols)]);
466             }
467             }
468 15         40 my $osse = join(' ', @ops);
469 15         158 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 28 my $self = shift;
512 16         41 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 21 my $self = shift;
523 14         35 my $sort = [ $self->groupby ];
524 14         103 return []; # XXX aggregates are actually sorted, so figure out what should go here...
525             }
526              
527             sub _node_type {
528 67     67   89 my $node = shift;
529 67 50       196 if (blessed($node)) {
530 67 50       213 if ($node->isa('RDF::Query::Node::Literal')) {
    0          
    0          
531 67 100       172 if (my $type = $node->literal_datatype) {
532 34         184 return $type;
533             } else {
534 33         194 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