File Coverage

blib/lib/DBIx/Class/ParseError/Parser.pm
Criterion Covered Total %
statement 89 93 95.7
branch 18 22 81.8
condition 2 3 66.6
subroutine 15 16 93.7
pod 3 3 100.0
total 127 137 92.7


line stmt bran cond sub pod time code
1             package DBIx::Class::ParseError::Parser;
2              
3 4     4   2067 use strict;
  4         12  
  4         106  
4 4     4   20 use warnings;
  4         7  
  4         86  
5 4     4   17 use Moo::Role;
  4         11  
  4         28  
6 4     4   2693 use DBIx::Class::ParseError::Error;
  4         10  
  4         120  
7 4     4   25 use Regexp::Common qw(list);
  4         8  
  4         22  
8              
9             requires 'type_regex';
10              
11             has _schema => (
12             is => 'ro', required => 1, init_arg => 'schema',
13             );
14              
15             has _source_table_map => (
16             is => 'lazy', builder => '_build_source_table_map',
17             );
18              
19             sub _build_source_table_map {
20 4     4   34 my $self = shift;
21 4         14 my $schema = $self->_schema;
22             return {
23             map {
24 4         27 my $source = $schema->source($_);
  12         720  
25 12         382 ( $schema->class($_) => $source, $source->from => $source )
26             } $schema->sources
27             };
28             }
29              
30             sub parse_type {
31 17     17 1 43 my ($self, $error) = @_;
32 17         70 my $type_regex = $self->type_regex;
33 17         5307 foreach (sort keys %$type_regex) {
34 63 100       433 if ( my @data = $error =~ $type_regex->{$_} ) {
35             return {
36             name => $_,
37 17 50       160 data => [ grep { defined && length } @data ],
  17         165  
38             };
39             }
40             }
41 0         0 return { name => 'unknown' };
42             }
43              
44             sub _add_info_from_type {
45 17     17   35 my ($self, $error_info, $error_type) = @_;
46 17         34 my $table = $error_info->{'table'};
47 17     6   62 my $replace_dots = sub { $_[0] =~ s{\.}{_}; $_[0] };
  6         22  
  6         18  
48 17     16   55 my $remove_table = sub { $_[0] =~ s{^$table\.}{}i; $_[0] };
  16         78  
  16         91  
49 17         285 my $source = $self->_source_table_map->{$table};
50             my $action_type_map = {
51             unique_key => sub {
52 6     6   28 my $unique_keys = { $source->unique_constraints };
53             my $unique_data = [
54 6         56 map { $replace_dots->($_) } @{ $error_type->{'data'} }
  6         15  
  6         14  
55             ];
56 6 100       20 if ( my $unique_cols = $unique_keys->{ $unique_data->[0] } ) {
57             $error_info->{'columns'} = [
58 2         6 map { $remove_table->($_) } @$unique_cols
  2         7  
59             ];
60             }
61             else {
62 4         9 $error_info->{'type'} = 'primary_key';
63             $error_info->{'columns'} = [
64 4         8 map { $remove_table->($_) } @{ $unique_keys->{'primary'} }
  4         8  
  4         9  
65             ];
66             }
67             },
68             primary_key => sub {
69             $error_info->{'columns'} = [
70 0     0   0 map { $remove_table->($_) } $source->primary_columns
  0         0  
71             ];
72             },
73             default => sub {
74 11 100   11   17 if ( @{ $error_type->{'data'} } ) {
  11         37  
75             $error_info->{'columns'} = [
76 10         19 map { $remove_table->($_) } @{ $error_type->{'data'} }
  10         26  
  10         20  
77             ];
78             }
79             },
80 17         236 };
81             ( $action_type_map->{ $error_type->{'name'} }
82 17   66     101 || $action_type_map->{'default'} )->();
83 17         287 return $error_info;
84             }
85              
86             sub _build_column_data {
87 12     12   32 my ($self, $column_keys, $column_values) = @_;
88 12         34 $column_keys =~ s{\s*=\s*\?}{}g;
89 12         75 $column_keys = [split(/\,\s+/, $column_keys)];
90 12 100       42 if ($column_values) {
91 11         27 $column_values =~ s{\'}{}g;
92             $column_values = [
93 11         46 map { (split(/=/))[1] }
  30         86  
94             split(/\,\s+/, $column_values)
95             ];
96             return {
97             map {
98 11         31 my $value = shift(@$column_values);
  28         38  
99 28 100       124 $_ => ($value =~ m/undef/ ? undef : $value)
100             } @$column_keys
101             };
102             }
103             else {
104 1         3 return { map { $_ => undef } @$column_keys };
  3         9  
105             }
106             }
107              
108             sub parse_general_info {
109 17     17 1 42 my ($self, $error, $error_type) = @_;
110              
111 17         67 my $insert_re = qr{
112             INSERT\s+INTO\s+
113             (\w+)\s+
114             \( \s* ($RE{list}{-pat => '\w+'}|\w+)\s* \)\s+
115             VALUES\s+
116             \( \s* (?:$RE{list}{-pat => '\?'}|\?)\s* \)\s*\"
117             \s*\w*\s*\w*:?\s*
118             ($RE{list}{-pat => '\d=\'?[\w\s]+\'?'})?
119             }ix;
120              
121 17         4849 my $update_re = qr{
122             UPDATE\s+
123             (\w+)\s+
124             SET\s+
125             ($RE{list}{-pat => '\w+\s*\=\s*\?'}|\w+\s*\=\s*\?)\s*
126             (?:WHERE)?.*\"
127             \s*\w*\s*\w*:?\s*
128             ($RE{list}{-pat => '\d=\'?[\w\s]+\'?'})?
129             }ix;
130              
131 17         3322 my $missing_column_re = qr{
132             (store_column|get_column)\(\)\:\s+
133             no\s+such\s+column\s+['"](\w+)['"]\s+
134             on\s+($RE{list}{-pat => '\w+'}{-sep => '::'})
135             }ix;
136              
137 17         2312 my $source_table_map = $self->_source_table_map;
138              
139 17         387 my $error_info;
140 17 100       66 if ( $error =~ $insert_re ) {
    100          
    50          
141 9         260 my ($table, $column_keys, $column_values) = ($1, $2, $3);
142 9         30 $error_info = {
143             operation => 'insert',
144             table => $table,
145             column_data => $self->_build_column_data(
146             $column_keys, $column_values
147             ),
148             };
149             }
150             elsif ( $error =~ $update_re ) {
151 3         71 my ($table, $column_keys, $column_values) = ($1, $2, $3);
152 3         12 $error_info = {
153             operation => 'update',
154             table => $table,
155             column_data => $self->_build_column_data(
156             $column_keys, $column_values
157             ),
158             };
159             }
160             elsif ( $error =~ $missing_column_re ) {
161 5         111 my ($op_key, $column, $source_name) = ($1, $2, $3);
162 5         17 my $op_mapping = {
163             'store_column' => 'insert',
164             'get_column' => 'update',
165             };
166 5         14 my $source = $source_table_map->{ $source_name };
167             $error_info = {
168 5 50       44 operation => $op_mapping->{ lc $op_key },
169             $source ? ( table => $source->name ) : (),
170             columns => [$column],
171             };
172             }
173             else {
174 0         0 die 'Parsing error string failed';
175             }
176              
177 17 50       73 if (my $source = $source_table_map->{ $error_info->{'table'} }) {
178 17         62 $error_info->{'source_name'} = $source->source_name;
179             }
180              
181 17         54 return $self->_add_info_from_type($error_info, $error_type);
182             }
183              
184             sub process {
185 17     17 1 4268 my ($self, $error) = @_;
186 17         56 my $error_type = $self->parse_type($error);
187             my $err_info = {
188             type => $error_type->{'name'},
189 17         50 %{ $self->parse_general_info($error, $error_type) },
  17         58  
190             };
191 17         77 return DBIx::Class::ParseError::Error->new(
192             message => "$error",
193             %$err_info,
194             );
195             }
196              
197             1;
198              
199             __END__