tokuhirom's Blog

How do you manage triggers in RDBMS.

RDBMSレベルのtriggerはアプリケーションレベルのトリガーにくらべてもれもなく、安心感があってよいものですね。
しかし一方で、RDBMS レベルのtriggerは、deploy がむずかしいという問題もあります。
わたくしは、以下のようなスクリプトをつかって、スキーマと実際のDBの様子をつきあわせて、ちがっていれば DROP して CREATE しなおすというようなスクリプトをかいています。
みなさんはどのように管理していますか?

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use 5.10.0;
use autodie;
use Log::Minimal;
use File::Spec;
use Data::Dumper; 

...
my $dbh = $dbh;
my %db_triggers = map { $_->{Trigger} => $_ } @{$dbh->selectall_arrayref(q{show triggers}, +{Slice => {}})};
my %triggers = parse_trigger_sql(File::Spec->catfile($c->base_dir, q{sql/trigger.sql}));
while (my ($name, $attr) = each %triggers) {
    if (need_install($db_triggers{$name}->{Statement}, $attr->{sql})) {
        say qq{-- $attr->{name}\@$attr->{table} --};
        say qq{LOCK TABLES $attr->{table} WRITE;};
        say qq{DROP TRIGGER IF EXISTS $attr->{name};};
        say qq{DELIMITER |};
        say qq{$attr->{header}};
        say qq{$attr->{sql}};
        say qq{|};
        say qq{DELIMITER ;};
        say qq{UNLOCK TABLES;};
        say qq{};
    }
}
exit;

# print trigger source
my $trigger_file = File::Spec->catfile($c->base_dir, 'sql/trigger.sql');
open my $fh, '<', $trigger_file;
print $_ while <$fh>;
close $fh;

sub need_install {
    my ($dbt, $srct) = @_;

    return 1 unless $dbt;

    $dbt =~ s/"\n"/"\\n"/g;
    $dbt =~ s/"\r"/"\\r"/g;
    $dbt =~ s/\n/ /g;
    $dbt =~ s/\s{2,}/ /g;
    $dbt =~ s/\s+$//g;

    $srct =~ s/\n/ /g;
    $srct =~ s/\s+$//g;
    if ($dbt ne $srct) {
        debugf("db : %s", ddf($dbt));
        debugf("src: %s", ddf($srct));
        return 1;
    } else {
        return 0;
    }
}

sub parse_trigger_sql {
    my ($src) = shift;
    open my $fh, '<', $src or die "Cannot open file: $src";
    my $delimiter = 'eMpTy'x10;
    my $sql;
    my %triggers;
    my $name;
    while (<$fh>) {
        s/--.+//;
        s/^\s+//;
        s/\s{2,}/ /g;
        next unless /\S/;
        given ($_) {
        when (/^DELIMITER\s+(.)$/) {
            $delimiter = quotemeta $1;
        }
        when (/^$delimiter/) {
            $triggers{$name}->{sql} = $sql;
            undef $sql;
        }
        when (/(?<header>CREATE TRIGGER (?<name>\S+) (?<before_after>\S+) (?<command>\S+) ON (?<table>\S+) FOR EACH ROW) BEGIN/) {
            $name = $+{name};
            $triggers{$name} = {%+};
            $sql = "BEGIN\n";
        }
        default {
            $sql .= $_;
        }
        }
    }
    close $fh;
    return %triggers;
}