File Coverage

blib/lib/SQL/Abstract/Plugin/Upsert.pm
Criterion Covered Total %
statement 27 30 90.0
branch 9 10 90.0
condition 1 3 33.3
subroutine 5 5 100.0
pod 0 1 0.0
total 42 49 85.7


line stmt bran cond sub pod time code
1             package SQL::Abstract::Plugin::Upsert;
2              
3 5     5   3412 use Moo;
  5         10  
  5         29  
4             with 'SQL::Abstract::Role::Plugin';
5              
6             sub register_extensions {
7 10     10 0 5206 my ($self, $sqla) = @_;
8             $sqla->clauses_of(
9             'insert' => sub {
10 10     10   120 my ($self, @clauses) = @_;
11 10         30 splice @clauses, -1, 0, 'on_conflict';
12 10         42 @clauses;
13             }
14 10         72 );
15             $sqla->clause_expander(
16             'insert.on_conflict' => sub {
17 7     7   1835 my ($sqla, $name, $value) = @_;
18             # a 0 is DO NOTHING
19 7 100       47 return (on_conflict => { -do => 'NOTHING' }) unless $value;
20              
21             # if we have keys that aren't prefixed by -, it's { TARGET => { SET_THIS => TO_THIS } }
22 3 50 33     24 if (!grep /^-/, keys %$value and keys %$value == 1) {
23 0         0 my @built;
24 0         0 for my $target (keys %$value) {
25 0         0 $value = { -target => $target, -set => $value->{$target} };
26             }
27             }
28 3         32 my (undef, $set) = $sqla->expand_clause('update.set', $value->{-set});
29 3         18 my $target = $sqla->expand_expr({ -list => $value->{-target} }, -ident);
30 3         648 return (on_conflict => { -target => $target, -set => $set });
31             }
32 10         255 );
33             $sqla->clause_renderer(
34             'insert.on_conflict' => sub {
35 7     7   9997 my ($sqla, $type, $value) = @_;
36 7         17 my @parts;
37 7         27 @parts = { -keyword => 'on conflict' };
38 7 100       35 if (my $target = $value->{-target}) {
39 3         10 push @parts, '(', $sqla->render_aqt($target), ')';
40             }
41 7 100       219 if (my $what_to_do = $value->{-do}) {
42 4         19 push @parts, { -keyword => "DO $what_to_do" };
43             }
44 7 100       32 if (my $set = $value->{-set}) {
45 3         11 push @parts, { -keyword => 'DO UPDATE SET' };
46 3         7 push @parts, $set;
47             }
48 7         25 $sqla->join_query_parts(' ', @parts);
49             }
50 10         587 );
51             }
52              
53             our $VERSION = '0.02_1';
54              
55             1;
56              
57             =encoding utf8
58              
59             =head1 NAME
60              
61             SQL::Abstract::Plugin::Upsert - Upsert (ON CONFLICT) support for SQLA2!
62              
63             =head1 SYNOPSIS
64              
65             # pass this to an SQLA 'insert'
66             { on_conflict => 0 }
67             # ON CONFLICT DO NOTHING
68              
69             # Do an update
70             { on_conflict => { id => { name => 'Bob Bobson' } } }
71             # ON CONFLICT (id) DO UPDATE SET name = 'Bob Bobson'
72              
73             # Slightly fancier
74             { on_conflict => { id => { name => \'name || ' ' || excluded.name } } }
75             # ON CONFLICT (id) DO UPDATE SET name = name || ' ' || excluded.name
76              
77             # More explicit
78             { on_conflict => { -target => 'id', -set => { name => 'Bob Bobson' } } }
79             # ON CONFLICT (id) DO UPDATE SET name = 'Bob Bobson'
80              
81             =head1 DESCRIPTION
82              
83             This is a work in progress to support upserts in SQLA2.
84              
85             B
86              
87             =head2 Using with DBIx::Class
88              
89             In order to use this with DBIx::Class, you need to add plugins to your Result and ResultSet classes.
90              
91             # In your Result:: Classes (you could also just inherit from it)
92             __PACKAGE__->load_components('Row::SQLA2Support');
93              
94             # In your ResultSet Classes (you could also just inherit from it)
95             __PACKAGE__->load_components('ResultSet::SQLA2Support')
96              
97             Now you can do the following cool things!
98              
99             =head3 create
100              
101             When making a new Row (like using $rs->create and friends), you can pass in a -on_conflict key which will get passed through to the INSERT for that row.
102              
103             $rs->create({ id => 3, name => 'John', -on_conflict => 0 });
104             # ON CONFLICT DO NOTHING
105            
106             You can also pass a -upsert key to let us create the correct ON CONFLICT clause to just
107             stomp any existing row. This is safer than the usual find_or_create. This handles
108             composite PKs just fine, by the way.
109              
110             $rs->create({ id => 3, name => 'Bob Bobson', -upsert => 1 })
111             # ON CONFLICT (id) DO UPDATE SET name = 'Bob Bobson'
112              
113             =head3 populate
114              
115             When doing a multi-insert, you can pass in a second arg after the rows to be passed
116             through to SQLA2; this allows you to do a blanket ON CONFLICT DO NOTHING for the whole bunch of INSERTs.
117              
118             $rs->populate([
119             # one million rows later
120             ], { on_conflict => 0 })
121              
122             =cut