File Coverage

blib/lib/FalkorDB/QueryResult.pm
Criterion Covered Total %
statement 18 143 12.5
branch 0 82 0.0
condition 0 36 0.0
subroutine 6 32 18.7
pod 17 19 89.4
total 41 312 13.1


line stmt bran cond sub pod time code
1             package FalkorDB::QueryResult;
2              
3 6     6   50 use strict;
  6         8  
  6         167  
4 6     6   19 use warnings;
  6         10  
  6         183  
5 6     6   2014 use FalkorDB::Node;
  6         14  
  6         137  
6 6     6   2044 use FalkorDB::Edge;
  6         14  
  6         166  
7 6     6   25 use Scalar::Util qw(looks_like_number);
  6         8  
  6         226  
8 6     6   1964 use FalkorDB::Path;
  6         14  
  6         9667  
9              
10             sub new {
11 0     0 0   my ( $class, %args ) = @_;
12 0           my $self = bless \%args, $class;
13 0           $self->_parse_stats();
14 0           return $self;
15             }
16              
17 0     0 1   sub header { shift->{header} }
18 0     0 1   sub result_set { shift->{result_set} }
19 0     0 1   sub stats { shift->{stats} }
20              
21 0 0   0 1   sub labels_added { shift->{labels_added} || 0 }
22 0 0   0 1   sub nodes_created { shift->{nodes_created} || 0 }
23 0 0   0 1   sub nodes_deleted { shift->{nodes_deleted} || 0 }
24 0 0   0 1   sub properties_set { shift->{properties_set} || 0 }
25 0 0   0 1   sub relationships_created { shift->{relationships_created} || 0 }
26 0 0   0 1   sub relationships_deleted { shift->{relationships_deleted} || 0 }
27 0 0   0 1   sub cached_execution { shift->{cached_execution} || 0 }
28 0 0   0 1   sub internal_execution_time { shift->{internal_execution_time} || 0 }
29              
30             sub row_count {
31 0     0 1   my ($self) = @_;
32 0 0         return scalar @{ $self->{result_set} || [] };
  0            
33             }
34              
35             sub get_row {
36 0     0 1   my ( $self, $idx ) = @_;
37 0           return $self->{result_set}->[$idx];
38             }
39              
40             sub next_row {
41 0     0 1   my ($self) = @_;
42 0   0       $self->{_iterator_idx} //= 0;
43 0 0         if ( $self->{_iterator_idx} < $self->row_count ) {
44 0           return $self->{result_set}->[ $self->{_iterator_idx}++ ];
45             }
46 0           return;
47             }
48              
49             sub reset_iterator {
50 0     0 1   my ($self) = @_;
51 0           $self->{_iterator_idx} = 0;
52             }
53              
54             sub hashes {
55 0     0 1   my ($self) = @_;
56 0           my $header = $self->header;
57 0           my $result_set = $self->result_set;
58 0           my @hashes;
59 0           for my $row (@$result_set) {
60 0           my %hash;
61 0           for my $i ( 0 .. $#$header ) {
62 0           $hash{ $header->[$i] } = $row->[$i];
63             }
64 0           push @hashes, \%hash;
65             }
66 0           return \@hashes;
67             }
68              
69             sub next_hash {
70 0     0 1   my ($self) = @_;
71 0           my $row = $self->next_row();
72 0 0         return unless $row;
73 0           my $header = $self->header;
74 0           my %hash;
75 0           for my $i ( 0 .. $#$header ) {
76 0           $hash{ $header->[$i] } = $row->[$i];
77             }
78 0           return \%hash;
79             }
80              
81             # --- Parsing logic ---
82              
83             sub new_from_raw {
84 0     0 0   my ( $class, $raw_res ) = @_;
85              
86 0           my ( $header, $rows, $stats );
87 0 0         if ( ref $raw_res eq 'ARRAY' ) {
88 0 0         if ( @$raw_res == 1 ) {
    0          
89 0           $stats = $raw_res->[0];
90 0           $header = [];
91 0           $rows = [];
92             }
93             elsif ( @$raw_res == 3 ) {
94 0           $header = $raw_res->[0];
95 0           $rows = $raw_res->[1];
96 0           $stats = $raw_res->[2];
97             }
98             else {
99 0           $stats = [];
100 0           $header = [];
101 0           $rows = [];
102             }
103             }
104              
105 0           my @processed_rows;
106 0 0         if ( ref $rows eq 'ARRAY' ) {
107 0           for my $row (@$rows) {
108 0           my @processed_row;
109 0 0         if ( ref $row eq 'ARRAY' ) {
110 0           for my $cell (@$row) {
111 0           push @processed_row, _parse_cell($cell);
112             }
113             }
114             else {
115 0           push @processed_row, _parse_cell($row);
116             }
117 0           push @processed_rows, \@processed_row;
118             }
119             }
120              
121 0   0       return $class->new(
      0        
122             header => $header || [],
123             result_set => \@processed_rows,
124             stats => $stats || [],
125             );
126             }
127              
128             sub _parse_cell {
129 0     0     my ($val) = @_;
130              
131 0 0 0       if ( _is_node($val) ) {
    0 0        
    0          
    0          
    0          
132 0           return FalkorDB::Node->new_from_resp($val);
133             }
134             elsif ( _is_edge($val) ) {
135 0           return FalkorDB::Edge->new_from_resp($val);
136             }
137             elsif ( _is_path($val) ) {
138 0           return FalkorDB::Path->new_from_string($val);
139             }
140             elsif ( defined $val && !ref $val && $val =~ /^\[.*\]$/ ) {
141 0           return _parse_array_string($val);
142             }
143             elsif ( ref $val eq 'ARRAY' ) {
144 0           return [ map { _parse_cell($_) } @$val ];
  0            
145             }
146             else {
147 0           return $val;
148             }
149             }
150              
151             sub _is_node {
152 0     0     my ($val) = @_;
153 0 0 0       return unless ref $val eq 'ARRAY' && @$val == 3;
154 0 0         my %keys = map { ref $_ eq 'ARRAY' ? ( $_->[0] => 1 ) : () } @$val;
  0            
155 0   0       return $keys{id} && $keys{labels} && $keys{properties};
156             }
157              
158             sub _is_edge {
159 0     0     my ($val) = @_;
160 0 0 0       return unless ref $val eq 'ARRAY' && @$val == 5;
161 0 0         my %keys = map { ref $_ eq 'ARRAY' ? ( $_->[0] => 1 ) : () } @$val;
  0            
162             return
163             $keys{id}
164             && $keys{type}
165             && $keys{src_node}
166             && $keys{dest_node}
167 0   0       && $keys{properties};
168             }
169              
170             sub _is_path {
171 0     0     my ($val) = @_;
172 0 0 0       return unless defined $val && !ref $val;
173 0           return $val =~ /^\[\s*(\(\d+\)|\[\d+\])(?:\s*,\s*(\(\d+\)|\[\d+\]))*\s*\]$/;
174             }
175              
176             sub _parse_array_string {
177 0     0     my ($str) = @_;
178 0 0         if ( $str =~ /^\[(.*)\]$/ ) {
179 0           my $content = $1;
180 0 0         return [] if $content =~ /^\s*$/;
181 0           my @elements = split /,\s*/, $content;
182 0           for my $el (@elements) {
183 0           $el =~ s/^['"]//;
184 0           $el =~ s/['"]$//;
185 0 0         if ( looks_like_number($el) ) {
186 0           $el = 0 + $el;
187             }
188             }
189 0           return \@elements;
190             }
191 0           return;
192             }
193              
194             sub _parse_properties {
195 0     0     my ($props_array) = @_;
196 0           my %props;
197 0 0         if ( ref $props_array eq 'ARRAY' ) {
198 0           for my $pair (@$props_array) {
199 0 0 0       if ( ref $pair eq 'ARRAY' && @$pair == 2 ) {
200 0           my ( $k, $v ) = @$pair;
201 0 0 0       if ( defined $v && !ref $v && $v =~ /^\[.*\]$/ ) {
      0        
202 0           $v = _parse_array_string($v);
203             }
204 0           $props{$k} = $v;
205             }
206             }
207             }
208 0           return \%props;
209             }
210              
211             # --- Statistics Parsing ---
212              
213             sub _parse_stats {
214 0     0     my ($self) = @_;
215 0           my $stats = $self->{stats};
216 0 0         return unless ref $stats eq 'ARRAY';
217              
218 0           for my $stat (@$stats) {
219 0 0         if ( $stat =~ /^Labels added:\s*(\d+)/i ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
220 0           $self->{labels_added} = 0 + $1;
221             }
222             elsif ( $stat =~ /^Nodes created:\s*(\d+)/i ) {
223 0           $self->{nodes_created} = 0 + $1;
224             }
225             elsif ( $stat =~ /^Nodes deleted:\s*(\d+)/i ) {
226 0           $self->{nodes_deleted} = 0 + $1;
227             }
228             elsif ( $stat =~ /^Properties set:\s*(\d+)/i ) {
229 0           $self->{properties_set} = 0 + $1;
230             }
231             elsif ( $stat =~ /^Relationships created:\s*(\d+)/i ) {
232 0           $self->{relationships_created} = 0 + $1;
233             }
234             elsif ( $stat =~ /^Relationships deleted:\s*(\d+)/i ) {
235 0           $self->{relationships_deleted} = 0 + $1;
236             }
237             elsif ( $stat =~ /^Cached execution:\s*(\d+)/i ) {
238 0           $self->{cached_execution} = 0 + $1;
239             }
240             elsif ( $stat =~ /^Query internal execution time:\s*([\d\.]+)/i ) {
241 0           $self->{internal_execution_time} = 0 + $1;
242             }
243             }
244             }
245              
246             1;
247             __END__