File Coverage

blib/lib/DBIx/UpdateTable/FromHoH.pm
Criterion Covered Total %
statement 97 97 100.0
branch 25 34 73.5
condition 8 16 50.0
subroutine 7 7 100.0
pod 1 1 100.0
total 138 155 89.0


line stmt bran cond sub pod time code
1             package DBIx::UpdateTable::FromHoH;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2020-05-06'; # DATE
5             our $DIST = 'DBIx-UpdateTable-FromHoH'; # DIST
6             our $VERSION = '0.002'; # VERSION
7              
8 1     1   89301 use 5.010001;
  1         11  
9 1     1   6 use strict 'subs', 'vars';
  1         1  
  1         26  
10 1     1   6 use warnings;
  1         1  
  1         25  
11 1     1   1810 use Log::ger;
  1         51  
  1         5  
12              
13 1     1   258 use Exporter;
  1         2  
  1         947  
14             our @ISA = qw(Exporter);
15             our @EXPORT_OK = qw(
16             update_table_from_hoh
17             );
18              
19             our %SPEC;
20              
21             sub _eq {
22 10     10   23 my ($v1, $v2) = @_;
23 10         20 my $v1_def = defined $v1;
24 10         19 my $v2_def = defined $v2;
25 10 0 33     23 return 1 if !$v1_def && !$v2_def;
26 10 50 25     40 return 0 if $v1_def xor $v2_def;
27 10         44 $v1 eq $v2;
28             }
29              
30             $SPEC{update_table_from_hoh} = {
31             v => 1.1,
32             summary => 'Update database table from hash-of-hash',
33             description => <<'_',
34              
35             Given a table `t1` like this:
36              
37             id col1 col2 col3
38             -- ---- ---- ----
39             1 a b foo
40             2 c c bar
41             3 g h qux
42              
43             this code:
44              
45             my $res = update_table_from_hoh(
46             dbh => $dbh,
47             table => 't1',
48             key_column => 'id',
49             hoh => {
50             1 => {col1=>'a', col2=>'b'},
51             2 => {col1=>'c', col2=>'d'},
52             4 => {col1=>'e', col2=>'f'},
53             },
54             );
55              
56             will perform these SQL queries:
57              
58             UPDATE TABLE t1 SET col2='d' WHERE id='2';
59             INSERT INTO t1 (id,col1,col2) VALUES (4,'e','f');
60             DELETE FROM t1 WHERE id='3';
61              
62             to make table `t1` become like this:
63              
64             id col1 col2 col3
65             -- ---- ---- ----
66             1 a b foo
67             2 c d bar
68             4 e f qux
69              
70             _
71             args => {
72             dbh => {
73             schema => ['obj*'],
74             req => 1,
75             },
76             table => {
77             schema => 'str*',
78             req => 1,
79             },
80             hoh => {
81             schema => 'hoh*',
82             req => 1,
83             },
84             key_column => {
85             schema => 'str*',
86             req => 1,
87             },
88             data_columns => {
89             schema => ['array*', of=>'str*'],
90             },
91             use_tx => {
92             schema => 'bool*',
93             default => 1,
94             },
95             extra_insert_columns => {
96             schema => ['hos*'], # XXX or code
97             },
98             extra_update_columns => {
99             schema => ['hos*'], # XXX or code
100             },
101             },
102             };
103             sub update_table_from_hoh {
104 2     2 1 98662 my %args = @_;
105              
106 2         8 my $dbh = $args{dbh};
107 2         4 my $table = $args{table};
108 2         5 my $hoh = $args{hoh};
109 2         4 my $key_column = $args{key_column};
110 2         7 my $data_columns = $args{data_columns};
111 2   50     20 my $use_tx = $args{use_tx} // 1;
112              
113 2 50       9 unless ($data_columns) {
114 2         5 my %columns;
115 2         13 for my $key (keys %$hoh) {
116 6         15 my $row = $hoh->{$key};
117 6         26 $columns{ $_ }++ for keys %$row;
118             }
119 2         31 $data_columns = [sort keys %columns];
120             }
121              
122 2         13 my @columns = @$data_columns;
123 2 50       7 push @columns, $key_column unless grep { $_ eq $key_column } @columns;
  4         21  
124 2         12 my $columns_str = join(",", @columns);
125              
126 2 50       31 $dbh->begin_work if $use_tx;
127              
128 2         50 my $hoh_table = {};
129             GET_ROWS: {
130 2         6 my $sth = $dbh->prepare("SELECT $columns_str FROM $table");
  2         17  
131 2         330 $sth->execute;
132 2         145 while (my $row = $sth->fetchrow_hashref) {
133 6         116 $hoh_table->{ $row->{$key_column} } = $row;
134             }
135             }
136 2         44 my $num_rows_unchanged = keys %$hoh_table;
137              
138 2         6 my $num_rows_deleted = 0;
139             DELETE: {
140 2         4 for my $key (sort keys %$hoh_table) {
  2         13  
141 6 100       18 unless (exists $hoh->{$key}) {
142 1         10 $dbh->do("DELETE FROM $table WHERE $key_column=?", {}, $key);
143 1         309 $num_rows_deleted++;
144 1         4 $num_rows_unchanged--;
145             }
146             }
147             }
148              
149 2         6 my $num_rows_updated = 0;
150             UPDATE: {
151 2         4 for my $key (sort keys %$hoh) {
  2         9  
152 6 100       22 next unless exists $hoh_table->{$key};
153 5         10 my @update_columns;
154             my @values;
155 5         12 for my $column (@columns) {
156 15 100       40 next if $column eq $key_column;
157 10 100       27 unless (_eq($hoh_table->{$key}{$column}, $hoh->{$key}{$column})) {
158 1         3 push @update_columns, $column;
159 1         3 push @values, $hoh->{$key}{$column};
160             }
161             }
162 5 100       17 next unless @update_columns;
163              
164 1   50     2 for my $column (keys %{ $args{extra_update_columns} // {}}) {
  1         6  
165 1 50       4 next if grep { $column eq $_ } @columns;
  3         9  
166 1         3 push @update_columns, $column;
167 1         3 push @values, $args{extra_update_columns}{$column};
168             }
169              
170             $dbh->do("UPDATE $table SET ".
171 1         6 join(",", map {"$_=?"} @update_columns).
  2         16  
172             " WHERE $key_column=?",
173             {},
174             @values, $key);
175 1         131 $num_rows_updated++;
176 1         3 $num_rows_unchanged--;
177             }
178             }
179              
180 2         11 my $num_rows_inserted = 0;
181             INSERT: {
182 2         5 my @insert_columns = @columns;
  2         6  
183 2   100     5 my @extra_insert_columns = keys %{ $args{extra_insert_columns} // {} };
  2         14  
184 2 50       6 for my $column (@extra_insert_columns) { push @insert_columns, $column unless grep { $_ eq $column } @insert_columns }
  1         3  
  3         21  
185              
186 2         9 my $insert_columns_str = join(",", @insert_columns);
187 2         5 my $placeholders_str = join(",", map {"?"} @insert_columns);
  7         26  
188 2         10 for my $key (sort keys %$hoh) {
189 6 100       18 unless (exists $hoh_table->{$key}) {
190 1         2 my @values;
191 1         2 for my $column (@insert_columns) {
192 4 100       11 if ($column eq $key_column) {
    100          
193 1         2 push @values, $key;
194 3         10 } elsif (grep { $column eq $_ } @extra_insert_columns) {
195 1         4 push @values, $args{extra_insert_columns}{$column};
196             } else {
197 2         6 push @values, $hoh->{$key}{$column};
198             }
199             }
200 1         11 $dbh->do("INSERT INTO $table ($insert_columns_str) VALUES ($placeholders_str)", {}, @values);
201 1         185 $num_rows_inserted++;
202             }
203             }
204             }
205              
206 2 50       13540 $dbh->commit if $use_tx;
207              
208 2 100 66     75 [$num_rows_deleted || $num_rows_inserted || $num_rows_updated ? 200 : 304,
209             "OK",
210             {
211             num_rows_deleted => $num_rows_deleted,
212             num_rows_inserted => $num_rows_inserted,
213             num_rows_updated => $num_rows_updated,
214             num_rows_unchanged => $num_rows_unchanged,
215             }];
216             }
217              
218             1;
219             # ABSTRACT: Update database table from hash-of-hash
220              
221             __END__