DBIx-QuickORM
view release on metacpan or search on metacpan
t/AI/handle/upsert.t view on Meta::CPAN
use Test2::V0;
use DBI;
use File::Temp qw/tempdir/;
# An upsert whose data consists solely of primary key fields has no non-key
# fields for the conflict UPDATE SET, which used to generate a syntax error
# ('DO UPDATE SET RETURNING ...'). A no-op assignment is injected instead,
# and both the insert and the conflict case must return the row.
BEGIN {
skip_all "DBD::SQLite is required for these tests"
unless eval { require DBD::SQLite; 1 };
}
require DBIx::QuickORM;
my $dir = tempdir(CLEANUP => 1);
my $dsn = "dbi:SQLite:dbname=$dir/upsert.sqlite";
{
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
$dbh->do('CREATE TABLE tags (name TEXT NOT NULL PRIMARY KEY)');
$dbh->disconnect;
}
my $con = DBIx::QuickORM->quick(credentials => {dsn => $dsn});
my $h = $con->handle('tags');
subtest insert_case => sub {
my $row = $h->upsert({name => 'perl'});
ok($row, "pk-only upsert returned a row when inserting");
is($row->field('name'), 'perl', "row carries the inserted key");
is($h->count, 1, "one row in the table");
};
subtest conflict_case => sub {
my $row = $h->upsert({name => 'perl'});
ok($row, "pk-only upsert returned a row on conflict");
is($row->field('name'), 'perl', "row carries the conflicting key");
is($h->count, 1, "still one row in the table");
};
subtest mixed_still_updates => sub {
{
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
$dbh->do('CREATE TABLE example (id INTEGER PRIMARY KEY, name TEXT)');
$dbh->disconnect;
}
my $con2 = DBIx::QuickORM->quick(credentials => {dsn => $dsn});
my $h2 = $con2->handle('example');
my $row = $h2->upsert({id => 1, name => 'a'});
is($row->field('name'), 'a', "upsert with non-key fields inserts them");
my $row2 = $h2->upsert({id => 1, name => 'b'});
is($row2->field('name'), 'b', "upsert with non-key fields still updates on conflict");
is($h2->count, 1, "no duplicate row");
};
subtest literal_value_containing_returning => sub {
{
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
$dbh->do('CREATE TABLE phrases (id INTEGER PRIMARY KEY, note TEXT)');
$dbh->disconnect;
}
my $con2 = DBIx::QuickORM->quick(credentials => {dsn => $dsn});
my $h2 = $con2->handle('phrases');
my $row;
ok(
lives { $row = $h2->upsert({id => 1, note => \"'contains returning token'"}) },
"upsert keeps the RETURNING clause separate from a literal value containing the word",
) or note $@;
is($row->field('id'), 1, "upsert returned the row primary key");
is($h2->by_id(1)->field('note'), 'contains returning token', "literal value was written intact");
};
subtest literal_returning_without_returning_clause => sub {
# On a dialect with no RETURNING-on-insert support the built statement has
# no RETURNING clause, so a literal write value containing the word
# "returning" must not be mistaken for one and split. Exercise qorm_upsert
# directly since the SQLite Handle path always requests RETURNING.
{
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
$dbh->do('CREATE TABLE lit (id INTEGER PRIMARY KEY, note TEXT)');
$dbh->disconnect;
}
my $con2 = DBIx::QuickORM->quick(credentials => {dsn => $dsn});
my $h2 = $con2->handle('lit');
my $sb = $h2->sql_builder;
my $sql = $sb->qorm_upsert(
source => $h2->source,
dialect => $con2->dialect,
insert => {id => 1, note => \"'x returning y'"},
);
like(
$sql->{statement},
qr/VALUES \(\?, 'x returning y'\) ON CONFLICT/,
"conflict clause follows the intact literal value instead of being spliced into it",
);
unlike($sql->{statement}, qr/returning\s+"/i, "no RETURNING clause emitted when none was requested");
my $sql2 = $sb->qorm_upsert(
source => $h2->source,
dialect => $con2->dialect,
insert => {id => 1, note => \"'x returning y'"},
returning => ['id'],
);
like(
$sql2->{statement},
qr/ON CONFLICT.*RETURNING "id"\s*\z/s,
"a real RETURNING clause is still separated correctly and placed last",
);
};
done_testing;
( run in 0.472 second using v1.01-cache-2.11-cpan-f4a522933cf )