Skip to content

Add support for MySQL 8.0.1+ locking features (FOR SHARE, NOWAIT, SKIP LOCKED) #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: maint/0.0828xx
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions lib/DBIx/Class/SQLMaker/MySQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,68 @@ sub delete {
return ($sql, @bind);
}

# LOCK IN SHARE MODE
my $for_syntax = {
update => 'FOR UPDATE',
shared => 'LOCK IN SHARE MODE'
#
# Support for MySQL lock clause syntax according to specification
# including updates introduced in MySQL 8.0.1)
# FOR UPDATE | FOR SHARE [OF tbl_name [, tbl_name] ...] [NOWAIT | SKIP LOCKED]
#

my $lock_types = {
update => 'FOR UPDATE',
share => 'FOR SHARE',
shared => 'LOCK IN SHARE MODE' # Deprecated but maintained
};

my $lock_modifiers = {
nowait => 'NOWAIT',
skip_locked => 'SKIP LOCKED'
};

sub _lock_select {
my ($self, $type) = @_;
my ($self, $type) = @_;

my $sql = $for_syntax->{$type}
|| $self->throw_exception("Unknown SELECT .. FOR type '$type' requested");
if ($type eq 'shared') {
warnings::warnif(
'deprecated',
"'for => 'shared'' is deprecated. Please use 'for => 'share'' instead"
);
}

# Handle hash-based configuration to support new featureset
if (ref $type eq 'HASH') {
my $lock_type = $type->{type};
my $tables = $type->{of};
my $modifier = $type->{modifier};

my $lock_clause = $lock_types->{$lock_type}
|| $self->throw_exception("Unknown SELECT .. FOR type '$lock_type' requested");

# Add OF clause if tables are specified
if ($tables) {
my @table_list = ref $tables eq 'ARRAY' ? @$tables : ($tables);
if (@table_list) {
my $quoted_tables = join(', ',
map { $self->_quote($_) } @table_list
);
$lock_clause .= " OF $quoted_tables";
}
}

# Add modifier if specified
if ($modifier) {
my $mod_sql = $lock_modifiers->{$modifier}
|| $self->throw_exception("Unknown lock modifier '$modifier' requested");
$lock_clause .= " $mod_sql";
}

return " $lock_clause";
}

return " $sql";
# Handle simple string types (for backward compatibility)
my $sql = $lock_types->{$type}
|| $self->throw_exception("Unknown SELECT .. FOR type '$type' requested");

return " $sql";
}

1;
165 changes: 165 additions & 0 deletions t/sqlmaker/mysql.t
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,169 @@ for (
);
}

# Test support for locking clauses
{
# Test basic locking options
is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => 'update'})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR UPDATE
)',
[],
'FOR UPDATE lock works correctly'
);

is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => 'shared'})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
LOCK IN SHARE MODE
)',
[],
'LOCK IN SHARE MODE (deprecated shared) works correctly'
);

is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => 'share'})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR SHARE
)',
[],
'FOR SHARE (new share syntax) works correctly'
);

# Test hash-based locking configuration
is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'update'}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR UPDATE
)',
[],
'Hash-based FOR UPDATE works correctly'
);

is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'share'}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR SHARE
)',
[],
'Hash-based FOR SHARE works correctly'
);

# Test NOWAIT modifier
is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'update', modifier => 'nowait'}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR UPDATE NOWAIT
)',
[],
'FOR UPDATE NOWAIT works correctly'
);

is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'share', modifier => 'nowait'}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR SHARE NOWAIT
)',
[],
'FOR SHARE NOWAIT works correctly'
);

# Test SKIP LOCKED modifier
is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'update', modifier => 'skip_locked'}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR UPDATE SKIP LOCKED
)',
[],
'FOR UPDATE SKIP LOCKED works correctly'
);

is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'share', modifier => 'skip_locked'}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR SHARE SKIP LOCKED
)',
[],
'FOR SHARE SKIP LOCKED works correctly'
);

# Test OF clause
is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'update', of => 'artist'}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR UPDATE OF `artist`
)',
[],
'FOR UPDATE OF table works correctly'
);

# Test OF clause with multiple tables
is_same_sql_bind(
$schema->resultset('Artist')->search({}, {for => {type => 'share', of => ['artist', 'cd']}})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR SHARE OF `artist`, `cd`
)',
[],
'FOR SHARE OF multiple tables works correctly'
);

# Test combination of OF clause and modifier
is_same_sql_bind(
$schema->resultset('Artist')->search({}, {
for => {
type => 'update',
of => 'artist',
modifier => 'nowait'
}
})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR UPDATE OF `artist` NOWAIT
)',
[],
'FOR UPDATE OF table NOWAIT works correctly'
);

is_same_sql_bind(
$schema->resultset('Artist')->search({}, {
for => {
type => 'share',
of => ['artist', 'cd'],
modifier => 'skip_locked'
}
})->as_query,
'(
SELECT `me`.`artistid`, `me`.`name`, `me`.`rank`, `me`.`charfield`
FROM `artist` `me`
FOR SHARE OF `artist`, `cd` SKIP LOCKED
)',
[],
'FOR SHARE OF multiple tables SKIP LOCKED works correctly'
);
}

done_testing;