1
0
Fork 0
mirror of https://github.com/DBD-SQLite/DBD-SQLite synced 2025-06-07 14:19:10 -04:00

Merge pull request #35 from mohawk2/add-deferrability

Add deferrability parsing to foreign_key_info
This commit is contained in:
Kenichi Ishigaki 2018-08-28 02:36:09 +09:00 committed by GitHub
commit 8ee8ac791c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 8 deletions

View file

@ -545,6 +545,15 @@ my @FOREIGN_KEY_INFO_SQL_CLI = qw(
UNIQUE_OR_PRIMARY
);
my $DEFERRABLE_RE = qr/
(?:(?:
on \s+ (?:delete|update) \s+ (?:set \s+ null|set \s+ default|cascade|restrict|no \s+ action)
|
match \s* (?:\S+|".+?(?<!")")
) \s*)*
((?:not)? \s* deferrable (?: \s* initially \s* (?: immediate | deferred))?)?
/sxi;
sub foreign_key_info {
my ($dbh, $pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table) = @_;
@ -562,9 +571,11 @@ sub foreign_key_info {
($dbname eq 'temp') ? 'sqlite_temp_master' :
$quoted_dbname.'.sqlite_master';
my $tables = $dbh->selectall_arrayref("SELECT name FROM $master_table WHERE type = ?", undef, "table") or return;
my $tables = $dbh->selectall_arrayref("SELECT name, sql FROM $master_table WHERE type = ?", undef, "table") or return;
for my $table (@$tables) {
my $tbname = $table->[0];
my $ddl = $table->[1];
my (@rels, %relid2rels);
next if defined $fk_table && $fk_table ne '%' && $fk_table ne $tbname;
my $quoted_tbname = $dbh->quote_identifier($tbname);
@ -595,7 +606,17 @@ sub foreign_key_info {
next if defined $pk_schema && $pk_schema ne '%' && $pk_schema ne $table_info{$row->{table}}{schema};
push @fk_info, {
# cribbed from DBIx::Class::Schema::Loader::DBI::SQLite
my $rel = $rels[ $row->{id} ] ||= {
local_columns => [],
remote_columns => undef,
remote_table => $row->{table},
};
push @{ $rel->{local_columns} }, $row->{from};
push @{ $rel->{remote_columns} }, $row->{to}
if defined $row->{to};
my $fk_row = {
PKTABLE_CAT => undef,
PKTABLE_SCHEM => $table_info{$row->{table}}{schema},
PKTABLE_NAME => $row->{table},
@ -612,6 +633,44 @@ sub foreign_key_info {
DEFERRABILITY => undef,
UNIQUE_OR_PRIMARY => $table_info{$row->{table}}{columns}{$row->{to}} ? 'PRIMARY' : 'UNIQUE',
};
push @fk_info, $fk_row;
push @{ $relid2rels{$row->{id}} }, $fk_row; # keep so can fixup
}
# cribbed from DBIx::Class::Schema::Loader::DBI::SQLite
# but with additional parsing of which kind of deferrable
REL: for my $relid (keys %relid2rels) {
my $rel = $rels[$relid];
my $deferrable = $DBI_code_for_rule{'NOT DEFERRABLE'};
my $local_cols = '"?' . (join '"? \s* , \s* "?', map quotemeta, @{ $rel->{local_columns} }) . '"?';
my $remote_cols = '"?' . (join '"? \s* , \s* "?', map quotemeta, @{ $rel->{remote_columns} || [] }) . '"?';
my ($deferrable_clause) = $ddl =~ /
foreign \s+ key \s* \( \s* $local_cols \s* \) \s* references \s* (?:\S+|".+?(?<!")") \s*
(?:\( \s* $remote_cols \s* \) \s*)?
$DEFERRABLE_RE
/sxi;
if (!$deferrable_clause) {
# check for inline constraint if 1 local column
if (@{ $rel->{local_columns} } == 1) {
my ($local_col) = @{ $rel->{local_columns} };
my ($remote_col) = @{ $rel->{remote_columns} || [] };
$remote_col ||= '';
($deferrable_clause) = $ddl =~ /
"?\Q$local_col\E"? \s* (?:\w+\s*)* (?: \( \s* \d\+ (?:\s*,\s*\d+)* \s* \) )? \s*
references \s+ (?:\S+|".+?(?<!")") (?:\s* \( \s* "?\Q$remote_col\E"? \s* \))? \s*
$DEFERRABLE_RE
/sxi;
}
}
if ($deferrable_clause) {
# default is already NOT
if ($deferrable_clause !~ /not/i) {
$deferrable = $deferrable_clause =~ /deferred/i
? $DBI_code_for_rule{'INITIALLY DEFERRED'}
: $DBI_code_for_rule{'INITIALLY IMMEDIATE'};
}
}
$_->{DEFERRABILITY} = $deferrable for @{ $relid2rels{$relid} };
}
}
}
@ -1657,10 +1716,12 @@ B<DELETE_RULE>:
The referential action for the DELETE rule.
The codes are the same as for UPDATE_RULE.
Unfortunately, the B<DEFERRABILITY> field is always C<undef>;
as a matter of fact, deferrability clauses are supported by SQLite,
but they can't be reported because the C<PRAGMA foreign_key_list>
tells nothing about them.
B<DEFERRABILITY>:
The following codes are defined:
INITIALLY DEFERRED 5
INITIALLY IMMEDIATE 6
NOT DEFERRABLE 7
B<UNIQUE_OR_PRIMARY>:
Whether the column is primary or unique.

View file

@ -42,7 +42,7 @@ ATTACH DATABASE ':memory:' AS remote;
CREATE TABLE remote.album (
albumartist INTEGER NOT NULL REFERENCES artist(artistid)
ON DELETE RESTRICT
ON UPDATE CASCADE,
ON UPDATE CASCADE DEFERRABLE,
albumname TEXT,
albumcover BINARY,
albumeditor INTEGER NOT NULL REFERENCES editor(editorid),
@ -59,7 +59,7 @@ CREATE TABLE song(
__EOSQL__
plan tests => @sql_statements + 20;
plan tests => @sql_statements + 22;
my $dbh = connect_ok( RaiseError => 1, PrintError => 0, AutoCommit => 1 );
my $sth;
@ -78,6 +78,7 @@ for ($fk_data->{albumartist}) {
is($_->{KEY_SEQ}, 1, "FK albumartist, key seq");
is($_->{DELETE_RULE}, $R->{RESTRICT}, "FK albumartist, delete rule");
is($_->{UPDATE_RULE}, $R->{CASCADE}, "FK albumartist, update rule");
is($_->{DEFERRABILITY}, $R->{'INITIALLY IMMEDIATE'}, "FK albumartist, deferrability");
is($_->{UNIQUE_OR_PRIMARY}, 'UNIQUE', "FK albumartist, unique");
}
for ($fk_data->{albumeditor}) {
@ -87,6 +88,7 @@ for ($fk_data->{albumeditor}) {
# rules are 'NO ACTION' by default
is($_->{DELETE_RULE}, $R->{'NO ACTION'}, "FK albumeditor, delete rule");
is($_->{UPDATE_RULE}, $R->{'NO ACTION'}, "FK albumeditor, update rule");
is($_->{DEFERRABILITY}, $R->{'NOT DEFERRABLE'}, "FK albumeditor, deferrability");
is($_->{UNIQUE_OR_PRIMARY}, 'PRIMARY', "FK albumeditor, primary");
}