File Coverage

blib/lib/RDF/Query/Plan/Aggregate.pm
Criterion Covered Total %
statement 255 296 86.1
branch 90 134 67.1
condition 16 48 33.3
subroutine 18 22 81.8
pod 11 11 100.0
total 390 511 76.3


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