summaryrefslogtreecommitdiffstats
path: root/Bugzilla/DB
diff options
context:
space:
mode:
authorPerl Tidy <perltidy@bugzilla.org>2018-12-05 21:38:52 +0100
committerDylan William Hardison <dylan@hardison.net>2018-12-05 23:49:08 +0100
commit8ec8da0491ad89604700b3e29a227966f6d84ba1 (patch)
tree9d270f173330ca19700e0ba9f2ee931300646de1 /Bugzilla/DB
parenta7bb5a65b71644d9efce5fed783ed545b9336548 (diff)
downloadbugzilla-8ec8da0491ad89604700b3e29a227966f6d84ba1.tar.gz
bugzilla-8ec8da0491ad89604700b3e29a227966f6d84ba1.tar.xz
no bug - reformat all the code using the new perltidy rules
Diffstat (limited to 'Bugzilla/DB')
-rw-r--r--Bugzilla/DB/Mysql.pm1515
-rw-r--r--Bugzilla/DB/Oracle.pm992
-rw-r--r--Bugzilla/DB/Pg.pm436
-rw-r--r--Bugzilla/DB/Schema.pm4208
-rw-r--r--Bugzilla/DB/Schema/Mysql.pm603
-rw-r--r--Bugzilla/DB/Schema/Oracle.pm772
-rw-r--r--Bugzilla/DB/Schema/Pg.pm286
-rw-r--r--Bugzilla/DB/Schema/Sqlite.pm414
-rw-r--r--Bugzilla/DB/Sqlite.pm305
9 files changed, 4843 insertions, 4688 deletions
diff --git a/Bugzilla/DB/Mysql.pm b/Bugzilla/DB/Mysql.pm
index 4dd2620d3..640cf89ec 100644
--- a/Bugzilla/DB/Mysql.pm
+++ b/Bugzilla/DB/Mysql.pm
@@ -44,224 +44,231 @@ use constant MAX_COMMENTS => 50;
use constant FULLTEXT_OR => '|';
sub BUILDARGS {
- my ($class, $params) = @_;
- my ($user, $pass, $host, $dbname, $port, $sock) =
- @$params{qw(db_user db_pass db_host db_name db_port db_sock)};
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port, $sock)
+ = @$params{qw(db_user db_pass db_host db_name db_port db_sock)};
- # construct the DSN from the parameters we got
- my $dsn = "dbi:mysql:host=$host;database=$dbname";
- $dsn .= ";port=$port" if $port;
- $dsn .= ";mysql_socket=$sock" if $sock;
+ # construct the DSN from the parameters we got
+ my $dsn = "dbi:mysql:host=$host;database=$dbname";
+ $dsn .= ";port=$port" if $port;
+ $dsn .= ";mysql_socket=$sock" if $sock;
- my %attrs = ( mysql_enable_utf8 => 1 );
+ my %attrs = (mysql_enable_utf8 => 1);
- return { dsn => $dsn, user => $user, pass => $pass, attrs => \%attrs };
+ return {dsn => $dsn, user => $user, pass => $pass, attrs => \%attrs};
}
sub on_dbi_connected {
- my ($class, $dbh) = @_;
-
- # This makes sure that if the tables are encoded as UTF-8, we
- # return their data correctly.
- my $charset = $class->utf8_charset;
- my $collate = $class->utf8_collate;
- $dbh->do("SET NAMES $charset COLLATE $collate");
-
- # Bug 321645 - disable MySQL strict mode, if set
- my ($var, $sql_mode) = $dbh->selectrow_array(
- "SHOW VARIABLES LIKE 'sql\\_mode'");
-
- if ($sql_mode) {
- # STRICT_TRANS_TABLE or STRICT_ALL_TABLES enable MySQL strict mode,
- # causing bug 321645. TRADITIONAL sets these modes (among others) as
- # well, so it has to be stipped as well
- my $new_sql_mode =
- join(",", grep {$_ !~ /^STRICT_(?:TRANS|ALL)_TABLES|TRADITIONAL$/}
- split(/,/, $sql_mode));
-
- if ($sql_mode ne $new_sql_mode) {
- $dbh->do("SET SESSION sql_mode = ?", undef, $new_sql_mode);
- }
+ my ($class, $dbh) = @_;
+
+ # This makes sure that if the tables are encoded as UTF-8, we
+ # return their data correctly.
+ my $charset = $class->utf8_charset;
+ my $collate = $class->utf8_collate;
+ $dbh->do("SET NAMES $charset COLLATE $collate");
+
+ # Bug 321645 - disable MySQL strict mode, if set
+ my ($var, $sql_mode)
+ = $dbh->selectrow_array("SHOW VARIABLES LIKE 'sql\\_mode'");
+
+ if ($sql_mode) {
+
+ # STRICT_TRANS_TABLE or STRICT_ALL_TABLES enable MySQL strict mode,
+ # causing bug 321645. TRADITIONAL sets these modes (among others) as
+ # well, so it has to be stipped as well
+ my $new_sql_mode = join(",",
+ grep { $_ !~ /^STRICT_(?:TRANS|ALL)_TABLES|TRADITIONAL$/ }
+ split(/,/, $sql_mode));
+
+ if ($sql_mode ne $new_sql_mode) {
+ $dbh->do("SET SESSION sql_mode = ?", undef, $new_sql_mode);
}
+ }
- # Allow large GROUP_CONCATs (largely for inserting comments
- # into bugs_fulltext).
- $dbh->do('SET SESSION group_concat_max_len = 128000000');
+ # Allow large GROUP_CONCATs (largely for inserting comments
+ # into bugs_fulltext).
+ $dbh->do('SET SESSION group_concat_max_len = 128000000');
}
# when last_insert_id() is supported on MySQL by lowest DBI/DBD version
# required by Bugzilla, this implementation can be removed.
sub bz_last_key {
- my ($self) = @_;
+ my ($self) = @_;
- my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
+ my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
- return $last_insert_id;
+ return $last_insert_id;
}
sub sql_group_concat {
- my ($self, $column, $separator, $sort) = @_;
- $separator = $self->quote(', ') if !defined $separator;
- $sort = 1 if !defined $sort;
- if ($sort) {
- my $sort_order = $column;
- $sort_order =~ s/^DISTINCT\s+//i;
- $column = "$column ORDER BY $sort_order";
- }
- return "GROUP_CONCAT($column SEPARATOR $separator)";
+ my ($self, $column, $separator, $sort) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ $sort = 1 if !defined $sort;
+ if ($sort) {
+ my $sort_order = $column;
+ $sort_order =~ s/^DISTINCT\s+//i;
+ $column = "$column ORDER BY $sort_order";
+ }
+ return "GROUP_CONCAT($column SEPARATOR $separator)";
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "$expr REGEXP $pattern";
+ return "$expr REGEXP $pattern";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "$expr NOT REGEXP $pattern";
+ return "$expr NOT REGEXP $pattern";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
-
- if (defined($offset)) {
- return "LIMIT $offset, $limit";
- } else {
- return "LIMIT $limit";
- }
+ my ($self, $limit, $offset) = @_;
+
+ if (defined($offset)) {
+ return "LIMIT $offset, $limit";
+ }
+ else {
+ return "LIMIT $limit";
+ }
}
sub sql_string_concat {
- my ($self, @params) = @_;
+ my ($self, @params) = @_;
- return 'CONCAT(' . join(', ', @params) . ')';
+ return 'CONCAT(' . join(', ', @params) . ')';
}
sub sql_fulltext_search {
- my ($self, $column, $text) = @_;
-
- # Add the boolean mode modifier if the search string contains
- # boolean operators at the start or end of a word.
- my $mode = '';
- if ($text =~ /(?:^|\W)[+\-<>~"()]/ || $text =~ /[()"*](?:$|\W)/) {
- $mode = 'IN BOOLEAN MODE';
-
- my @terms = split(quotemeta(FULLTEXT_OR), $text);
- foreach my $term (@terms) {
- # quote un-quoted compound words
- my @words = grep { defined } quotewords('[\s()]+', 'delimiters', $term);
- foreach my $word (@words) {
- # match words that have non-word chars in the middle of them
- if ($word =~ /\w\W+\w/ && $word !~ m/"/) {
- $word = '"' . $word . '"';
- # match words that contain only boolean operators
- } elsif ($word =~ /^[\+\-\<\>\~\*]+$/) {
- $word = '"' . $word . '"';
- }
- }
- $term = join('', @words);
+ my ($self, $column, $text) = @_;
+
+ # Add the boolean mode modifier if the search string contains
+ # boolean operators at the start or end of a word.
+ my $mode = '';
+ if ($text =~ /(?:^|\W)[+\-<>~"()]/ || $text =~ /[()"*](?:$|\W)/) {
+ $mode = 'IN BOOLEAN MODE';
+
+ my @terms = split(quotemeta(FULLTEXT_OR), $text);
+ foreach my $term (@terms) {
+
+ # quote un-quoted compound words
+ my @words = grep {defined} quotewords('[\s()]+', 'delimiters', $term);
+ foreach my $word (@words) {
+
+ # match words that have non-word chars in the middle of them
+ if ($word =~ /\w\W+\w/ && $word !~ m/"/) {
+ $word = '"' . $word . '"';
+
+ # match words that contain only boolean operators
}
- $text = join(FULLTEXT_OR, @terms);
+ elsif ($word =~ /^[\+\-\<\>\~\*]+$/) {
+ $word = '"' . $word . '"';
+ }
+ }
+ $term = join('', @words);
}
+ $text = join(FULLTEXT_OR, @terms);
+ }
- # quote the text for use in the MATCH AGAINST expression
- $text = $self->quote($text);
+ # quote the text for use in the MATCH AGAINST expression
+ $text = $self->quote($text);
- # untaint the text, since it's safe to use now that we've quoted it
- trick_taint($text);
+ # untaint the text, since it's safe to use now that we've quoted it
+ trick_taint($text);
- return "MATCH($column) AGAINST($text $mode)";
+ return "MATCH($column) AGAINST($text $mode)";
}
sub sql_istring {
- my ($self, $string) = @_;
+ my ($self, $string) = @_;
- return $string;
+ return $string;
}
sub sql_from_days {
- my ($self, $days) = @_;
+ my ($self, $days) = @_;
- return "FROM_DAYS($days)";
+ return "FROM_DAYS($days)";
}
sub sql_to_days {
- my ($self, $date) = @_;
+ my ($self, $date) = @_;
- return "TO_DAYS($date)";
+ return "TO_DAYS($date)";
}
sub sql_date_format {
- my ($self, $date, $format) = @_;
+ my ($self, $date, $format) = @_;
- $format = "%Y.%m.%d %H:%i:%s" if !$format;
+ $format = "%Y.%m.%d %H:%i:%s" if !$format;
- return "DATE_FORMAT($date, " . $self->quote($format) . ")";
+ return "DATE_FORMAT($date, " . $self->quote($format) . ")";
}
sub sql_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
+ my ($self, $date, $operator, $interval, $units) = @_;
- return "$date $operator INTERVAL $interval $units";
+ return "$date $operator INTERVAL $interval $units";
}
sub sql_iposition {
- my ($self, $fragment, $text) = @_;
- return "INSTR($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "INSTR($text, $fragment)";
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
+ my ($self, $fragment, $text) = @_;
- return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
+ return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
}
sub sql_group_by {
- my ($self, $needed_columns, $optional_columns) = @_;
+ my ($self, $needed_columns, $optional_columns) = @_;
- # MySQL allows you to specify the minimal subset of columns to get
- # a unique result. While it does allow specifying all columns as
- # ANSI SQL requires, according to MySQL documentation, the fewer
- # columns you specify, the faster the query runs.
- return "GROUP BY $needed_columns";
+ # MySQL allows you to specify the minimal subset of columns to get
+ # a unique result. While it does allow specifying all columns as
+ # ANSI SQL requires, according to MySQL documentation, the fewer
+ # columns you specify, the faster the query runs.
+ return "GROUP BY $needed_columns";
}
sub bz_explain {
- my ($self, $sql) = @_;
- my $sth = $self->prepare("EXPLAIN $sql");
- $sth->execute();
- my $columns = $sth->{'NAME'};
- my $lengths = $sth->{'mysql_max_length'};
- my $format_string = '|';
- my $i = 0;
- foreach my $column (@$columns) {
- # Sometimes the column name is longer than the contents.
- my $length = max($lengths->[$i], length($column));
- $format_string .= ' %-' . $length . 's |';
- $i++;
- }
-
- my $first_row = sprintf($format_string, @$columns);
- my @explain_rows = ($first_row, '-' x length($first_row));
- while (my $row = $sth->fetchrow_arrayref) {
- my @fixed = map { defined $_ ? $_ : 'NULL' } @$row;
- push(@explain_rows, sprintf($format_string, @fixed));
- }
-
- return join("\n", @explain_rows);
+ my ($self, $sql) = @_;
+ my $sth = $self->prepare("EXPLAIN $sql");
+ $sth->execute();
+ my $columns = $sth->{'NAME'};
+ my $lengths = $sth->{'mysql_max_length'};
+ my $format_string = '|';
+ my $i = 0;
+ foreach my $column (@$columns) {
+
+ # Sometimes the column name is longer than the contents.
+ my $length = max($lengths->[$i], length($column));
+ $format_string .= ' %-' . $length . 's |';
+ $i++;
+ }
+
+ my $first_row = sprintf($format_string, @$columns);
+ my @explain_rows = ($first_row, '-' x length($first_row));
+ while (my $row = $sth->fetchrow_arrayref) {
+ my @fixed = map { defined $_ ? $_ : 'NULL' } @$row;
+ push(@explain_rows, sprintf($format_string, @fixed));
+ }
+
+ return join("\n", @explain_rows);
}
sub _bz_get_initial_schema {
- my ($self) = @_;
- return $self->_bz_build_schema_from_disk();
+ my ($self) = @_;
+ return $self->_bz_build_schema_from_disk();
}
#####################################################################
@@ -269,435 +276,451 @@ sub _bz_get_initial_schema {
#####################################################################
sub bz_check_server_version {
- my $self = shift;
+ my $self = shift;
- my $lc = Bugzilla->localconfig;
- if (lc(Bugzilla->localconfig->{db_name}) eq 'mysql') {
- die "It is not safe to run Bugzilla inside a database named 'mysql'.\n"
- . " Please pick a different value for \$db_name in localconfig.\n";
- }
+ my $lc = Bugzilla->localconfig;
+ if (lc(Bugzilla->localconfig->{db_name}) eq 'mysql') {
+ die "It is not safe to run Bugzilla inside a database named 'mysql'.\n"
+ . " Please pick a different value for \$db_name in localconfig.\n";
+ }
- $self->SUPER::bz_check_server_version(@_);
+ $self->SUPER::bz_check_server_version(@_);
}
sub bz_setup_database {
- my ($self) = @_;
-
- # The "comments" field of the bugs_fulltext table could easily exceed
- # MySQL's default max_allowed_packet. Also, MySQL should never have
- # a max_allowed_packet smaller than our max_attachment_size. So, we
- # warn the user here if max_allowed_packet is too small.
- my $min_max_allowed = MAX_COMMENTS * MAX_COMMENT_LENGTH;
- my (undef, $current_max_allowed) = $self->selectrow_array(
- q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
- # This parameter is not yet defined when the DB is being built for
- # the very first time. The code below still works properly, however,
- # because the default maxattachmentsize is smaller than $min_max_allowed.
- my $max_attachment = (Bugzilla->params->{'maxattachmentsize'} || 0) * 1024;
- my $needed_max_allowed = max($min_max_allowed, $max_attachment);
- if ($current_max_allowed < $needed_max_allowed) {
- warn install_string('max_allowed_packet',
- { current => $current_max_allowed,
- needed => $needed_max_allowed }) . "\n";
+ my ($self) = @_;
+
+ # The "comments" field of the bugs_fulltext table could easily exceed
+ # MySQL's default max_allowed_packet. Also, MySQL should never have
+ # a max_allowed_packet smaller than our max_attachment_size. So, we
+ # warn the user here if max_allowed_packet is too small.
+ my $min_max_allowed = MAX_COMMENTS * MAX_COMMENT_LENGTH;
+ my (undef, $current_max_allowed)
+ = $self->selectrow_array(q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
+
+ # This parameter is not yet defined when the DB is being built for
+ # the very first time. The code below still works properly, however,
+ # because the default maxattachmentsize is smaller than $min_max_allowed.
+ my $max_attachment = (Bugzilla->params->{'maxattachmentsize'} || 0) * 1024;
+ my $needed_max_allowed = max($min_max_allowed, $max_attachment);
+ if ($current_max_allowed < $needed_max_allowed) {
+ warn install_string('max_allowed_packet',
+ {current => $current_max_allowed, needed => $needed_max_allowed})
+ . "\n";
+ }
+
+ # Make sure the installation has InnoDB turned on, or we're going to be
+ # doing silly things like making foreign keys on MyISAM tables, which is
+ # hard to fix later. We do this up here because none of the code below
+ # works if InnoDB is off. (Particularly if we've already converted the
+ # tables to InnoDB.)
+ my %engines = @{$self->selectcol_arrayref('SHOW ENGINES', {Columns => [1, 2]})};
+ if (!$engines{InnoDB} || $engines{InnoDB} !~ /^(YES|DEFAULT)$/) {
+ die install_string('mysql_innodb_disabled');
+ }
+
+ if ($self->utf8_charset eq 'utf8mb4') {
+ my %global = map {@$_}
+ @{$self->selectall_arrayref(q(SHOW GLOBAL VARIABLES LIKE 'innodb_%'))};
+ my $utf8mb4_supported
+ = $global{innodb_file_format} eq 'Barracuda'
+ && $global{innodb_file_per_table} eq 'ON'
+ && $global{innodb_large_prefix} eq 'ON';
+
+ die install_string('mysql_innodb_settings') unless $utf8mb4_supported;
+
+ my $tables = $self->selectall_arrayref('SHOW TABLE STATUS');
+ foreach my $table (@$tables) {
+ my ($table, undef, undef, $row_format) = @$table;
+ my $new_row_format = $self->default_row_format($table);
+ next if $new_row_format =~ /compact/i;
+ if (lc($new_row_format) ne lc($row_format)) {
+ print install_string(
+ 'mysql_row_format_conversion', {table => $table, format => $new_row_format}
+ ),
+ "\n";
+ $self->do(sprintf 'ALTER TABLE %s ROW_FORMAT=%s', $table, $new_row_format);
+ }
}
+ }
- # Make sure the installation has InnoDB turned on, or we're going to be
- # doing silly things like making foreign keys on MyISAM tables, which is
- # hard to fix later. We do this up here because none of the code below
- # works if InnoDB is off. (Particularly if we've already converted the
- # tables to InnoDB.)
- my %engines = @{$self->selectcol_arrayref('SHOW ENGINES', {Columns => [1,2]})};
- if (!$engines{InnoDB} || $engines{InnoDB} !~ /^(YES|DEFAULT)$/) {
- die install_string('mysql_innodb_disabled');
- }
+ my ($sd_index_deleted, $longdescs_index_deleted);
+ my @tables = $self->bz_table_list_real();
- if ($self->utf8_charset eq 'utf8mb4') {
- my %global = map { @$_ } @{ $self->selectall_arrayref(q(SHOW GLOBAL VARIABLES LIKE 'innodb_%')) };
- my $utf8mb4_supported
- = $global{innodb_file_format} eq 'Barracuda'
- && $global{innodb_file_per_table} eq 'ON'
- && $global{innodb_large_prefix} eq 'ON';
-
- die install_string('mysql_innodb_settings') unless $utf8mb4_supported;
-
- my $tables = $self->selectall_arrayref('SHOW TABLE STATUS');
- foreach my $table (@$tables) {
- my ($table, undef, undef, $row_format) = @$table;
- my $new_row_format = $self->default_row_format($table);
- next if $new_row_format =~ /compact/i;
- if (lc($new_row_format) ne lc($row_format)) {
- print install_string('mysql_row_format_conversion', { table => $table, format => $new_row_format }), "\n";
- $self->do(sprintf 'ALTER TABLE %s ROW_FORMAT=%s', $table, $new_row_format);
- }
- }
+ # We want to convert tables to InnoDB, but it's possible that they have
+ # fulltext indexes on them, and conversion will fail unless we remove
+ # the indexes.
+ if (grep($_ eq 'bugs', @tables) and !grep($_ eq 'bugs_fulltext', @tables)) {
+ if ($self->bz_index_info_real('bugs', 'short_desc')) {
+ $self->bz_drop_index_raw('bugs', 'short_desc');
}
-
- my ($sd_index_deleted, $longdescs_index_deleted);
- my @tables = $self->bz_table_list_real();
- # We want to convert tables to InnoDB, but it's possible that they have
- # fulltext indexes on them, and conversion will fail unless we remove
- # the indexes.
- if (grep($_ eq 'bugs', @tables)
- and !grep($_ eq 'bugs_fulltext', @tables))
- {
- if ($self->bz_index_info_real('bugs', 'short_desc')) {
- $self->bz_drop_index_raw('bugs', 'short_desc');
- }
- if ($self->bz_index_info_real('bugs', 'bugs_short_desc_idx')) {
- $self->bz_drop_index_raw('bugs', 'bugs_short_desc_idx');
- $sd_index_deleted = 1; # Used for later schema cleanup.
- }
+ if ($self->bz_index_info_real('bugs', 'bugs_short_desc_idx')) {
+ $self->bz_drop_index_raw('bugs', 'bugs_short_desc_idx');
+ $sd_index_deleted = 1; # Used for later schema cleanup.
}
- if (grep($_ eq 'longdescs', @tables)
- and !grep($_ eq 'bugs_fulltext', @tables))
- {
- if ($self->bz_index_info_real('longdescs', 'thetext')) {
- $self->bz_drop_index_raw('longdescs', 'thetext');
- }
- if ($self->bz_index_info_real('longdescs', 'longdescs_thetext_idx')) {
- $self->bz_drop_index_raw('longdescs', 'longdescs_thetext_idx');
- $longdescs_index_deleted = 1; # For later schema cleanup.
- }
+ }
+ if (grep($_ eq 'longdescs', @tables) and !grep($_ eq 'bugs_fulltext', @tables))
+ {
+ if ($self->bz_index_info_real('longdescs', 'thetext')) {
+ $self->bz_drop_index_raw('longdescs', 'thetext');
}
-
- # Upgrade tables from MyISAM to InnoDB
- my $db_name = Bugzilla->localconfig->{db_name};
- my $myisam_tables = $self->selectcol_arrayref(
- 'SELECT TABLE_NAME FROM information_schema.TABLES
- WHERE TABLE_SCHEMA = ? AND ENGINE = ?',
- undef, $db_name, 'MyISAM');
-
- if (scalar @$myisam_tables) {
- print "Bugzilla now uses the InnoDB storage engine in MySQL for",
- " most tables.\nConverting tables to InnoDB:\n";
- foreach my $table (@$myisam_tables) {
- print "Converting table $table... ";
- $self->do("ALTER TABLE $table ENGINE = InnoDB");
- print "done.\n";
- }
+ if ($self->bz_index_info_real('longdescs', 'longdescs_thetext_idx')) {
+ $self->bz_drop_index_raw('longdescs', 'longdescs_thetext_idx');
+ $longdescs_index_deleted = 1; # For later schema cleanup.
+ }
+ }
+
+ # Upgrade tables from MyISAM to InnoDB
+ my $db_name = Bugzilla->localconfig->{db_name};
+ my $myisam_tables = $self->selectcol_arrayref(
+ 'SELECT TABLE_NAME FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = ? AND ENGINE = ?', undef, $db_name, 'MyISAM'
+ );
+
+ if (scalar @$myisam_tables) {
+ print "Bugzilla now uses the InnoDB storage engine in MySQL for",
+ " most tables.\nConverting tables to InnoDB:\n";
+ foreach my $table (@$myisam_tables) {
+ print "Converting table $table... ";
+ $self->do("ALTER TABLE $table ENGINE = InnoDB");
+ print "done.\n";
+ }
+ }
+
+ # Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
+ # not provide explicit names for the table indexes. This means
+ # that our upgrades will not be reliable, because we look for the name
+ # of the index, not what fields it is on, when doing upgrades.
+ # (using the name is much better for cross-database compatibility
+ # and general reliability). It's also very important that our
+ # Schema object be consistent with what is on the disk.
+ #
+ # While we're at it, we also fix some inconsistent index naming
+ # from the original checkin of Bugzilla::DB::Schema.
+
+ # We check for the existence of a particular "short name" index that
+ # has existed at least since Bugzilla 2.8, and probably earlier.
+ # For fixing the inconsistent naming of Schema indexes,
+ # we also check for one of those inconsistently-named indexes.
+ if (
+ grep($_ eq 'bugs', @tables)
+ && ( $self->bz_index_info_real('bugs', 'assigned_to')
+ || $self->bz_index_info_real('flags', 'flags_bidattid_idx'))
+ )
+ {
+
+ # This is a check unrelated to the indexes, to see if people are
+ # upgrading from 2.18 or below, but somehow have a bz_schema table
+ # already. This only happens if they have done a mysqldump into
+ # a database without doing a DROP DATABASE first.
+ # We just do the check here since this check is a reliable way
+ # of telling that we are upgrading from a version pre-2.20.
+ if (grep($_ eq 'bz_schema', $self->bz_table_list_real())) {
+ die install_string('bz_schema_exists_before_220');
}
- # Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
- # not provide explicit names for the table indexes. This means
- # that our upgrades will not be reliable, because we look for the name
- # of the index, not what fields it is on, when doing upgrades.
- # (using the name is much better for cross-database compatibility
- # and general reliability). It's also very important that our
- # Schema object be consistent with what is on the disk.
- #
- # While we're at it, we also fix some inconsistent index naming
- # from the original checkin of Bugzilla::DB::Schema.
-
- # We check for the existence of a particular "short name" index that
- # has existed at least since Bugzilla 2.8, and probably earlier.
- # For fixing the inconsistent naming of Schema indexes,
- # we also check for one of those inconsistently-named indexes.
- if (grep($_ eq 'bugs', @tables)
- && ($self->bz_index_info_real('bugs', 'assigned_to')
- || $self->bz_index_info_real('flags', 'flags_bidattid_idx')) )
- {
-
- # This is a check unrelated to the indexes, to see if people are
- # upgrading from 2.18 or below, but somehow have a bz_schema table
- # already. This only happens if they have done a mysqldump into
- # a database without doing a DROP DATABASE first.
- # We just do the check here since this check is a reliable way
- # of telling that we are upgrading from a version pre-2.20.
- if (grep($_ eq 'bz_schema', $self->bz_table_list_real())) {
- die install_string('bz_schema_exists_before_220');
- }
-
- my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
- # We estimate one minute for each 3000 bugs, plus 3 minutes just
- # to handle basic MySQL stuff.
- my $rename_time = int($bug_count / 3000) + 3;
- # And 45 minutes for every 15,000 attachments, per some experiments.
- my ($attachment_count) =
- $self->selectrow_array("SELECT COUNT(*) FROM attachments");
- $rename_time += int(($attachment_count * 45) / 15000);
- # If we're going to take longer than 5 minutes, we let the user know
- # and allow them to abort.
- if ($rename_time > 5) {
- print "\n", install_string('mysql_index_renaming',
- { minutes => $rename_time });
- # Wait 45 seconds for them to respond.
- sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
- }
- print "Renaming indexes...\n";
-
- # We can't be interrupted, because of how the "if"
- # works above.
- local $SIG{INT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- # Certain indexes had names in Schema that did not easily conform
- # to a standard. We store those names here, so that they
- # can be properly renamed.
- # Also, sometimes an old mysqldump would incorrectly rename
- # unique indexes to "PRIMARY", so we address that here, also.
- my $bad_names = {
- # 'when' is a possible leftover from Bugzillas before 2.8
- bugs_activity => ['when', 'bugs_activity_bugid_idx',
- 'bugs_activity_bugwhen_idx'],
- cc => ['PRIMARY'],
- longdescs => ['longdescs_bugid_idx',
- 'longdescs_bugwhen_idx'],
- flags => ['flags_bidattid_idx'],
- flaginclusions => ['flaginclusions_tpcid_idx'],
- flagexclusions => ['flagexclusions_tpc_id_idx'],
- keywords => ['PRIMARY'],
- milestones => ['PRIMARY'],
- profiles_activity => ['profiles_activity_when_idx'],
- group_control_map => ['group_control_map_gid_idx', 'PRIMARY'],
- user_group_map => ['PRIMARY'],
- group_group_map => ['PRIMARY'],
- email_setting => ['PRIMARY'],
- bug_group_map => ['PRIMARY'],
- category_group_map => ['PRIMARY'],
- watch => ['PRIMARY'],
- namedqueries => ['PRIMARY'],
- series_data => ['PRIMARY'],
- # series_categories is dealt with below, not here.
- };
-
- # The series table is broken and needs to have one index
- # dropped before we begin the renaming, because it had a
- # useless index on it that would cause a naming conflict here.
- if (grep($_ eq 'series', @tables)) {
- my $dropname;
- # This is what the bad index was called before Schema.
- if ($self->bz_index_info_real('series', 'creator_2')) {
- $dropname = 'creator_2';
- }
- # This is what the bad index is called in Schema.
- elsif ($self->bz_index_info_real('series', 'series_creator_idx')) {
- $dropname = 'series_creator_idx';
- }
- $self->bz_drop_index_raw('series', $dropname) if $dropname;
- }
+ my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
- # The email_setting table also had the same problem.
- if( grep($_ eq 'email_setting', @tables)
- && $self->bz_index_info_real('email_setting',
- 'email_settings_user_id_idx') )
- {
- $self->bz_drop_index_raw('email_setting',
- 'email_settings_user_id_idx');
- }
+ # We estimate one minute for each 3000 bugs, plus 3 minutes just
+ # to handle basic MySQL stuff.
+ my $rename_time = int($bug_count / 3000) + 3;
- # Go through all the tables.
- foreach my $table (@tables) {
- # Will contain the names of old indexes as keys, and the
- # definition of the new indexes as a value. The values
- # include an extra hash key, NAME, with the new name of
- # the index.
- my %rename_indexes;
- # And go through all the columns on each table.
- my @columns = $self->bz_table_columns_real($table);
-
- # We also want to fix the silly naming of unique indexes
- # that happened when we first checked-in Bugzilla::DB::Schema.
- if ($table eq 'series_categories') {
- # The series_categories index had a nonstandard name.
- push(@columns, 'series_cats_unique_idx');
- }
- elsif ($table eq 'email_setting') {
- # The email_setting table had a similar problem.
- push(@columns, 'email_settings_unique_idx');
- }
- else {
- push(@columns, "${table}_unique_idx");
- }
- # And this is how we fix the other inconsistent Schema naming.
- push(@columns, @{$bad_names->{$table}})
- if (exists $bad_names->{$table});
- foreach my $column (@columns) {
- # If we have an index named after this column, it's an
- # old-style-name index.
- if (my $index = $self->bz_index_info_real($table, $column)) {
- # Fix the name to fit in with the new naming scheme.
- $index->{NAME} = $table . "_" .
- $index->{FIELDS}->[0] . "_idx";
- print "Renaming index $column to "
- . $index->{NAME} . "...\n";
- $rename_indexes{$column} = $index;
- } # if
- } # foreach column
-
- my @rename_sql = $self->_bz_schema->get_rename_indexes_ddl(
- $table, %rename_indexes);
- $self->do($_) foreach (@rename_sql);
-
- } # foreach table
- } # if old-name indexes
-
- # If there are no tables, but the DB isn't utf8 and it should be,
- # then we should alter the database to be utf8. We know it should be
- # if the utf8 parameter is true or there are no params at all.
- # This kind of situation happens when people create the database
- # themselves, and if we don't do this they will get the big
- # scary WARNING statement about conversion to UTF8.
- unless ( $self->bz_db_is_utf8 ) {
- $self->_alter_db_charset_to_utf8();
- }
+ # And 45 minutes for every 15,000 attachments, per some experiments.
+ my ($attachment_count)
+ = $self->selectrow_array("SELECT COUNT(*) FROM attachments");
+ $rename_time += int(($attachment_count * 45) / 15000);
- # And now we create the tables and the Schema object.
- $self->SUPER::bz_setup_database();
+ # If we're going to take longer than 5 minutes, we let the user know
+ # and allow them to abort.
+ if ($rename_time > 5) {
+ print "\n", install_string('mysql_index_renaming', {minutes => $rename_time});
- if ($sd_index_deleted) {
- $self->_bz_real_schema->delete_index('bugs', 'bugs_short_desc_idx');
- $self->_bz_store_real_schema;
+ # Wait 45 seconds for them to respond.
+ sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
}
- if ($longdescs_index_deleted) {
- $self->_bz_real_schema->delete_index('longdescs',
- 'longdescs_thetext_idx');
- $self->_bz_store_real_schema;
+ print "Renaming indexes...\n";
+
+ # We can't be interrupted, because of how the "if"
+ # works above.
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ # Certain indexes had names in Schema that did not easily conform
+ # to a standard. We store those names here, so that they
+ # can be properly renamed.
+ # Also, sometimes an old mysqldump would incorrectly rename
+ # unique indexes to "PRIMARY", so we address that here, also.
+ my $bad_names = {
+
+ # 'when' is a possible leftover from Bugzillas before 2.8
+ bugs_activity =>
+ ['when', 'bugs_activity_bugid_idx', 'bugs_activity_bugwhen_idx'],
+ cc => ['PRIMARY'],
+ longdescs => ['longdescs_bugid_idx', 'longdescs_bugwhen_idx'],
+ flags => ['flags_bidattid_idx'],
+ flaginclusions => ['flaginclusions_tpcid_idx'],
+ flagexclusions => ['flagexclusions_tpc_id_idx'],
+ keywords => ['PRIMARY'],
+ milestones => ['PRIMARY'],
+ profiles_activity => ['profiles_activity_when_idx'],
+ group_control_map => ['group_control_map_gid_idx', 'PRIMARY'],
+ user_group_map => ['PRIMARY'],
+ group_group_map => ['PRIMARY'],
+ email_setting => ['PRIMARY'],
+ bug_group_map => ['PRIMARY'],
+ category_group_map => ['PRIMARY'],
+ watch => ['PRIMARY'],
+ namedqueries => ['PRIMARY'],
+ series_data => ['PRIMARY'],
+
+ # series_categories is dealt with below, not here.
+ };
+
+ # The series table is broken and needs to have one index
+ # dropped before we begin the renaming, because it had a
+ # useless index on it that would cause a naming conflict here.
+ if (grep($_ eq 'series', @tables)) {
+ my $dropname;
+
+ # This is what the bad index was called before Schema.
+ if ($self->bz_index_info_real('series', 'creator_2')) {
+ $dropname = 'creator_2';
+ }
+
+ # This is what the bad index is called in Schema.
+ elsif ($self->bz_index_info_real('series', 'series_creator_idx')) {
+ $dropname = 'series_creator_idx';
+ }
+ $self->bz_drop_index_raw('series', $dropname) if $dropname;
}
- # 2005-09-24 - bugreport@peshkin.net, bug 307602
- # Make sure that default 4G table limit is overridden
- my $attach_data_create = $self->selectrow_array(
- 'SELECT CREATE_OPTIONS FROM information_schema.TABLES
- WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?',
- undef, $db_name, 'attach_data');
- if ($attach_data_create !~ /MAX_ROWS/i) {
- print "Converting attach_data maximum size to 100G...\n";
- $self->do("ALTER TABLE attach_data
- AVG_ROW_LENGTH=1000000,
- MAX_ROWS=100000");
+ # The email_setting table also had the same problem.
+ if (grep($_ eq 'email_setting', @tables)
+ && $self->bz_index_info_real('email_setting', 'email_settings_user_id_idx'))
+ {
+ $self->bz_drop_index_raw('email_setting', 'email_settings_user_id_idx');
}
- # Convert the database to UTF-8 if the utf8 parameter is on.
- # We check if any table isn't utf8, because lots of crazy
- # partial-conversion situations can happen, and this handles anything
- # that could come up (including having the DB charset be utf8 but not
- # the table charsets.
- #
- # TABLE_COLLATION IS NOT NULL prevents us from trying to convert views.
- my $charset = $self->utf8_charset;
- my $collate = $self->utf8_collate;
- my $non_utf8_tables = $self->selectrow_array(
- "SELECT 1 FROM information_schema.TABLES
+ # Go through all the tables.
+ foreach my $table (@tables) {
+
+ # Will contain the names of old indexes as keys, and the
+ # definition of the new indexes as a value. The values
+ # include an extra hash key, NAME, with the new name of
+ # the index.
+ my %rename_indexes;
+
+ # And go through all the columns on each table.
+ my @columns = $self->bz_table_columns_real($table);
+
+ # We also want to fix the silly naming of unique indexes
+ # that happened when we first checked-in Bugzilla::DB::Schema.
+ if ($table eq 'series_categories') {
+
+ # The series_categories index had a nonstandard name.
+ push(@columns, 'series_cats_unique_idx');
+ }
+ elsif ($table eq 'email_setting') {
+
+ # The email_setting table had a similar problem.
+ push(@columns, 'email_settings_unique_idx');
+ }
+ else {
+ push(@columns, "${table}_unique_idx");
+ }
+
+ # And this is how we fix the other inconsistent Schema naming.
+ push(@columns, @{$bad_names->{$table}}) if (exists $bad_names->{$table});
+ foreach my $column (@columns) {
+
+ # If we have an index named after this column, it's an
+ # old-style-name index.
+ if (my $index = $self->bz_index_info_real($table, $column)) {
+
+ # Fix the name to fit in with the new naming scheme.
+ $index->{NAME} = $table . "_" . $index->{FIELDS}->[0] . "_idx";
+ print "Renaming index $column to " . $index->{NAME} . "...\n";
+ $rename_indexes{$column} = $index;
+ } # if
+ } # foreach column
+
+ my @rename_sql
+ = $self->_bz_schema->get_rename_indexes_ddl($table, %rename_indexes);
+ $self->do($_) foreach (@rename_sql);
+
+ } # foreach table
+ } # if old-name indexes
+
+ # If there are no tables, but the DB isn't utf8 and it should be,
+ # then we should alter the database to be utf8. We know it should be
+ # if the utf8 parameter is true or there are no params at all.
+ # This kind of situation happens when people create the database
+ # themselves, and if we don't do this they will get the big
+ # scary WARNING statement about conversion to UTF8.
+ unless ($self->bz_db_is_utf8) {
+ $self->_alter_db_charset_to_utf8();
+ }
+
+ # And now we create the tables and the Schema object.
+ $self->SUPER::bz_setup_database();
+
+ if ($sd_index_deleted) {
+ $self->_bz_real_schema->delete_index('bugs', 'bugs_short_desc_idx');
+ $self->_bz_store_real_schema;
+ }
+ if ($longdescs_index_deleted) {
+ $self->_bz_real_schema->delete_index('longdescs', 'longdescs_thetext_idx');
+ $self->_bz_store_real_schema;
+ }
+
+ # 2005-09-24 - bugreport@peshkin.net, bug 307602
+ # Make sure that default 4G table limit is overridden
+ my $attach_data_create = $self->selectrow_array(
+ 'SELECT CREATE_OPTIONS FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?', undef, $db_name, 'attach_data'
+ );
+ if ($attach_data_create !~ /MAX_ROWS/i) {
+ print "Converting attach_data maximum size to 100G...\n";
+ $self->do(
+ "ALTER TABLE attach_data
+ AVG_ROW_LENGTH=1000000,
+ MAX_ROWS=100000"
+ );
+ }
+
+ # Convert the database to UTF-8 if the utf8 parameter is on.
+ # We check if any table isn't utf8, because lots of crazy
+ # partial-conversion situations can happen, and this handles anything
+ # that could come up (including having the DB charset be utf8 but not
+ # the table charsets.
+ #
+ # TABLE_COLLATION IS NOT NULL prevents us from trying to convert views.
+ my $charset = $self->utf8_charset;
+ my $collate = $self->utf8_collate;
+ my $non_utf8_tables = $self->selectrow_array(
+ "SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ? AND TABLE_COLLATION IS NOT NULL
AND TABLE_COLLATION != ?
- LIMIT 1", undef, $db_name, $collate);
-
- if (Bugzilla->params->{'utf8'} && $non_utf8_tables) {
- print "\n", install_string('mysql_utf8_conversion');
-
- if (!Bugzilla->installation_answers->{NO_PAUSE}) {
- if (Bugzilla->installation_mode ==
- INSTALLATION_MODE_NON_INTERACTIVE)
- {
- die install_string('continue_without_answers'), "\n";
- }
- else {
- print "\n " . install_string('enter_or_ctrl_c');
- getc;
- }
- }
-
- print "Converting table storage format to $charset (collate $collate). This may take a while.\n";
- foreach my $table ($self->bz_table_list_real) {
- my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
- $info_sth->execute();
- my (@binary_sql, @utf8_sql);
- while (my $column = $info_sth->fetchrow_hashref) {
- # Our conversion code doesn't work on enum fields, but they
- # all go away later in checksetup anyway.
- next if $column->{Type} =~ /enum/i;
-
- # If this particular column isn't stored in utf-8
- if ($column->{Collation}
- && $column->{Collation} ne 'NULL'
- && $column->{Collation} ne $collate)
- {
- my $name = $column->{Field};
-
- print "$table.$name needs to be converted to $charset (collate $collate)...\n";
-
- # These will be automatically re-created at the end
- # of checksetup.
- $self->bz_drop_related_fks($table, $name);
-
- my $col_info =
- $self->bz_column_info_real($table, $name);
- # CHANGE COLUMN doesn't take PRIMARY KEY
- delete $col_info->{PRIMARYKEY};
- my $sql_def = $self->_bz_schema->get_type_ddl($col_info);
- # We don't want MySQL to actually try to *convert*
- # from our current charset to UTF-8, we just want to
- # transfer the bytes directly. This is how we do that.
-
- # The CHARACTER SET part of the definition has to come
- # right after the type, which will always come first.
- my ($binary, $utf8) = ($sql_def, $sql_def);
- my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
- $binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
- $utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET $charset COLLATE $collate/;
- push(@binary_sql, "MODIFY COLUMN $name $binary");
- push(@utf8_sql, "MODIFY COLUMN $name $utf8");
- }
- } # foreach column
-
- if (@binary_sql) {
- my %indexes = %{ $self->bz_table_indexes($table) };
- foreach my $index_name (keys %indexes) {
- my $index = $indexes{$index_name};
- if ($index->{TYPE} and $index->{TYPE} eq 'FULLTEXT') {
- $self->bz_drop_index($table, $index_name);
- }
- else {
- delete $indexes{$index_name};
- }
- }
-
- print "Converting the $table table to UTF-8...\n";
- my $bin = "ALTER TABLE $table " . join(', ', @binary_sql);
- my $utf = "ALTER TABLE $table " . join(', ', @utf8_sql,
- "DEFAULT CHARACTER SET $charset COLLATE $collate");
- $self->do($bin);
- $self->do($utf);
-
- # Re-add any removed FULLTEXT indexes.
- foreach my $index (keys %indexes) {
- $self->bz_add_index($table, $index, $indexes{$index});
- }
- }
- else {
- $self->do("ALTER TABLE $table DEFAULT CHARACTER SET $charset COLLATE $collate");
- }
-
- } # foreach my $table (@tables)
+ LIMIT 1", undef, $db_name, $collate
+ );
+
+ if (Bugzilla->params->{'utf8'} && $non_utf8_tables) {
+ print "\n", install_string('mysql_utf8_conversion');
+
+ if (!Bugzilla->installation_answers->{NO_PAUSE}) {
+ if (Bugzilla->installation_mode == INSTALLATION_MODE_NON_INTERACTIVE) {
+ die install_string('continue_without_answers'), "\n";
+ }
+ else {
+ print "\n " . install_string('enter_or_ctrl_c');
+ getc;
+ }
}
- # Sometimes you can have a situation where all the tables are utf8,
- # but the database isn't. (This tends to happen when you've done
- # a mysqldump.) So we have this change outside of the above block,
- # so that it just happens silently if no actual *table* conversion
- # needs to happen.
- unless ($self->bz_db_is_utf8) {
- $self->_alter_db_charset_to_utf8();
- }
+ print
+ "Converting table storage format to $charset (collate $collate). This may take a while.\n";
+ foreach my $table ($self->bz_table_list_real) {
+ my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
+ $info_sth->execute();
+ my (@binary_sql, @utf8_sql);
+ while (my $column = $info_sth->fetchrow_hashref) {
+
+ # Our conversion code doesn't work on enum fields, but they
+ # all go away later in checksetup anyway.
+ next if $column->{Type} =~ /enum/i;
+
+ # If this particular column isn't stored in utf-8
+ if ( $column->{Collation}
+ && $column->{Collation} ne 'NULL'
+ && $column->{Collation} ne $collate)
+ {
+ my $name = $column->{Field};
- $self->_fix_defaults();
+ print "$table.$name needs to be converted to $charset (collate $collate)...\n";
- # Bug 451735 highlighted a bug in bz_drop_index() which didn't
- # check for FKs before trying to delete an index. Consequently,
- # the series_creator_idx index was considered to be deleted
- # despite it was still present in the DB. That's why we have to
- # force the deletion, bypassing the DB schema.
- if (!$self->bz_index_info('series', 'series_category_idx')) {
- if (!$self->bz_index_info('series', 'series_creator_idx')
- && $self->bz_index_info_real('series', 'series_creator_idx'))
- {
- foreach my $column (qw(creator category subcategory name)) {
- $self->bz_drop_related_fks('series', $column);
- }
- $self->bz_drop_index_raw('series', 'series_creator_idx');
+ # These will be automatically re-created at the end
+ # of checksetup.
+ $self->bz_drop_related_fks($table, $name);
+
+ my $col_info = $self->bz_column_info_real($table, $name);
+
+ # CHANGE COLUMN doesn't take PRIMARY KEY
+ delete $col_info->{PRIMARYKEY};
+ my $sql_def = $self->_bz_schema->get_type_ddl($col_info);
+
+ # We don't want MySQL to actually try to *convert*
+ # from our current charset to UTF-8, we just want to
+ # transfer the bytes directly. This is how we do that.
+
+ # The CHARACTER SET part of the definition has to come
+ # right after the type, which will always come first.
+ my ($binary, $utf8) = ($sql_def, $sql_def);
+ my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
+ $binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
+ $utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET $charset COLLATE $collate/;
+ push(@binary_sql, "MODIFY COLUMN $name $binary");
+ push(@utf8_sql, "MODIFY COLUMN $name $utf8");
+ }
+ } # foreach column
+
+ if (@binary_sql) {
+ my %indexes = %{$self->bz_table_indexes($table)};
+ foreach my $index_name (keys %indexes) {
+ my $index = $indexes{$index_name};
+ if ($index->{TYPE} and $index->{TYPE} eq 'FULLTEXT') {
+ $self->bz_drop_index($table, $index_name);
+ }
+ else {
+ delete $indexes{$index_name};
+ }
+ }
+
+ print "Converting the $table table to UTF-8...\n";
+ my $bin = "ALTER TABLE $table " . join(', ', @binary_sql);
+ my $utf = "ALTER TABLE $table "
+ . join(', ', @utf8_sql, "DEFAULT CHARACTER SET $charset COLLATE $collate");
+ $self->do($bin);
+ $self->do($utf);
+
+ # Re-add any removed FULLTEXT indexes.
+ foreach my $index (keys %indexes) {
+ $self->bz_add_index($table, $index, $indexes{$index});
}
+ }
+ else {
+ $self->do("ALTER TABLE $table DEFAULT CHARACTER SET $charset COLLATE $collate");
+ }
+
+ } # foreach my $table (@tables)
+ }
+
+ # Sometimes you can have a situation where all the tables are utf8,
+ # but the database isn't. (This tends to happen when you've done
+ # a mysqldump.) So we have this change outside of the above block,
+ # so that it just happens silently if no actual *table* conversion
+ # needs to happen.
+ unless ($self->bz_db_is_utf8) {
+ $self->_alter_db_charset_to_utf8();
+ }
+
+ $self->_fix_defaults();
+
+ # Bug 451735 highlighted a bug in bz_drop_index() which didn't
+ # check for FKs before trying to delete an index. Consequently,
+ # the series_creator_idx index was considered to be deleted
+ # despite it was still present in the DB. That's why we have to
+ # force the deletion, bypassing the DB schema.
+ if (!$self->bz_index_info('series', 'series_category_idx')) {
+ if (!$self->bz_index_info('series', 'series_creator_idx')
+ && $self->bz_index_info_real('series', 'series_creator_idx'))
+ {
+ foreach my $column (qw(creator category subcategory name)) {
+ $self->bz_drop_related_fks('series', $column);
+ }
+ $self->bz_drop_index_raw('series', 'series_creator_idx');
}
+ }
}
# When you import a MySQL 3/4 mysqldump into MySQL 5, columns that
@@ -707,151 +730,160 @@ sub bz_setup_database {
# looks like. So we remove defaults from columns that aren't supposed
# to have them
sub _fix_defaults {
- my $self = shift;
- my $maj_version = substr($self->bz_server_version, 0, 1);
- return if $maj_version < 5;
-
- # The oldest column that could have this problem is bugs.assigned_to,
- # so if it doesn't have the problem, we just skip doing this entirely.
- my $assi_def = $self->_bz_raw_column_info('bugs', 'assigned_to');
- my $assi_default = $assi_def->{COLUMN_DEF};
- # This "ne ''" thing is necessary because _raw_column_info seems to
- # return COLUMN_DEF as an empty string for columns that don't have
- # a default.
- return unless (defined $assi_default && $assi_default ne '');
-
- my %fix_columns;
- foreach my $table ($self->_bz_real_schema->get_table_list()) {
- foreach my $column ($self->bz_table_columns($table)) {
- my $abs_def = $self->bz_column_info($table, $column);
- # BLOB/TEXT columns never have defaults
- next if $abs_def->{TYPE} =~ /BLOB|TEXT/i;
- if (!defined $abs_def->{DEFAULT}) {
- # Get the exact default from the database without any
- # "fixing" by bz_column_info_real.
- my $raw_info = $self->_bz_raw_column_info($table, $column);
- my $raw_default = $raw_info->{COLUMN_DEF};
- if (defined $raw_default) {
- if ($raw_default eq '') {
- # Only (var)char columns can have empty strings as
- # defaults, so if we got an empty string for some
- # other default type, then it's bogus.
- next unless $abs_def->{TYPE} =~ /char/i;
- $raw_default = "''";
- }
- $fix_columns{$table} ||= [];
- push(@{ $fix_columns{$table} }, $column);
- print "$table.$column has incorrect DB default: $raw_default\n";
- }
- }
- } # foreach $column
- } # foreach $table
-
- print "Fixing defaults...\n";
- foreach my $table (reverse sort keys %fix_columns) {
- my @alters = map("ALTER COLUMN $_ DROP DEFAULT",
- @{ $fix_columns{$table} });
- my $sql = "ALTER TABLE $table " . join(',', @alters);
- $self->do($sql);
- }
+ my $self = shift;
+ my $maj_version = substr($self->bz_server_version, 0, 1);
+ return if $maj_version < 5;
+
+ # The oldest column that could have this problem is bugs.assigned_to,
+ # so if it doesn't have the problem, we just skip doing this entirely.
+ my $assi_def = $self->_bz_raw_column_info('bugs', 'assigned_to');
+ my $assi_default = $assi_def->{COLUMN_DEF};
+
+ # This "ne ''" thing is necessary because _raw_column_info seems to
+ # return COLUMN_DEF as an empty string for columns that don't have
+ # a default.
+ return unless (defined $assi_default && $assi_default ne '');
+
+ my %fix_columns;
+ foreach my $table ($self->_bz_real_schema->get_table_list()) {
+ foreach my $column ($self->bz_table_columns($table)) {
+ my $abs_def = $self->bz_column_info($table, $column);
+
+ # BLOB/TEXT columns never have defaults
+ next if $abs_def->{TYPE} =~ /BLOB|TEXT/i;
+ if (!defined $abs_def->{DEFAULT}) {
+
+ # Get the exact default from the database without any
+ # "fixing" by bz_column_info_real.
+ my $raw_info = $self->_bz_raw_column_info($table, $column);
+ my $raw_default = $raw_info->{COLUMN_DEF};
+ if (defined $raw_default) {
+ if ($raw_default eq '') {
+
+ # Only (var)char columns can have empty strings as
+ # defaults, so if we got an empty string for some
+ # other default type, then it's bogus.
+ next unless $abs_def->{TYPE} =~ /char/i;
+ $raw_default = "''";
+ }
+ $fix_columns{$table} ||= [];
+ push(@{$fix_columns{$table}}, $column);
+ print "$table.$column has incorrect DB default: $raw_default\n";
+ }
+ }
+ } # foreach $column
+ } # foreach $table
+
+ print "Fixing defaults...\n";
+ foreach my $table (reverse sort keys %fix_columns) {
+ my @alters = map("ALTER COLUMN $_ DROP DEFAULT", @{$fix_columns{$table}});
+ my $sql = "ALTER TABLE $table " . join(',', @alters);
+ $self->do($sql);
+ }
}
sub utf8_charset {
- return 'utf8' unless Bugzilla->params->{'utf8'};
- return Bugzilla->params->{'utf8'} eq 'utf8mb4' ? 'utf8mb4' : 'utf8';
+ return 'utf8' unless Bugzilla->params->{'utf8'};
+ return Bugzilla->params->{'utf8'} eq 'utf8mb4' ? 'utf8mb4' : 'utf8';
}
sub utf8_collate {
- my $charset = utf8_charset();
- if ($charset eq 'utf8') {
- return 'utf8_general_ci';
- }
- elsif ($charset eq 'utf8mb4') {
- return 'utf8mb4_unicode_520_ci';
- }
- else {
- croak "invalid charset: $charset";
- }
+ my $charset = utf8_charset();
+ if ($charset eq 'utf8') {
+ return 'utf8_general_ci';
+ }
+ elsif ($charset eq 'utf8mb4') {
+ return 'utf8mb4_unicode_520_ci';
+ }
+ else {
+ croak "invalid charset: $charset";
+ }
}
sub default_row_format {
- my ($class, $table) = @_;
- my $charset = utf8_charset();
- if ($charset eq 'utf8') {
- return 'Compact';
- }
- elsif ($charset eq 'utf8mb4') {
- my @no_compress = qw(
- bug_user_last_visit
- cc
- email_rates
- logincookies
- token_data
- tokens
- ts_error
- ts_exitstatus
- ts_funcmap
- ts_job
- ts_note
- user_request_log
- votes
- );
- return 'Dynamic' if any { $table eq $_ } @no_compress;
- return 'Compressed';
- }
- else {
- croak "invalid charset: $charset";
- }
+ my ($class, $table) = @_;
+ my $charset = utf8_charset();
+ if ($charset eq 'utf8') {
+ return 'Compact';
+ }
+ elsif ($charset eq 'utf8mb4') {
+ my @no_compress = qw(
+ bug_user_last_visit
+ cc
+ email_rates
+ logincookies
+ token_data
+ tokens
+ ts_error
+ ts_exitstatus
+ ts_funcmap
+ ts_job
+ ts_note
+ user_request_log
+ votes
+ );
+ return 'Dynamic' if any { $table eq $_ } @no_compress;
+ return 'Compressed';
+ }
+ else {
+ croak "invalid charset: $charset";
+ }
}
sub _alter_db_charset_to_utf8 {
- my $self = shift;
- my $db_name = Bugzilla->localconfig->{db_name};
- my $charset = $self->utf8_charset;
- my $collate = $self->utf8_collate;
- $self->do("ALTER DATABASE $db_name CHARACTER SET $charset COLLATE $collate");
+ my $self = shift;
+ my $db_name = Bugzilla->localconfig->{db_name};
+ my $charset = $self->utf8_charset;
+ my $collate = $self->utf8_collate;
+ $self->do("ALTER DATABASE $db_name CHARACTER SET $charset COLLATE $collate");
}
sub bz_db_is_utf8 {
- my $self = shift;
- my $db_charset = $self->selectrow_arrayref(
- "SHOW VARIABLES LIKE 'character_set_database'");
- # First column holds the variable name, second column holds the value.
- my $charset = $self->utf8_charset;
- return $db_charset->[1] eq $charset ? 1 : 0;
+ my $self = shift;
+ my $db_charset
+ = $self->selectrow_arrayref("SHOW VARIABLES LIKE 'character_set_database'");
+
+ # First column holds the variable name, second column holds the value.
+ my $charset = $self->utf8_charset;
+ return $db_charset->[1] eq $charset ? 1 : 0;
}
sub bz_enum_initial_values {
- my ($self) = @_;
- my %enum_values = %{$self->ENUM_DEFAULTS};
- # Get a complete description of the 'bugs' table; with DBD::MySQL
- # there isn't a column-by-column way of doing this. Could use
- # $dbh->column_info, but it would go slower and we would have to
- # use the undocumented mysql_type_name accessor to get the type
- # of each row.
- my $sth = $self->prepare("DESCRIBE bugs");
- $sth->execute();
- # Look for the particular columns we are interested in.
- while (my ($thiscol, $thistype) = $sth->fetchrow_array()) {
- if (defined $enum_values{$thiscol}) {
- # this is a column of interest.
- my @value_list;
- if ($thistype and ($thistype =~ /^enum\(/)) {
- # it has an enum type; get the set of values.
- while ($thistype =~ /'([^']*)'(.*)/) {
- push(@value_list, $1);
- $thistype = $2;
- }
- }
- if (@value_list) {
- # record the enum values found.
- $enum_values{$thiscol} = \@value_list;
- }
+ my ($self) = @_;
+ my %enum_values = %{$self->ENUM_DEFAULTS};
+
+ # Get a complete description of the 'bugs' table; with DBD::MySQL
+ # there isn't a column-by-column way of doing this. Could use
+ # $dbh->column_info, but it would go slower and we would have to
+ # use the undocumented mysql_type_name accessor to get the type
+ # of each row.
+ my $sth = $self->prepare("DESCRIBE bugs");
+ $sth->execute();
+
+ # Look for the particular columns we are interested in.
+ while (my ($thiscol, $thistype) = $sth->fetchrow_array()) {
+ if (defined $enum_values{$thiscol}) {
+
+ # this is a column of interest.
+ my @value_list;
+ if ($thistype and ($thistype =~ /^enum\(/)) {
+
+ # it has an enum type; get the set of values.
+ while ($thistype =~ /'([^']*)'(.*)/) {
+ push(@value_list, $1);
+ $thistype = $2;
}
+ }
+ if (@value_list) {
+
+ # record the enum values found.
+ $enum_values{$thiscol} = \@value_list;
+ }
}
+ }
- return \%enum_values;
+ return \%enum_values;
}
#####################################################################
@@ -882,29 +914,29 @@ backwards-compatibility anyway, for versions of Bugzilla before 2.20.
=cut
sub bz_column_info_real {
- my ($self, $table, $column) = @_;
- my $col_data = $self->_bz_raw_column_info($table, $column);
- return $self->_bz_schema->column_info_to_column($col_data);
+ my ($self, $table, $column) = @_;
+ my $col_data = $self->_bz_raw_column_info($table, $column);
+ return $self->_bz_schema->column_info_to_column($col_data);
}
sub _bz_raw_column_info {
- my ($self, $table, $column) = @_;
-
- # DBD::mysql does not support selecting a specific column,
- # so we have to get all the columns on the table and find
- # the one we want.
- my $info_sth = $self->column_info(undef, undef, $table, '%');
-
- # Don't use fetchall_hashref as there's a Win32 DBI bug (292821)
- my $col_data;
- while ($col_data = $info_sth->fetchrow_hashref) {
- last if $col_data->{'COLUMN_NAME'} eq $column;
- }
-
- if (!defined $col_data) {
- return undef;
- }
- return $col_data;
+ my ($self, $table, $column) = @_;
+
+ # DBD::mysql does not support selecting a specific column,
+ # so we have to get all the columns on the table and find
+ # the one we want.
+ my $info_sth = $self->column_info(undef, undef, $table, '%');
+
+ # Don't use fetchall_hashref as there's a Win32 DBI bug (292821)
+ my $col_data;
+ while ($col_data = $info_sth->fetchrow_hashref) {
+ last if $col_data->{'COLUMN_NAME'} eq $column;
+ }
+
+ if (!defined $col_data) {
+ return undef;
+ }
+ return $col_data;
}
=item C<bz_index_info_real($table, $index)>
@@ -918,42 +950,43 @@ sub _bz_raw_column_info {
=cut
sub bz_index_info_real {
- my ($self, $table, $index) = @_;
-
- my $sth = $self->prepare("SHOW INDEX FROM $table");
- $sth->execute;
-
- my @fields;
- my $index_type;
- # $raw_def will be an arrayref containing the following information:
- # 0 = name of the table that the index is on
- # 1 = 0 if unique, 1 if not unique
- # 2 = name of the index
- # 3 = seq_in_index (The order of the current field in the index).
- # 4 = Name of ONE column that the index is on
- # 5 = 'Collation' of the index. Usually 'A'.
- # 6 = Cardinality. Either a number or undef.
- # 7 = sub_part. Usually undef. Sometimes 1.
- # 8 = "packed". Usually undef.
- # 9 = Null. Sometimes undef, sometimes 'YES'.
- # 10 = Index_type. The type of the index. Usually either 'BTREE' or 'FULLTEXT'
- # 11 = 'Comment.' Usually undef.
- while (my $raw_def = $sth->fetchrow_arrayref) {
- if ($raw_def->[2] eq $index) {
- push(@fields, $raw_def->[4]);
- # No index can be both UNIQUE and FULLTEXT, that's why
- # this is written this way.
- $index_type = $raw_def->[1] ? '' : 'UNIQUE';
- $index_type = $raw_def->[10] eq 'FULLTEXT'
- ? 'FULLTEXT' : $index_type;
- }
+ my ($self, $table, $index) = @_;
+
+ my $sth = $self->prepare("SHOW INDEX FROM $table");
+ $sth->execute;
+
+ my @fields;
+ my $index_type;
+
+ # $raw_def will be an arrayref containing the following information:
+ # 0 = name of the table that the index is on
+ # 1 = 0 if unique, 1 if not unique
+ # 2 = name of the index
+ # 3 = seq_in_index (The order of the current field in the index).
+ # 4 = Name of ONE column that the index is on
+ # 5 = 'Collation' of the index. Usually 'A'.
+ # 6 = Cardinality. Either a number or undef.
+ # 7 = sub_part. Usually undef. Sometimes 1.
+ # 8 = "packed". Usually undef.
+ # 9 = Null. Sometimes undef, sometimes 'YES'.
+ # 10 = Index_type. The type of the index. Usually either 'BTREE' or 'FULLTEXT'
+ # 11 = 'Comment.' Usually undef.
+ while (my $raw_def = $sth->fetchrow_arrayref) {
+ if ($raw_def->[2] eq $index) {
+ push(@fields, $raw_def->[4]);
+
+ # No index can be both UNIQUE and FULLTEXT, that's why
+ # this is written this way.
+ $index_type = $raw_def->[1] ? '' : 'UNIQUE';
+ $index_type = $raw_def->[10] eq 'FULLTEXT' ? 'FULLTEXT' : $index_type;
}
+ }
- my $retval;
- if (scalar(@fields)) {
- $retval = {FIELDS => \@fields, TYPE => $index_type};
- }
- return $retval;
+ my $retval;
+ if (scalar(@fields)) {
+ $retval = {FIELDS => \@fields, TYPE => $index_type};
+ }
+ return $retval;
}
=item C<bz_index_list_real($table)>
@@ -966,10 +999,11 @@ sub bz_index_info_real {
=cut
sub bz_index_list_real {
- my ($self, $table) = @_;
- my $sth = $self->prepare("SHOW INDEX FROM $table");
- # Column 3 of a SHOW INDEX statement contains the name of the index.
- return @{ $self->selectcol_arrayref($sth, {Columns => [3]}) };
+ my ($self, $table) = @_;
+ my $sth = $self->prepare("SHOW INDEX FROM $table");
+
+ # Column 3 of a SHOW INDEX statement contains the name of the index.
+ return @{$self->selectcol_arrayref($sth, {Columns => [3]})};
}
#####################################################################
@@ -993,34 +1027,33 @@ this code does.
# bz_column_info_real function would be very difficult to create
# properly for any other DB besides MySQL.
sub _bz_build_schema_from_disk {
- my ($self) = @_;
-
- my $schema = $self->_bz_schema->get_empty_schema();
-
- my @tables = $self->bz_table_list_real();
- if (@tables) {
- print "Building Schema object from database...\n";
+ my ($self) = @_;
+
+ my $schema = $self->_bz_schema->get_empty_schema();
+
+ my @tables = $self->bz_table_list_real();
+ if (@tables) {
+ print "Building Schema object from database...\n";
+ }
+ foreach my $table (@tables) {
+ $schema->add_table($table);
+ my @columns = $self->bz_table_columns_real($table);
+ foreach my $column (@columns) {
+ my $type_info = $self->bz_column_info_real($table, $column);
+ $schema->set_column($table, $column, $type_info);
}
- foreach my $table (@tables) {
- $schema->add_table($table);
- my @columns = $self->bz_table_columns_real($table);
- foreach my $column (@columns) {
- my $type_info = $self->bz_column_info_real($table, $column);
- $schema->set_column($table, $column, $type_info);
- }
- my @indexes = $self->bz_index_list_real($table);
- foreach my $index (@indexes) {
- unless ($index eq 'PRIMARY') {
- my $index_info = $self->bz_index_info_real($table, $index);
- ($index_info = $index_info->{FIELDS})
- if (!$index_info->{TYPE});
- $schema->set_index($table, $index, $index_info);
- }
- }
+ my @indexes = $self->bz_index_list_real($table);
+ foreach my $index (@indexes) {
+ unless ($index eq 'PRIMARY') {
+ my $index_info = $self->bz_index_info_real($table, $index);
+ ($index_info = $index_info->{FIELDS}) if (!$index_info->{TYPE});
+ $schema->set_index($table, $index, $index_info);
+ }
}
+ }
- return $schema;
+ return $schema;
}
1;
diff --git a/Bugzilla/DB/Oracle.pm b/Bugzilla/DB/Oracle.pm
index a519bb796..81ca1090f 100644
--- a/Bugzilla/DB/Oracle.pm
+++ b/Bugzilla/DB/Oracle.pm
@@ -37,466 +37,477 @@ use Bugzilla::Util;
#####################################################################
# Constants
#####################################################################
-use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
+use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
use constant ISOLATION_LEVEL => 'READ COMMITTED';
-use constant BLOB_TYPE => { ora_type => ORA_BLOB };
+use constant BLOB_TYPE => {ora_type => ORA_BLOB};
+
# The max size allowed for LOB fields, in kilobytes.
use constant MIN_LONG_READ_LEN => 32 * 1024;
-use constant FULLTEXT_OR => ' OR ';
+use constant FULLTEXT_OR => ' OR ';
our $fulltext_label = 0;
sub BUILDARGS {
- my ($class, $params) = @_;
- my ($user, $pass, $host, $dbname, $port) =
- @$params{qw(db_user db_pass db_host db_name db_port)};
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port)
+ = @$params{qw(db_user db_pass db_host db_name db_port)};
- # You can never connect to Oracle without a DB name,
- # and there is no default DB.
- $dbname ||= Bugzilla->localconfig->{db_name};
+ # You can never connect to Oracle without a DB name,
+ # and there is no default DB.
+ $dbname ||= Bugzilla->localconfig->{db_name};
- # Set the language enviroment
- $ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
+ # Set the language enviroment
+ $ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
- # construct the DSN from the parameters we got
- my $dsn = "dbi:Oracle:host=$host;sid=$dbname";
- $dsn .= ";port=$port" if $port;
- my $attrs = { FetchHashKeyName => 'NAME_lc',
- LongReadLen => max(Bugzilla->params->{'maxattachmentsize'} || 0,
- MIN_LONG_READ_LEN) * 1024,
- };
- return { dsn => $dsn, user => $user, pass => $pass, attrs => $attrs };
+ # construct the DSN from the parameters we got
+ my $dsn = "dbi:Oracle:host=$host;sid=$dbname";
+ $dsn .= ";port=$port" if $port;
+ my $attrs = {
+ FetchHashKeyName => 'NAME_lc',
+ LongReadLen =>
+ max(Bugzilla->params->{'maxattachmentsize'} || 0, MIN_LONG_READ_LEN) * 1024,
+ };
+ return {dsn => $dsn, user => $user, pass => $pass, attrs => $attrs};
}
sub on_dbi_connected {
- my ($class, $dbh) = @_;
+ my ($class, $dbh) = @_;
+
+ # Set the session's default date format to match MySQL
+ $dbh->do("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
+ $dbh->do("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'");
+ $dbh->do("ALTER SESSION SET NLS_LENGTH_SEMANTICS='CHAR'")
+ if Bugzilla->params->{'utf8'};
- # Set the session's default date format to match MySQL
- $dbh->do("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
- $dbh->do("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'");
- $dbh->do("ALTER SESSION SET NLS_LENGTH_SEMANTICS='CHAR'")
- if Bugzilla->params->{'utf8'};
- # To allow case insensitive query.
- $dbh->do("ALTER SESSION SET NLS_COMP='ANSI'");
- $dbh->do("ALTER SESSION SET NLS_SORT='BINARY_AI'");
+ # To allow case insensitive query.
+ $dbh->do("ALTER SESSION SET NLS_COMP='ANSI'");
+ $dbh->do("ALTER SESSION SET NLS_SORT='BINARY_AI'");
}
sub bz_last_key {
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- my $seq = $table . "_" . $column . "_SEQ";
- my ($last_insert_id) = $self->selectrow_array("SELECT $seq.CURRVAL "
- . " FROM DUAL");
- return $last_insert_id;
+ my $seq = $table . "_" . $column . "_SEQ";
+ my ($last_insert_id)
+ = $self->selectrow_array("SELECT $seq.CURRVAL " . " FROM DUAL");
+ return $last_insert_id;
}
sub bz_check_regexp {
- my ($self, $pattern) = @_;
+ my ($self, $pattern) = @_;
- eval { $self->do("SELECT 1 FROM DUAL WHERE "
- . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+ eval {
+ $self->do("SELECT 1 FROM DUAL WHERE "
+ . $self->sql_regexp($self->quote("a"), $pattern, 1));
+ };
- $@ && ThrowUserError('illegal_regexp',
- { value => $pattern, dberror => $self->errstr });
+ $@
+ && ThrowUserError('illegal_regexp',
+ {value => $pattern, dberror => $self->errstr});
}
sub bz_explain {
- my ($self, $sql) = @_;
- my $sth = $self->prepare("EXPLAIN PLAN FOR $sql");
- $sth->execute();
- my $explain = $self->selectcol_arrayref(
- "SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY)");
- return join("\n", @$explain);
+ my ($self, $sql) = @_;
+ my $sth = $self->prepare("EXPLAIN PLAN FOR $sql");
+ $sth->execute();
+ my $explain = $self->selectcol_arrayref(
+ "SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY)");
+ return join("\n", @$explain);
}
sub sql_group_concat {
- my ($self, $text, $separator) = @_;
- $separator = $self->quote(', ') if !defined $separator;
- my ($distinct, $rest) = $text =~/^(\s*DISTINCT\s|)(.+)$/i;
- return "group_concat($distinct T_CLOB_DELIM(NVL($rest, ' '), $separator))";
+ my ($self, $text, $separator) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ my ($distinct, $rest) = $text =~ /^(\s*DISTINCT\s|)(.+)$/i;
+ return "group_concat($distinct T_CLOB_DELIM(NVL($rest, ' '), $separator))";
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "REGEXP_LIKE($expr, $pattern)";
+ return "REGEXP_LIKE($expr, $pattern)";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "NOT REGEXP_LIKE($expr, $pattern)"
+ return "NOT REGEXP_LIKE($expr, $pattern)";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
+ my ($self, $limit, $offset) = @_;
- if(defined $offset) {
- return "/* LIMIT $limit $offset */";
- }
- return "/* LIMIT $limit */";
+ if (defined $offset) {
+ return "/* LIMIT $limit $offset */";
+ }
+ return "/* LIMIT $limit */";
}
sub sql_string_concat {
- my ($self, @params) = @_;
+ my ($self, @params) = @_;
- return 'CONCAT(' . join(', ', @params) . ')';
+ return 'CONCAT(' . join(', ', @params) . ')';
}
sub sql_to_days {
- my ($self, $date) = @_;
+ my ($self, $date) = @_;
- return " TO_CHAR(TO_DATE($date),'J') ";
+ return " TO_CHAR(TO_DATE($date),'J') ";
}
-sub sql_from_days{
- my ($self, $date) = @_;
- return " TO_DATE($date,'J') ";
+sub sql_from_days {
+ my ($self, $date) = @_;
+
+ return " TO_DATE($date,'J') ";
}
sub sql_fulltext_search {
- my ($self, $column, $text) = @_;
- $text = $self->quote($text);
- trick_taint($text);
- $fulltext_label++;
- return "CONTAINS($column,$text,$fulltext_label) > 0", "SCORE($fulltext_label)";
+ my ($self, $column, $text) = @_;
+ $text = $self->quote($text);
+ trick_taint($text);
+ $fulltext_label++;
+ return "CONTAINS($column,$text,$fulltext_label) > 0", "SCORE($fulltext_label)";
}
sub sql_date_format {
- my ($self, $date, $format) = @_;
+ my ($self, $date, $format) = @_;
- $format = "%Y.%m.%d %H:%i:%s" if !$format;
+ $format = "%Y.%m.%d %H:%i:%s" if !$format;
- $format =~ s/\%Y/YYYY/g;
- $format =~ s/\%y/YY/g;
- $format =~ s/\%m/MM/g;
- $format =~ s/\%d/DD/g;
- $format =~ s/\%a/Dy/g;
- $format =~ s/\%H/HH24/g;
- $format =~ s/\%i/MI/g;
- $format =~ s/\%s/SS/g;
+ $format =~ s/\%Y/YYYY/g;
+ $format =~ s/\%y/YY/g;
+ $format =~ s/\%m/MM/g;
+ $format =~ s/\%d/DD/g;
+ $format =~ s/\%a/Dy/g;
+ $format =~ s/\%H/HH24/g;
+ $format =~ s/\%i/MI/g;
+ $format =~ s/\%s/SS/g;
- return "TO_CHAR($date, " . $self->quote($format) . ")";
+ return "TO_CHAR($date, " . $self->quote($format) . ")";
}
sub sql_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
- my $time_sql;
- if ($units =~ /YEAR|MONTH/i) {
- $time_sql = "NUMTOYMINTERVAL($interval,'$units')";
- } else{
- $time_sql = "NUMTODSINTERVAL($interval,'$units')";
- }
- return "$date $operator $time_sql";
+ my ($self, $date, $operator, $interval, $units) = @_;
+ my $time_sql;
+ if ($units =~ /YEAR|MONTH/i) {
+ $time_sql = "NUMTOYMINTERVAL($interval,'$units')";
+ }
+ else {
+ $time_sql = "NUMTODSINTERVAL($interval,'$units')";
+ }
+ return "$date $operator $time_sql";
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
- return "INSTR($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "INSTR($text, $fragment)";
}
sub sql_in {
- my ($self, $column_name, $in_list_ref, $negate) = @_;
- my @in_list = @$in_list_ref;
- return $self->SUPER::sql_in($column_name, $in_list_ref, $negate) if $#in_list < 1000;
- my @in_str;
- while (@in_list) {
- my $length = $#in_list + 1;
- my $splice = $length > 1000 ? 1000 : $length;
- my @sub_in_list = splice(@in_list, 0, $splice);
- push(@in_str,
- $self->SUPER::sql_in($column_name, \@sub_in_list, $negate));
- }
- return "( " . join(" OR ", @in_str) . " )";
+ my ($self, $column_name, $in_list_ref, $negate) = @_;
+ my @in_list = @$in_list_ref;
+ return $self->SUPER::sql_in($column_name, $in_list_ref, $negate)
+ if $#in_list < 1000;
+ my @in_str;
+ while (@in_list) {
+ my $length = $#in_list + 1;
+ my $splice = $length > 1000 ? 1000 : $length;
+ my @sub_in_list = splice(@in_list, 0, $splice);
+ push(@in_str, $self->SUPER::sql_in($column_name, \@sub_in_list, $negate));
+ }
+ return "( " . join(" OR ", @in_str) . " )";
}
sub _bz_add_field_table {
- my ($self, $name, $schema_ref, $type) = @_;
- $self->SUPER::_bz_add_field_table($name, $schema_ref);
- if (defined($type) && $type == FIELD_TYPE_MULTI_SELECT) {
- my $uk_name = "UK_" . $self->_bz_schema->_hash_identifier($name . '_value');
- $self->do("ALTER TABLE $name ADD CONSTRAINT $uk_name UNIQUE(value)");
- }
+ my ($self, $name, $schema_ref, $type) = @_;
+ $self->SUPER::_bz_add_field_table($name, $schema_ref);
+ if (defined($type) && $type == FIELD_TYPE_MULTI_SELECT) {
+ my $uk_name = "UK_" . $self->_bz_schema->_hash_identifier($name . '_value');
+ $self->do("ALTER TABLE $name ADD CONSTRAINT $uk_name UNIQUE(value)");
+ }
}
sub bz_drop_table {
- my ($self, $name) = @_;
- my $table_exists = $self->bz_table_info($name);
- if ($table_exists) {
- $self->_bz_drop_fks($name);
- $self->SUPER::bz_drop_table($name);
- }
+ my ($self, $name) = @_;
+ my $table_exists = $self->bz_table_info($name);
+ if ($table_exists) {
+ $self->_bz_drop_fks($name);
+ $self->SUPER::bz_drop_table($name);
+ }
}
# Dropping all FKs for a specified table.
sub _bz_drop_fks {
- my ($self, $table) = @_;
- my @columns = $self->bz_table_columns($table);
- foreach my $column (@columns) {
- $self->bz_drop_fk($table, $column);
- }
+ my ($self, $table) = @_;
+ my @columns = $self->bz_table_columns($table);
+ foreach my $column (@columns) {
+ $self->bz_drop_fk($table, $column);
+ }
}
sub _fix_empty {
- my ($string) = @_;
- $string = '' if $string eq EMPTY_STRING;
- return $string;
+ my ($string) = @_;
+ $string = '' if $string eq EMPTY_STRING;
+ return $string;
}
sub _fix_arrayref {
- my ($row) = @_;
- return undef if !defined $row;
- foreach my $field (@$row) {
- $field = _fix_empty($field) if defined $field;
- }
- return $row;
+ my ($row) = @_;
+ return undef if !defined $row;
+ foreach my $field (@$row) {
+ $field = _fix_empty($field) if defined $field;
+ }
+ return $row;
}
sub _fix_hashref {
- my ($row) = @_;
- return undef if !defined $row;
- foreach my $value (values %$row) {
- $value = _fix_empty($value) if defined $value;
- }
- return $row;
+ my ($row) = @_;
+ return undef if !defined $row;
+ foreach my $value (values %$row) {
+ $value = _fix_empty($value) if defined $value;
+ }
+ return $row;
}
sub adjust_statement {
- my ($sql) = @_;
-
- if ($sql =~ /^CREATE OR REPLACE.*/i){
- return $sql;
+ my ($sql) = @_;
+
+ if ($sql =~ /^CREATE OR REPLACE.*/i) {
+ return $sql;
+ }
+
+ # We can't just assume any occurrence of "''" in $sql is an empty
+ # string, since "''" can occur inside a string literal as a way of
+ # escaping a single "'" in the literal. Therefore we must be trickier...
+
+ # split the statement into parts by single-quotes. The negative value
+ # at the end to the split operator from dropping trailing empty strings
+ # (e.g., when $sql ends in "''")
+ my @parts = split /'/, $sql, -1;
+
+ if (!(@parts % 2)) {
+
+ # Either the string is empty or the quotes are mismatched
+ # Returning input unmodified.
+ return $sql;
+ }
+
+ # We already verified that we have an odd number of parts. If we take
+ # the first part off now, we know we're entering the loop with an even
+ # number of parts
+ my @result;
+ my $part = shift @parts;
+
+ # Oracle requires a FROM clause in all SELECT statements, so append
+ # "FROM dual" to queries without one (e.g., "SELECT NOW()")
+ my $is_select = ($part =~ m/^\s*SELECT\b/io);
+ my $has_from = ($part =~ m/\bFROM\b/io) if $is_select;
+
+ # Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
+ # and its CURRENT_DATE is a date+time, so wrap in TRUNC()
+ $part =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
+
+ # Oracle use SUBSTR instead of SUBSTRING
+ $part =~ s/\bSUBSTRING\b/SUBSTR/io;
+
+ # Oracle need no 'AS'
+ $part =~ s/\bAS\b//ig;
+
+ # Oracle doesn't have LIMIT, so if we find the LIMIT comment, wrap the
+ # query with "SELECT * FROM (...) WHERE rownum < $limit"
+ my ($limit, $offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o);
+
+ push @result, $part;
+ while (@parts) {
+ my $string = shift @parts;
+ my $nonstring = shift @parts;
+
+ # if the non-string part is zero-length and there are more parts left,
+ # then this is an escaped quote inside a string literal
+ while (!(length $nonstring) && @parts) {
+
+ # we know it's safe to remove two parts at a time, since we
+ # entered the loop with an even number of parts
+ $string .= "''" . shift @parts;
+ $nonstring = shift @parts;
}
- # We can't just assume any occurrence of "''" in $sql is an empty
- # string, since "''" can occur inside a string literal as a way of
- # escaping a single "'" in the literal. Therefore we must be trickier...
-
- # split the statement into parts by single-quotes. The negative value
- # at the end to the split operator from dropping trailing empty strings
- # (e.g., when $sql ends in "''")
- my @parts = split /'/, $sql, -1;
-
- if( !(@parts % 2) ) {
- # Either the string is empty or the quotes are mismatched
- # Returning input unmodified.
- return $sql;
- }
-
- # We already verified that we have an odd number of parts. If we take
- # the first part off now, we know we're entering the loop with an even
- # number of parts
- my @result;
- my $part = shift @parts;
-
- # Oracle requires a FROM clause in all SELECT statements, so append
- # "FROM dual" to queries without one (e.g., "SELECT NOW()")
- my $is_select = ($part =~ m/^\s*SELECT\b/io);
- my $has_from = ($part =~ m/\bFROM\b/io) if $is_select;
+ # Look for a FROM if this is a SELECT and we haven't found one yet
+ $has_from = ($nonstring =~ m/\bFROM\b/io) if ($is_select and !$has_from);
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
# and its CURRENT_DATE is a date+time, so wrap in TRUNC()
- $part =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
+ $nonstring =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
# Oracle use SUBSTR instead of SUBSTRING
- $part =~ s/\bSUBSTRING\b/SUBSTR/io;
+ $nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
# Oracle need no 'AS'
- $part =~ s/\bAS\b//ig;
-
- # Oracle doesn't have LIMIT, so if we find the LIMIT comment, wrap the
- # query with "SELECT * FROM (...) WHERE rownum < $limit"
- my ($limit,$offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o);
-
- push @result, $part;
- while( @parts ) {
- my $string = shift @parts;
- my $nonstring = shift @parts;
-
- # if the non-string part is zero-length and there are more parts left,
- # then this is an escaped quote inside a string literal
- while( !(length $nonstring) && @parts ) {
- # we know it's safe to remove two parts at a time, since we
- # entered the loop with an even number of parts
- $string .= "''" . shift @parts;
- $nonstring = shift @parts;
- }
+ $nonstring =~ s/\bAS\b//ig;
- # Look for a FROM if this is a SELECT and we haven't found one yet
- $has_from = ($nonstring =~ m/\bFROM\b/io)
- if ($is_select and !$has_from);
-
- # Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
- # and its CURRENT_DATE is a date+time, so wrap in TRUNC()
- $nonstring =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
-
- # Oracle use SUBSTR instead of SUBSTRING
- $nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
-
- # Oracle need no 'AS'
- $nonstring =~ s/\bAS\b//ig;
-
- # Take the first 4000 chars for comparison
- $nonstring =~ s/\(\s*(longdescs_\d+\.thetext|attachdata_\d+\.thedata)/
+ # Take the first 4000 chars for comparison
+ $nonstring =~ s/\(\s*(longdescs_\d+\.thetext|attachdata_\d+\.thedata)/
\(DBMS_LOB.SUBSTR\($1, 4000, 1\)/ig;
- # Look for a LIMIT clause
- ($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
+ # Look for a LIMIT clause
+ ($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
- if(!length($string)){
- push @result, EMPTY_STRING;
- push @result, $nonstring;
- } else {
- push @result, $string;
- push @result, $nonstring;
- }
+ if (!length($string)) {
+ push @result, EMPTY_STRING;
+ push @result, $nonstring;
}
+ else {
+ push @result, $string;
+ push @result, $nonstring;
+ }
+ }
- my $new_sql = join "'", @result;
+ my $new_sql = join "'", @result;
- # Append "FROM dual" if this is a SELECT without a FROM clause
- $new_sql .= " FROM DUAL" if ($is_select and !$has_from);
+ # Append "FROM dual" if this is a SELECT without a FROM clause
+ $new_sql .= " FROM DUAL" if ($is_select and !$has_from);
- # Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
+ # Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
- if (defined($limit)) {
- if ($new_sql !~ /\bWHERE\b/) {
- $new_sql = $new_sql." WHERE 1=1";
- }
- my ($before_where, $after_where) = split(/\bWHERE\b/i, $new_sql, 2);
- if (defined($offset)) {
- my ($before_from, $after_from) = split(/\bFROM\b/i, $new_sql, 2);
- $before_where = "$before_from FROM ($before_from,"
- . " ROW_NUMBER() OVER (ORDER BY 1) R "
- . " FROM $after_from ) ";
- $after_where = " R BETWEEN $offset+1 AND $limit+$offset";
- } else {
- $after_where = " rownum <=$limit AND ".$after_where;
- }
- $new_sql = $before_where." WHERE ".$after_where;
+ if (defined($limit)) {
+ if ($new_sql !~ /\bWHERE\b/) {
+ $new_sql = $new_sql . " WHERE 1=1";
}
- return $new_sql;
+ my ($before_where, $after_where) = split(/\bWHERE\b/i, $new_sql, 2);
+ if (defined($offset)) {
+ my ($before_from, $after_from) = split(/\bFROM\b/i, $new_sql, 2);
+ $before_where
+ = "$before_from FROM ($before_from,"
+ . " ROW_NUMBER() OVER (ORDER BY 1) R "
+ . " FROM $after_from ) ";
+ $after_where = " R BETWEEN $offset+1 AND $limit+$offset";
+ }
+ else {
+ $after_where = " rownum <=$limit AND " . $after_where;
+ }
+ $new_sql = $before_where . " WHERE " . $after_where;
+ }
+ return $new_sql;
}
sub do {
- my $self = shift;
- my $sql = shift;
- $sql = adjust_statement($sql);
- unshift @_, $sql;
- return $self->SUPER::do(@_);
+ my $self = shift;
+ my $sql = shift;
+ $sql = adjust_statement($sql);
+ unshift @_, $sql;
+ return $self->SUPER::do(@_);
}
sub selectrow_array {
- my $self = shift;
- my $stmt = shift;
- my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
- unshift @_, $new_stmt;
- if ( wantarray ) {
- my @row = $self->SUPER::selectrow_array(@_);
- _fix_arrayref(\@row);
- return @row;
- } else {
- my $row = $self->SUPER::selectrow_array(@_);
- $row = _fix_empty($row) if defined $row;
- return $row;
- }
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ if (wantarray) {
+ my @row = $self->SUPER::selectrow_array(@_);
+ _fix_arrayref(\@row);
+ return @row;
+ }
+ else {
+ my $row = $self->SUPER::selectrow_array(@_);
+ $row = _fix_empty($row) if defined $row;
+ return $row;
+ }
}
sub selectrow_arrayref {
- my $self = shift;
- my $stmt = shift;
- my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
- unshift @_, $new_stmt;
- my $ref = $self->SUPER::selectrow_arrayref(@_);
- return undef if !defined $ref;
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectrow_arrayref(@_);
+ return undef if !defined $ref;
- _fix_arrayref($ref);
- return $ref;
+ _fix_arrayref($ref);
+ return $ref;
}
sub selectrow_hashref {
- my $self = shift;
- my $stmt = shift;
- my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
- unshift @_, $new_stmt;
- my $ref = $self->SUPER::selectrow_hashref(@_);
- return undef if !defined $ref;
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectrow_hashref(@_);
+ return undef if !defined $ref;
- _fix_hashref($ref);
- return $ref;
+ _fix_hashref($ref);
+ return $ref;
}
sub selectall_arrayref {
- my $self = shift;
- my $stmt = shift;
- my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
- unshift @_, $new_stmt;
- my $ref = $self->SUPER::selectall_arrayref(@_);
- return undef if !defined $ref;
-
- foreach my $row (@$ref) {
- if (ref($row) eq 'ARRAY') {
- _fix_arrayref($row);
- }
- elsif (ref($row) eq 'HASH') {
- _fix_hashref($row);
- }
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectall_arrayref(@_);
+ return undef if !defined $ref;
+
+ foreach my $row (@$ref) {
+ if (ref($row) eq 'ARRAY') {
+ _fix_arrayref($row);
+ }
+ elsif (ref($row) eq 'HASH') {
+ _fix_hashref($row);
}
+ }
- return $ref;
+ return $ref;
}
sub selectall_hashref {
- my $self = shift;
- my $stmt = shift;
- my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
- unshift @_, $new_stmt;
- my $rows = $self->SUPER::selectall_hashref(@_);
- return undef if !defined $rows;
- foreach my $row (values %$rows) {
- _fix_hashref($row);
- }
- return $rows;
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $rows = $self->SUPER::selectall_hashref(@_);
+ return undef if !defined $rows;
+ foreach my $row (values %$rows) {
+ _fix_hashref($row);
+ }
+ return $rows;
}
sub selectcol_arrayref {
- my $self = shift;
- my $stmt = shift;
- my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
- unshift @_, $new_stmt;
- my $ref = $self->SUPER::selectcol_arrayref(@_);
- return undef if !defined $ref;
- _fix_arrayref($ref);
- return $ref;
+ my $self = shift;
+ my $stmt = shift;
+ my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+ unshift @_, $new_stmt;
+ my $ref = $self->SUPER::selectcol_arrayref(@_);
+ return undef if !defined $ref;
+ _fix_arrayref($ref);
+ return $ref;
}
sub prepare {
- my $self = shift;
- my $sql = shift;
- my $new_sql = adjust_statement($sql);
- unshift @_, $new_sql;
- return bless $self->SUPER::prepare(@_),
- 'Bugzilla::DB::Oracle::st';
+ my $self = shift;
+ my $sql = shift;
+ my $new_sql = adjust_statement($sql);
+ unshift @_, $new_sql;
+ return bless $self->SUPER::prepare(@_), 'Bugzilla::DB::Oracle::st';
}
sub prepare_cached {
- my $self = shift;
- my $sql = shift;
- my $new_sql = adjust_statement($sql);
- unshift @_, $new_sql;
- return bless $self->SUPER::prepare_cached(@_),
- 'Bugzilla::DB::Oracle::st';
+ my $self = shift;
+ my $sql = shift;
+ my $new_sql = adjust_statement($sql);
+ unshift @_, $new_sql;
+ return bless $self->SUPER::prepare_cached(@_), 'Bugzilla::DB::Oracle::st';
}
sub quote_identifier {
- my ($self,$id) = @_;
- return $id;
+ my ($self, $id) = @_;
+ return $id;
}
#####################################################################
@@ -504,20 +515,22 @@ sub quote_identifier {
#####################################################################
sub bz_table_columns_real {
- my ($self, $table) = @_;
- $table = uc($table);
- my $cols = $self->selectcol_arrayref(
- "SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE
- TABLE_NAME = ? ORDER BY COLUMN_NAME", undef, $table);
- return @$cols;
+ my ($self, $table) = @_;
+ $table = uc($table);
+ my $cols = $self->selectcol_arrayref(
+ "SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE
+ TABLE_NAME = ? ORDER BY COLUMN_NAME", undef, $table
+ );
+ return @$cols;
}
sub bz_table_list_real {
- my ($self) = @_;
- my $tables = $self->selectcol_arrayref(
- "SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE
- TABLE_NAME NOT LIKE ? ORDER BY TABLE_NAME", undef, 'DR$%');
- return @$tables;
+ my ($self) = @_;
+ my $tables = $self->selectcol_arrayref(
+ "SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE
+ TABLE_NAME NOT LIKE ? ORDER BY TABLE_NAME", undef, 'DR$%'
+ );
+ return @$tables;
}
#####################################################################
@@ -525,32 +538,37 @@ sub bz_table_list_real {
#####################################################################
sub bz_setup_database {
- my $self = shift;
-
- # Create a function that returns SYSDATE to emulate MySQL's "NOW()".
- # Function NOW() is used widely in Bugzilla SQLs, but Oracle does not
- # have that function, So we have to create one ourself.
- $self->do("CREATE OR REPLACE FUNCTION NOW "
- . " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
- $self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
- . " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
-
- # Create types for group_concat
- my $type_exists = $self->selectrow_array("SELECT 1 FROM user_types
- WHERE type_name = 'T_GROUP_CONCAT'");
- $self->do("DROP TYPE T_GROUP_CONCAT") if $type_exists;
- $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
- . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)"
- . ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2"
- . ");");
- $self->do("CREATE OR REPLACE TYPE BODY T_CLOB_DELIM IS
+ my $self = shift;
+
+ # Create a function that returns SYSDATE to emulate MySQL's "NOW()".
+ # Function NOW() is used widely in Bugzilla SQLs, but Oracle does not
+ # have that function, So we have to create one ourself.
+ $self->do("CREATE OR REPLACE FUNCTION NOW "
+ . " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
+ $self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
+ . " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
+
+ # Create types for group_concat
+ my $type_exists = $self->selectrow_array(
+ "SELECT 1 FROM user_types
+ WHERE type_name = 'T_GROUP_CONCAT'"
+ );
+ $self->do("DROP TYPE T_GROUP_CONCAT") if $type_exists;
+ $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
+ . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)"
+ . ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2"
+ . ");");
+ $self->do(
+ "CREATE OR REPLACE TYPE BODY T_CLOB_DELIM IS
MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2 is
BEGIN
RETURN p_CONTENT;
END;
- END;");
+ END;"
+ );
- $self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
+ $self->do(
+ "CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
( CLOB_CONTENT CLOB,
DELIMITER VARCHAR2(256),
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
@@ -568,9 +586,11 @@ sub bz_setup_database {
MEMBER FUNCTION ODCIAGGREGATEMERGE(
SELF IN OUT NOCOPY T_GROUP_CONCAT,
CTX2 IN T_GROUP_CONCAT)
- RETURN NUMBER);");
+ RETURN NUMBER);"
+ );
- $self->do("CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT IS
+ $self->do(
+ "CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT IS
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
SCTX IN OUT NOCOPY T_GROUP_CONCAT)
RETURN NUMBER IS
@@ -614,110 +634,117 @@ sub bz_setup_database {
DBMS_LOB.APPEND(SELF.CLOB_CONTENT, CTX2.CLOB_CONTENT);
RETURN ODCICONST.SUCCESS;
END;
- END;");
+ END;"
+ );
- # Create user-defined aggregate function group_concat
- $self->do("CREATE OR REPLACE FUNCTION GROUP_CONCAT(P_INPUT T_CLOB_DELIM)
+ # Create user-defined aggregate function group_concat
+ $self->do(
+ "CREATE OR REPLACE FUNCTION GROUP_CONCAT(P_INPUT T_CLOB_DELIM)
RETURN CLOB
- DETERMINISTIC PARALLEL_ENABLE AGGREGATE USING T_GROUP_CONCAT;");
-
- # Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
- my $lexer = $self->selectcol_arrayref(
- "SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
- pre_owner = ?",
- undef,'BZ_LEX',uc(Bugzilla->localconfig->{db_user}));
- if(!@$lexer) {
- $self->do("BEGIN CTX_DDL.CREATE_PREFERENCE
- ('BZ_LEX', 'WORLD_LEXER'); END;");
- }
-
- $self->SUPER::bz_setup_database(@_);
-
- my $sth = $self->prepare("SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_NAME = ?");
- my @tables = $self->bz_table_list_real();
-
- foreach my $table (@tables) {
- my @columns = $self->bz_table_columns_real($table);
- foreach my $column (@columns) {
- my $def = $self->bz_column_info($table, $column);
- # bz_add_column() before Bugzilla 4.2.3 didn't handle primary keys
- # correctly (bug 731156). We have to add missing sequences and
- # triggers ourselves.
- if ($def->{TYPE} =~ /SERIAL/i) {
- my $sequence = "${table}_${column}_SEQ";
- my $exists = $self->selectrow_array($sth, undef, $sequence);
- if (!$exists) {
- my @sql = $self->_get_create_seq_ddl($table, $column);
- $self->do($_) foreach @sql;
- }
- }
-
- if ($def->{REFERENCES}) {
- my $references = $def->{REFERENCES};
- my $update = $references->{UPDATE} || 'CASCADE';
- my $to_table = $references->{TABLE};
- my $to_column = $references->{COLUMN};
- my $fk_name = $self->_bz_schema->_get_fk_name($table,
- $column,
- $references);
- # bz_rename_table didn't rename the trigger correctly.
- if ($table eq 'bug_tag' && $to_table eq 'tags') {
- $to_table = 'tag';
- }
- if ( $update =~ /CASCADE/i ){
- my $trigger_name = uc($fk_name . "_UC");
- my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
- if(@$exist_trigger) {
- $self->do("DROP TRIGGER $trigger_name");
- }
-
- my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
- . " AFTER UPDATE OF $to_column ON $to_table "
- . " REFERENCING "
- . " NEW AS NEW "
- . " OLD AS OLD "
- . " FOR EACH ROW "
- . " BEGIN "
- . " UPDATE $table"
- . " SET $column = :NEW.$to_column"
- . " WHERE $column = :OLD.$to_column;"
- . " END $trigger_name;";
- $self->do($tr_str);
- }
- }
+ DETERMINISTIC PARALLEL_ENABLE AGGREGATE USING T_GROUP_CONCAT;"
+ );
+
+ # Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
+ my $lexer = $self->selectcol_arrayref(
+ "SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
+ pre_owner = ?", undef, 'BZ_LEX', uc(Bugzilla->localconfig->{db_user})
+ );
+ if (!@$lexer) {
+ $self->do(
+ "BEGIN CTX_DDL.CREATE_PREFERENCE
+ ('BZ_LEX', 'WORLD_LEXER'); END;"
+ );
+ }
+
+ $self->SUPER::bz_setup_database(@_);
+
+ my $sth = $self->prepare(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_NAME = ?");
+ my @tables = $self->bz_table_list_real();
+
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns_real($table);
+ foreach my $column (@columns) {
+ my $def = $self->bz_column_info($table, $column);
+
+ # bz_add_column() before Bugzilla 4.2.3 didn't handle primary keys
+ # correctly (bug 731156). We have to add missing sequences and
+ # triggers ourselves.
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ my $sequence = "${table}_${column}_SEQ";
+ my $exists = $self->selectrow_array($sth, undef, $sequence);
+ if (!$exists) {
+ my @sql = $self->_get_create_seq_ddl($table, $column);
+ $self->do($_) foreach @sql;
}
+ }
+
+ if ($def->{REFERENCES}) {
+ my $references = $def->{REFERENCES};
+ my $update = $references->{UPDATE} || 'CASCADE';
+ my $to_table = $references->{TABLE};
+ my $to_column = $references->{COLUMN};
+ my $fk_name = $self->_bz_schema->_get_fk_name($table, $column, $references);
+
+ # bz_rename_table didn't rename the trigger correctly.
+ if ($table eq 'bug_tag' && $to_table eq 'tags') {
+ $to_table = 'tag';
+ }
+ if ($update =~ /CASCADE/i) {
+ my $trigger_name = uc($fk_name . "_UC");
+ my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
+ if (@$exist_trigger) {
+ $self->do("DROP TRIGGER $trigger_name");
+ }
+
+ my $tr_str
+ = "CREATE OR REPLACE TRIGGER $trigger_name"
+ . " AFTER UPDATE OF $to_column ON $to_table "
+ . " REFERENCING "
+ . " NEW AS NEW "
+ . " OLD AS OLD "
+ . " FOR EACH ROW "
+ . " BEGIN "
+ . " UPDATE $table"
+ . " SET $column = :NEW.$to_column"
+ . " WHERE $column = :OLD.$to_column;"
+ . " END $trigger_name;";
+ $self->do($tr_str);
+ }
+ }
}
+ }
- # Drop the trigger which causes bug 541553
- my $trigger_name = "PRODUCTS_MILESTONEURL";
- my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
- if(@$exist_trigger) {
- $self->do("DROP TRIGGER $trigger_name");
- }
+ # Drop the trigger which causes bug 541553
+ my $trigger_name = "PRODUCTS_MILESTONEURL";
+ my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
+ if (@$exist_trigger) {
+ $self->do("DROP TRIGGER $trigger_name");
+ }
}
# These two methods have been copied from Bugzilla::DB::Schema::Oracle.
sub _get_create_seq_ddl {
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- my $seq_name = "${table}_${column}_SEQ";
- my $seq_sql = "CREATE SEQUENCE $seq_name INCREMENT BY 1 START WITH 1 " .
- "NOMAXVALUE NOCYCLE NOCACHE";
- my $trigger_sql = $self->_get_create_trigger_ddl($table, $column, $seq_name);
- return ($seq_sql, $trigger_sql);
+ my $seq_name = "${table}_${column}_SEQ";
+ my $seq_sql = "CREATE SEQUENCE $seq_name INCREMENT BY 1 START WITH 1 "
+ . "NOMAXVALUE NOCYCLE NOCACHE";
+ my $trigger_sql = $self->_get_create_trigger_ddl($table, $column, $seq_name);
+ return ($seq_sql, $trigger_sql);
}
sub _get_create_trigger_ddl {
- my ($self, $table, $column, $seq_name) = @_;
+ my ($self, $table, $column, $seq_name) = @_;
- my $trigger_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
- . " BEFORE INSERT ON $table "
- . " FOR EACH ROW "
- . " BEGIN "
- . " SELECT ${seq_name}.NEXTVAL "
- . " INTO :NEW.$column FROM DUAL; "
- . " END;";
- return $trigger_sql;
+ my $trigger_sql
+ = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
+ . " BEFORE INSERT ON $table "
+ . " FOR EACH ROW "
+ . " BEGIN "
+ . " SELECT ${seq_name}.NEXTVAL "
+ . " INTO :NEW.$column FROM DUAL; " . " END;";
+ return $trigger_sql;
}
############################################################################
@@ -726,65 +753,66 @@ package Bugzilla::DB::Oracle::st;
use base qw(DBI::st);
sub fetchrow_arrayref {
- my $self = shift;
- my $ref = $self->SUPER::fetchrow_arrayref(@_);
- return undef if !defined $ref;
- Bugzilla::DB::Oracle::_fix_arrayref($ref);
- return $ref;
+ my $self = shift;
+ my $ref = $self->SUPER::fetchrow_arrayref(@_);
+ return undef if !defined $ref;
+ Bugzilla::DB::Oracle::_fix_arrayref($ref);
+ return $ref;
}
sub fetchrow_array {
- my $self = shift;
- if ( wantarray ) {
- my @row = $self->SUPER::fetchrow_array(@_);
- Bugzilla::DB::Oracle::_fix_arrayref(\@row);
- return @row;
- } else {
- my $row = $self->SUPER::fetchrow_array(@_);
- $row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
- return $row;
- }
+ my $self = shift;
+ if (wantarray) {
+ my @row = $self->SUPER::fetchrow_array(@_);
+ Bugzilla::DB::Oracle::_fix_arrayref(\@row);
+ return @row;
+ }
+ else {
+ my $row = $self->SUPER::fetchrow_array(@_);
+ $row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
+ return $row;
+ }
}
sub fetchrow_hashref {
- my $self = shift;
- my $ref = $self->SUPER::fetchrow_hashref(@_);
- return undef if !defined $ref;
- Bugzilla::DB::Oracle::_fix_hashref($ref);
- return $ref;
+ my $self = shift;
+ my $ref = $self->SUPER::fetchrow_hashref(@_);
+ return undef if !defined $ref;
+ Bugzilla::DB::Oracle::_fix_hashref($ref);
+ return $ref;
}
sub fetchall_arrayref {
- my $self = shift;
- my $ref = $self->SUPER::fetchall_arrayref(@_);
- return undef if !defined $ref;
- foreach my $row (@$ref) {
- if (ref($row) eq 'ARRAY') {
- Bugzilla::DB::Oracle::_fix_arrayref($row);
- }
- elsif (ref($row) eq 'HASH') {
- Bugzilla::DB::Oracle::_fix_hashref($row);
- }
+ my $self = shift;
+ my $ref = $self->SUPER::fetchall_arrayref(@_);
+ return undef if !defined $ref;
+ foreach my $row (@$ref) {
+ if (ref($row) eq 'ARRAY') {
+ Bugzilla::DB::Oracle::_fix_arrayref($row);
+ }
+ elsif (ref($row) eq 'HASH') {
+ Bugzilla::DB::Oracle::_fix_hashref($row);
}
- return $ref;
+ }
+ return $ref;
}
sub fetchall_hashref {
- my $self = shift;
- my $ref = $self->SUPER::fetchall_hashref(@_);
- return undef if !defined $ref;
- foreach my $row (values %$ref) {
- Bugzilla::DB::Oracle::_fix_hashref($row);
- }
- return $ref;
+ my $self = shift;
+ my $ref = $self->SUPER::fetchall_hashref(@_);
+ return undef if !defined $ref;
+ foreach my $row (values %$ref) {
+ Bugzilla::DB::Oracle::_fix_hashref($row);
+ }
+ return $ref;
}
sub fetch {
- my $self = shift;
- my $row = $self->SUPER::fetch(@_);
- if ($row) {
- Bugzilla::DB::Oracle::_fix_arrayref($row);
- }
- return $row;
+ my $self = shift;
+ my $row = $self->SUPER::fetch(@_);
+ if ($row) {
+ Bugzilla::DB::Oracle::_fix_arrayref($row);
+ }
+ return $row;
}
1;
diff --git a/Bugzilla/DB/Pg.pm b/Bugzilla/DB/Pg.pm
index 0db349412..d1bb0f798 100644
--- a/Bugzilla/DB/Pg.pm
+++ b/Bugzilla/DB/Pg.pm
@@ -31,151 +31,153 @@ use DBD::Pg;
# This module extends the DB interface via inheritance
extends qw(Bugzilla::DB);
-use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
+use constant BLOB_TYPE => {pg_type => DBD::Pg::PG_BYTEA};
sub BUILDARGS {
- my ($class, $params) = @_;
- my ($user, $pass, $host, $dbname, $port) =
- @$params{qw(db_user db_pass db_host db_name db_port)};
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port)
+ = @$params{qw(db_user db_pass db_host db_name db_port)};
- # The default database name for PostgreSQL. We have
- # to connect to SOME database, even if we have
- # no $dbname parameter.
- $dbname ||= 'template1';
+ # The default database name for PostgreSQL. We have
+ # to connect to SOME database, even if we have
+ # no $dbname parameter.
+ $dbname ||= 'template1';
- # construct the DSN from the parameters we got
- my $dsn = "dbi:Pg:dbname=$dbname";
- $dsn .= ";host=$host" if $host;
- $dsn .= ";port=$port" if $port;
+ # construct the DSN from the parameters we got
+ my $dsn = "dbi:Pg:dbname=$dbname";
+ $dsn .= ";host=$host" if $host;
+ $dsn .= ";port=$port" if $port;
- # This stops Pg from printing out lots of "NOTICE" messages when
- # creating tables.
- $dsn .= ";options='-c client_min_messages=warning'";
+ # This stops Pg from printing out lots of "NOTICE" messages when
+ # creating tables.
+ $dsn .= ";options='-c client_min_messages=warning'";
- my $attrs = { pg_enable_utf8 => Bugzilla->params->{'utf8'} };
+ my $attrs = {pg_enable_utf8 => Bugzilla->params->{'utf8'}};
- return { dsn => $dsn, user => $user, pass => $pass, attrs => $attrs }
+ return {dsn => $dsn, user => $user, pass => $pass, attrs => $attrs};
}
# if last_insert_id is supported on PostgreSQL by lowest DBI/DBD version
# supported by Bugzilla, this implementation can be removed.
sub bz_last_key {
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- my $seq = $table . "_" . $column . "_seq";
- my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
+ my $seq = $table . "_" . $column . "_seq";
+ my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
- return $last_insert_id;
+ return $last_insert_id;
}
sub sql_group_concat {
- my ($self, $text, $separator, $sort) = @_;
- $sort = 1 if !defined $sort;
- $separator = $self->quote(', ') if !defined $separator;
- my $sql = "array_accum($text)";
- if ($sort) {
- $sql = "array_sort($sql)";
- }
- return "array_to_string($sql, $separator)";
+ my ($self, $text, $separator, $sort) = @_;
+ $sort = 1 if !defined $sort;
+ $separator = $self->quote(', ') if !defined $separator;
+ my $sql = "array_accum($text)";
+ if ($sort) {
+ $sql = "array_sort($sql)";
+ }
+ return "array_to_string($sql, $separator)";
}
sub sql_istring {
- my ($self, $string) = @_;
+ my ($self, $string) = @_;
- return "LOWER(${string}::text)";
+ return "LOWER(${string}::text)";
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
+ my ($self, $fragment, $text) = @_;
- return "POSITION(${fragment}::text IN ${text}::text)";
+ return "POSITION(${fragment}::text IN ${text}::text)";
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "${expr}::text ~* $pattern";
+ return "${expr}::text ~* $pattern";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "${expr}::text !~* $pattern"
+ return "${expr}::text !~* $pattern";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
-
- if (defined($offset)) {
- return "LIMIT $limit OFFSET $offset";
- } else {
- return "LIMIT $limit";
- }
+ my ($self, $limit, $offset) = @_;
+
+ if (defined($offset)) {
+ return "LIMIT $limit OFFSET $offset";
+ }
+ else {
+ return "LIMIT $limit";
+ }
}
sub sql_from_days {
- my ($self, $days) = @_;
+ my ($self, $days) = @_;
- return "TO_TIMESTAMP('$days', 'J')::date";
+ return "TO_TIMESTAMP('$days', 'J')::date";
}
sub sql_to_days {
- my ($self, $date) = @_;
+ my ($self, $date) = @_;
- return "TO_CHAR(${date}::date, 'J')::int";
+ return "TO_CHAR(${date}::date, 'J')::int";
}
sub sql_date_format {
- my ($self, $date, $format) = @_;
+ my ($self, $date, $format) = @_;
- $format = "%Y.%m.%d %H:%i:%s" if !$format;
+ $format = "%Y.%m.%d %H:%i:%s" if !$format;
- $format =~ s/\%Y/YYYY/g;
- $format =~ s/\%y/YY/g;
- $format =~ s/\%m/MM/g;
- $format =~ s/\%d/DD/g;
- $format =~ s/\%a/Dy/g;
- $format =~ s/\%H/HH24/g;
- $format =~ s/\%i/MI/g;
- $format =~ s/\%s/SS/g;
+ $format =~ s/\%Y/YYYY/g;
+ $format =~ s/\%y/YY/g;
+ $format =~ s/\%m/MM/g;
+ $format =~ s/\%d/DD/g;
+ $format =~ s/\%a/Dy/g;
+ $format =~ s/\%H/HH24/g;
+ $format =~ s/\%i/MI/g;
+ $format =~ s/\%s/SS/g;
- return "TO_CHAR($date, " . $self->quote($format) . ")";
+ return "TO_CHAR($date, " . $self->quote($format) . ")";
}
sub sql_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
+ my ($self, $date, $operator, $interval, $units) = @_;
- return "$date $operator $interval * INTERVAL '1 $units'";
+ return "$date $operator $interval * INTERVAL '1 $units'";
}
sub sql_string_concat {
- my ($self, @params) = @_;
+ my ($self, @params) = @_;
- # Postgres 7.3 does not support concatenating of different types, so we
- # need to cast both parameters to text. Version 7.4 seems to handle this
- # properly, so when we stop support 7.3, this can be removed.
- return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
+ # Postgres 7.3 does not support concatenating of different types, so we
+ # need to cast both parameters to text. Version 7.4 seems to handle this
+ # properly, so when we stop support 7.3, this can be removed.
+ return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
}
# Tell us whether or not a particular sequence exists in the DB.
sub bz_sequence_exists {
- my ($self, $seq_name) = @_;
- my $exists = $self->selectrow_array(
- 'SELECT 1 FROM pg_statio_user_sequences WHERE relname = ?',
- undef, $seq_name);
- return $exists || 0;
+ my ($self, $seq_name) = @_;
+ my $exists
+ = $self->selectrow_array(
+ 'SELECT 1 FROM pg_statio_user_sequences WHERE relname = ?',
+ undef, $seq_name);
+ return $exists || 0;
}
sub bz_explain {
- my ($self, $sql) = @_;
- my $explain = $self->selectcol_arrayref("EXPLAIN ANALYZE $sql");
- return join("\n", @$explain);
+ my ($self, $sql) = @_;
+ my $explain = $self->selectcol_arrayref("EXPLAIN ANALYZE $sql");
+ return join("\n", @$explain);
}
#####################################################################
@@ -183,38 +185,42 @@ sub bz_explain {
#####################################################################
sub bz_check_server_version {
- my $self = shift;
- my ($db) = @_;
- my $server_version = $self->SUPER::bz_check_server_version(@_);
- my ($major_version, $minor_version) = $server_version =~ /^0*(\d+)\.0*(\d+)/;
- # Pg 9.0 requires DBD::Pg 2.17.2 in order to properly read bytea values.
- # Pg 9.2 requires DBD::Pg 2.19.3 as spclocation no longer exists.
- if ($major_version >= 9) {
- local $db->{dbd}->{version} = ($minor_version >= 2) ? '2.19.3' : '2.17.2';
- local $db->{name} = $db->{name} . " ${major_version}.$minor_version";
- Bugzilla::DB::_bz_check_dbd(@_);
- }
+ my $self = shift;
+ my ($db) = @_;
+ my $server_version = $self->SUPER::bz_check_server_version(@_);
+ my ($major_version, $minor_version) = $server_version =~ /^0*(\d+)\.0*(\d+)/;
+
+ # Pg 9.0 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+ # Pg 9.2 requires DBD::Pg 2.19.3 as spclocation no longer exists.
+ if ($major_version >= 9) {
+ local $db->{dbd}->{version} = ($minor_version >= 2) ? '2.19.3' : '2.17.2';
+ local $db->{name} = $db->{name} . " ${major_version}.$minor_version";
+ Bugzilla::DB::_bz_check_dbd(@_);
+ }
}
sub bz_setup_database {
- my $self = shift;
- $self->SUPER::bz_setup_database(@_);
-
- # Custom Functions
- my $function = 'array_accum';
- my $array_accum = $self->selectrow_array(
- 'SELECT 1 FROM pg_proc WHERE proname = ?', undef, $function);
- if (!$array_accum) {
- print "Creating function $function...\n";
- $self->do("CREATE AGGREGATE array_accum (
+ my $self = shift;
+ $self->SUPER::bz_setup_database(@_);
+
+ # Custom Functions
+ my $function = 'array_accum';
+ my $array_accum
+ = $self->selectrow_array('SELECT 1 FROM pg_proc WHERE proname = ?',
+ undef, $function);
+ if (!$array_accum) {
+ print "Creating function $function...\n";
+ $self->do(
+ "CREATE AGGREGATE array_accum (
SFUNC = array_append,
BASETYPE = anyelement,
STYPE = anyarray,
INITCOND = '{}'
- )");
- }
+ )"
+ );
+ }
- $self->do(<<'END');
+ $self->do(<<'END');
CREATE OR REPLACE FUNCTION array_sort(ANYARRAY)
RETURNS ANYARRAY LANGUAGE SQL
IMMUTABLE STRICT
@@ -228,117 +234,132 @@ SELECT ARRAY(
$$;
END
- # PostgreSQL doesn't like having *any* index on the thetext
- # field, because it can't have index data longer than 2770
- # characters on that field.
- $self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
- # Same for all the comments fields in the fulltext table.
- $self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_idx');
- $self->bz_drop_index('bugs_fulltext',
- 'bugs_fulltext_comments_noprivate_idx');
-
- # PostgreSQL also wants an index for calling LOWER on
- # login_name, which we do with sql_istrcmp all over the place.
- $self->bz_add_index('profiles', 'profiles_login_name_lower_idx',
- {FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'});
-
- # Now that Bugzilla::Object uses sql_istrcmp, other tables
- # also need a LOWER() index.
- _fix_case_differences('fielddefs', 'name');
- $self->bz_add_index('fielddefs', 'fielddefs_name_lower_idx',
- {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
- _fix_case_differences('keyworddefs', 'name');
- $self->bz_add_index('keyworddefs', 'keyworddefs_name_lower_idx',
- {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
- _fix_case_differences('products', 'name');
- $self->bz_add_index('products', 'products_name_lower_idx',
- {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
-
- # bz_rename_column and bz_rename_table didn't correctly rename
- # the sequence.
- $self->_fix_bad_sequence('fielddefs', 'id', 'fielddefs_fieldid_seq', 'fielddefs_id_seq');
- # If the 'tags' table still exists, then bz_rename_table()
- # will fix the sequence for us.
- if (!$self->bz_table_info('tags')) {
- my $res = $self->_fix_bad_sequence('tag', 'id', 'tags_id_seq', 'tag_id_seq');
- # If $res is true, then the sequence has been renamed, meaning that
- # the primary key must be renamed too.
- if ($res) {
- $self->do('ALTER INDEX tags_pkey RENAME TO tag_pkey');
- }
+ # PostgreSQL doesn't like having *any* index on the thetext
+ # field, because it can't have index data longer than 2770
+ # characters on that field.
+ $self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
+
+ # Same for all the comments fields in the fulltext table.
+ $self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_idx');
+ $self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_noprivate_idx');
+
+ # PostgreSQL also wants an index for calling LOWER on
+ # login_name, which we do with sql_istrcmp all over the place.
+ $self->bz_add_index(
+ 'profiles',
+ 'profiles_login_name_lower_idx',
+ {FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'}
+ );
+
+ # Now that Bugzilla::Object uses sql_istrcmp, other tables
+ # also need a LOWER() index.
+ _fix_case_differences('fielddefs', 'name');
+ $self->bz_add_index('fielddefs', 'fielddefs_name_lower_idx',
+ {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
+ _fix_case_differences('keyworddefs', 'name');
+ $self->bz_add_index('keyworddefs', 'keyworddefs_name_lower_idx',
+ {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
+ _fix_case_differences('products', 'name');
+ $self->bz_add_index('products', 'products_name_lower_idx',
+ {FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
+
+ # bz_rename_column and bz_rename_table didn't correctly rename
+ # the sequence.
+ $self->_fix_bad_sequence('fielddefs', 'id', 'fielddefs_fieldid_seq',
+ 'fielddefs_id_seq');
+
+ # If the 'tags' table still exists, then bz_rename_table()
+ # will fix the sequence for us.
+ if (!$self->bz_table_info('tags')) {
+ my $res = $self->_fix_bad_sequence('tag', 'id', 'tags_id_seq', 'tag_id_seq');
+
+ # If $res is true, then the sequence has been renamed, meaning that
+ # the primary key must be renamed too.
+ if ($res) {
+ $self->do('ALTER INDEX tags_pkey RENAME TO tag_pkey');
}
-
- # Certain sequences got upgraded before we required Pg 8.3, and
- # so they were not properly associated with their columns.
- my @tables = $self->bz_table_list_real;
- foreach my $table (@tables) {
- my @columns = $self->bz_table_columns_real($table);
- foreach my $column (@columns) {
- # All our SERIAL pks have "id" in their name at the end.
- next unless $column =~ /id$/;
- my $sequence = "${table}_${column}_seq";
- if ($self->bz_sequence_exists($sequence)) {
- my $is_associated = $self->selectrow_array(
- 'SELECT pg_get_serial_sequence(?,?)',
- undef, $table, $column);
- next if $is_associated;
- print "Fixing $sequence to be associated"
- . " with $table.$column...\n";
- $self->do("ALTER SEQUENCE $sequence OWNED BY $table.$column");
- # In order to produce an exactly identical schema to what
- # a brand-new checksetup.pl run would produce, we also need
- # to re-set the default on this column.
- $self->do("ALTER TABLE $table
+ }
+
+ # Certain sequences got upgraded before we required Pg 8.3, and
+ # so they were not properly associated with their columns.
+ my @tables = $self->bz_table_list_real;
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns_real($table);
+ foreach my $column (@columns) {
+
+ # All our SERIAL pks have "id" in their name at the end.
+ next unless $column =~ /id$/;
+ my $sequence = "${table}_${column}_seq";
+ if ($self->bz_sequence_exists($sequence)) {
+ my $is_associated = $self->selectrow_array('SELECT pg_get_serial_sequence(?,?)',
+ undef, $table, $column);
+ next if $is_associated;
+ print "Fixing $sequence to be associated" . " with $table.$column...\n";
+ $self->do("ALTER SEQUENCE $sequence OWNED BY $table.$column");
+
+ # In order to produce an exactly identical schema to what
+ # a brand-new checksetup.pl run would produce, we also need
+ # to re-set the default on this column.
+ $self->do(
+ "ALTER TABLE $table
ALTER COLUMN $column
- SET DEFAULT nextval('$sequence')");
- }
- }
+ SET DEFAULT nextval('$sequence')"
+ );
+ }
}
+ }
}
sub _fix_bad_sequence {
- my ($self, $table, $column, $old_seq, $new_seq) = @_;
- if ($self->bz_column_info($table, $column)
- && $self->bz_sequence_exists($old_seq))
- {
- print "Fixing $old_seq sequence...\n";
- $self->do("ALTER SEQUENCE $old_seq RENAME TO $new_seq");
- $self->do("ALTER TABLE $table ALTER COLUMN $column
- SET DEFAULT NEXTVAL('$new_seq')");
- return 1;
- }
- return 0;
+ my ($self, $table, $column, $old_seq, $new_seq) = @_;
+ if ( $self->bz_column_info($table, $column)
+ && $self->bz_sequence_exists($old_seq))
+ {
+ print "Fixing $old_seq sequence...\n";
+ $self->do("ALTER SEQUENCE $old_seq RENAME TO $new_seq");
+ $self->do(
+ "ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT NEXTVAL('$new_seq')"
+ );
+ return 1;
+ }
+ return 0;
}
# Renames things that differ only in case.
sub _fix_case_differences {
- my ($table, $field) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $duplicates = $dbh->selectcol_arrayref(
- "SELECT DISTINCT LOWER($field) FROM $table
- GROUP BY LOWER($field) HAVING COUNT(LOWER($field)) > 1");
-
- foreach my $name (@$duplicates) {
- my $dups = $dbh->selectcol_arrayref(
- "SELECT $field FROM $table WHERE LOWER($field) = ?",
- undef, $name);
- my $primary = shift @$dups;
- foreach my $dup (@$dups) {
- my $new_name = "${dup}_";
- # Make sure the new name isn't *also* a duplicate.
- while (1) {
- last if (!$dbh->selectrow_array(
- "SELECT 1 FROM $table WHERE LOWER($field) = ?",
- undef, lc($new_name)));
- $new_name .= "_";
- }
- print "$table '$primary' and '$dup' have names that differ",
- " only in case.\nRenaming '$dup' to '$new_name'...\n";
- $dbh->do("UPDATE $table SET $field = ? WHERE $field = ?",
- undef, $new_name, $dup);
- }
+ my ($table, $field) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $duplicates = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT LOWER($field) FROM $table
+ GROUP BY LOWER($field) HAVING COUNT(LOWER($field)) > 1"
+ );
+
+ foreach my $name (@$duplicates) {
+ my $dups
+ = $dbh->selectcol_arrayref(
+ "SELECT $field FROM $table WHERE LOWER($field) = ?",
+ undef, $name);
+ my $primary = shift @$dups;
+ foreach my $dup (@$dups) {
+ my $new_name = "${dup}_";
+
+ # Make sure the new name isn't *also* a duplicate.
+ while (1) {
+ last
+ if (!$dbh->selectrow_array(
+ "SELECT 1 FROM $table WHERE LOWER($field) = ?",
+ undef, lc($new_name)
+ ));
+ $new_name .= "_";
+ }
+ print "$table '$primary' and '$dup' have names that differ",
+ " only in case.\nRenaming '$dup' to '$new_name'...\n";
+ $dbh->do("UPDATE $table SET $field = ? WHERE $field = ?",
+ undef, $new_name, $dup);
}
+ }
}
#####################################################################
@@ -348,12 +369,13 @@ sub _fix_case_differences {
# Pg includes the PostgreSQL system tables in table_list_real, so
# we need to remove those.
sub bz_table_list_real {
- my $self = shift;
+ my $self = shift;
+
+ my @full_table_list = $self->SUPER::bz_table_list_real(@_);
- my @full_table_list = $self->SUPER::bz_table_list_real(@_);
- # All PostgreSQL system tables start with "pg_" or "sql_"
- my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
- return @table_list;
+ # All PostgreSQL system tables start with "pg_" or "sql_"
+ my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
+ return @table_list;
}
1;
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index e1c19fa51..f681445b0 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -31,6 +31,7 @@ use List::MoreUtils qw(firstidx natatime);
use Try::Tiny;
use Module::Runtime qw(require_module);
use Safe;
+
# Historical, needed for SCHEMA_VERSION = '1.00'
use Storable qw(dclone freeze thaw);
@@ -199,1644 +200,1593 @@ update this column in this table."
=cut
-use constant SCHEMA_VERSION => 3;
-use constant ADD_COLUMN => 'ADD COLUMN';
+use constant SCHEMA_VERSION => 3;
+use constant ADD_COLUMN => 'ADD COLUMN';
+
# Multiple FKs can be added using ALTER TABLE ADD CONSTRAINT in one
# SQL statement. This isn't true for all databases.
use constant MULTIPLE_FKS_IN_ALTER => 1;
+
# This is a reasonable default that's true for both PostgreSQL and MySQL.
use constant MAX_IDENTIFIER_LEN => 63;
use constant FIELD_TABLE_SCHEMA => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ visibility_value_id => {TYPE => 'INT2'},
+ ],
+
+ # Note that bz_add_field_table should prepend the table name
+ # to these index names.
+ INDEXES => [
+ value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ sortkey_idx => ['sortkey', 'value'],
+ visibility_value_id_idx => ['visibility_value_id'],
+ ],
+};
+
+use constant ABSTRACT_SCHEMA => {
+
+ # BUG-RELATED TABLES
+ # ------------------
+
+ # General Bug Information
+ # -----------------------
+ bugs => {
FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- visibility_value_id => {TYPE => 'INT2'},
+ bug_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ assigned_to => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_file_loc => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"},
+ bug_severity => {TYPE => 'varchar(64)', NOTNULL => 1},
+ bug_status => {TYPE => 'varchar(64)', NOTNULL => 1},
+ creation_ts => {TYPE => 'DATETIME'},
+ delta_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
+ op_sys => {TYPE => 'varchar(64)', NOTNULL => 1},
+ priority => {TYPE => 'varchar(64)', NOTNULL => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id'}
+ },
+ rep_platform => {TYPE => 'varchar(64)', NOTNULL => 1},
+ reporter => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ version => {TYPE => 'varchar(64)', NOTNULL => 1},
+ component_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'components', COLUMN => 'id'}
+ },
+ resolution => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"},
+ target_milestone => {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"},
+ qa_contact =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}},
+ status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"},
+ lastdiffed => {TYPE => 'DATETIME'},
+ everconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1},
+ reporter_accessible => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ cclist_accessible => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ estimated_time => {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'},
+ remaining_time => {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'},
+ deadline => {TYPE => 'DATETIME'},
+ alias => {TYPE => 'varchar(40)'},
],
- # Note that bz_add_field_table should prepend the table name
- # to these index names.
INDEXES => [
- value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
- sortkey_idx => ['sortkey', 'value'],
- visibility_value_id_idx => ['visibility_value_id'],
+ bugs_alias_idx => {FIELDS => ['alias'], TYPE => 'UNIQUE'},
+ bugs_assigned_to_idx => ['assigned_to'],
+ bugs_creation_ts_idx => ['creation_ts'],
+ bugs_delta_ts_idx => ['delta_ts'],
+ bugs_bug_severity_idx => ['bug_severity'],
+ bugs_bug_status_idx => ['bug_status'],
+ bugs_op_sys_idx => ['op_sys'],
+ bugs_priority_idx => ['priority'],
+ bugs_product_id_idx => ['product_id'],
+ bugs_reporter_idx => ['reporter'],
+ bugs_version_idx => ['version'],
+ bugs_component_id_idx => ['component_id'],
+ bugs_resolution_idx => ['resolution'],
+ bugs_target_milestone_idx => ['target_milestone'],
+ bugs_qa_contact_idx => ['qa_contact'],
],
-};
+ },
-use constant ABSTRACT_SCHEMA => {
+ bugs_fulltext => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
+
+ # Comments are stored all together in one column for searching.
+ # This allows us to examine all comments together when deciding
+ # the relevance of a bug in fulltext search.
+ comments => {TYPE => 'LONGTEXT'},
+ comments_noprivate => {TYPE => 'LONGTEXT'},
+ ],
+ INDEXES => [
+ bugs_fulltext_short_desc_idx => {FIELDS => ['short_desc'], TYPE => 'FULLTEXT'},
+ bugs_fulltext_comments_idx => {FIELDS => ['comments'], TYPE => 'FULLTEXT'},
+ bugs_fulltext_comments_noprivate_idx =>
+ {FIELDS => ['comments_noprivate'], TYPE => 'FULLTEXT'},
+ ],
+ },
- # BUG-RELATED TABLES
- # ------------------
-
- # General Bug Information
- # -----------------------
- bugs => {
- FIELDS => [
- bug_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- assigned_to => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- bug_file_loc => {TYPE => 'MEDIUMTEXT',
- NOTNULL => 1, DEFAULT => "''"},
- bug_severity => {TYPE => 'varchar(64)', NOTNULL => 1},
- bug_status => {TYPE => 'varchar(64)', NOTNULL => 1},
- creation_ts => {TYPE => 'DATETIME'},
- delta_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
- op_sys => {TYPE => 'varchar(64)', NOTNULL => 1},
- priority => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id'}},
- rep_platform => {TYPE => 'varchar(64)', NOTNULL => 1},
- reporter => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- version => {TYPE => 'varchar(64)', NOTNULL => 1},
- component_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id'}},
- resolution => {TYPE => 'varchar(64)',
- NOTNULL => 1, DEFAULT => "''"},
- target_milestone => {TYPE => 'varchar(20)',
- NOTNULL => 1, DEFAULT => "'---'"},
- qa_contact => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- lastdiffed => {TYPE => 'DATETIME'},
- everconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1},
- reporter_accessible => {TYPE => 'BOOLEAN',
- NOTNULL => 1, DEFAULT => 'TRUE'},
- cclist_accessible => {TYPE => 'BOOLEAN',
- NOTNULL => 1, DEFAULT => 'TRUE'},
- estimated_time => {TYPE => 'decimal(7,2)',
- NOTNULL => 1, DEFAULT => '0'},
- remaining_time => {TYPE => 'decimal(7,2)',
- NOTNULL => 1, DEFAULT => '0'},
- deadline => {TYPE => 'DATETIME'},
- alias => {TYPE => 'varchar(40)'},
- ],
- INDEXES => [
- bugs_alias_idx => {FIELDS => ['alias'],
- TYPE => 'UNIQUE'},
- bugs_assigned_to_idx => ['assigned_to'],
- bugs_creation_ts_idx => ['creation_ts'],
- bugs_delta_ts_idx => ['delta_ts'],
- bugs_bug_severity_idx => ['bug_severity'],
- bugs_bug_status_idx => ['bug_status'],
- bugs_op_sys_idx => ['op_sys'],
- bugs_priority_idx => ['priority'],
- bugs_product_id_idx => ['product_id'],
- bugs_reporter_idx => ['reporter'],
- bugs_version_idx => ['version'],
- bugs_component_id_idx => ['component_id'],
- bugs_resolution_idx => ['resolution'],
- bugs_target_milestone_idx => ['target_milestone'],
- bugs_qa_contact_idx => ['qa_contact'],
- ],
- },
-
- bugs_fulltext => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1, PRIMARYKEY => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
- # Comments are stored all together in one column for searching.
- # This allows us to examine all comments together when deciding
- # the relevance of a bug in fulltext search.
- comments => {TYPE => 'LONGTEXT'},
- comments_noprivate => {TYPE => 'LONGTEXT'},
- ],
- INDEXES => [
- bugs_fulltext_short_desc_idx => {FIELDS => ['short_desc'],
- TYPE => 'FULLTEXT'},
- bugs_fulltext_comments_idx => {FIELDS => ['comments'],
- TYPE => 'FULLTEXT'},
- bugs_fulltext_comments_noprivate_idx => {
- FIELDS => ['comments_noprivate'], TYPE => 'FULLTEXT'},
- ],
- },
-
- bugs_activity => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- attach_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'attachments',
- COLUMN => 'attach_id',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- fieldid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- added => {TYPE => 'varchar(255)'},
- removed => {TYPE => 'varchar(255)'},
- comment_id => {TYPE => 'INT4',
- REFERENCES => { TABLE => 'longdescs',
- COLUMN => 'comment_id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- bugs_activity_bug_id_idx => ['bug_id'],
- bugs_activity_who_idx => ['who'],
- bugs_activity_bug_when_idx => ['bug_when'],
- bugs_activity_fieldid_idx => ['fieldid'],
- bugs_activity_added_idx => ['added'],
- bugs_activity_removed_idx => ['removed'],
- ],
- },
-
- cc => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- cc_bug_id_idx => {FIELDS => [qw(bug_id who)],
- TYPE => 'UNIQUE'},
- cc_who_idx => ['who'],
- ],
- },
-
- longdescs => {
- FIELDS => [
- comment_id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- work_time => {TYPE => 'decimal(7,2)', NOTNULL => 1,
- DEFAULT => '0'},
- thetext => {TYPE => 'LONGTEXT', NOTNULL => 1},
- isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- already_wrapped => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- type => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- extra_data => {TYPE => 'varchar(255)'},
- is_markdown => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}
- ],
- INDEXES => [
- longdescs_bug_id_idx => ['bug_id'],
- longdescs_who_idx => [qw(who bug_id)],
- longdescs_bug_when_idx => ['bug_when'],
- ],
- },
-
- longdescs_tags => {
- FIELDS => [
- id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- comment_id => { TYPE => 'INT4',
- REFERENCES => { TABLE => 'longdescs',
- COLUMN => 'comment_id',
- DELETE => 'CASCADE' }},
- tag => { TYPE => 'varchar(24)', NOTNULL => 1 },
- ],
- INDEXES => [
- longdescs_tags_idx => { FIELDS => ['comment_id', 'tag'], TYPE => 'UNIQUE' },
- ],
- },
-
- longdescs_tags_weights => {
- FIELDS => [
- id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- tag => { TYPE => 'varchar(24)', NOTNULL => 1 },
- weight => { TYPE => 'INT3', NOTNULL => 1 },
- ],
- INDEXES => [
- longdescs_tags_weights_tag_idx => { FIELDS => ['tag'], TYPE => 'UNIQUE' },
- ],
- },
-
- longdescs_tags_activity => {
- FIELDS => [
- id => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- bug_id => { TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => { TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE' }},
- comment_id => { TYPE => 'INT4',
- REFERENCES => { TABLE => 'longdescs',
- COLUMN => 'comment_id',
- DELETE => 'CASCADE' }},
- who => { TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => { TABLE => 'profiles',
- COLUMN => 'userid' }},
- bug_when => { TYPE => 'DATETIME', NOTNULL => 1 },
- added => { TYPE => 'varchar(24)' },
- removed => { TYPE => 'varchar(24)' },
- ],
- INDEXES => [
- longdescs_tags_activity_bug_id_idx => ['bug_id'],
- ],
- },
-
- dependencies => {
- FIELDS => [
- blocked => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- dependson => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- dependencies_blocked_idx => {FIELDS => [qw(blocked dependson)],
- TYPE => 'UNIQUE'},
- dependencies_dependson_idx => ['dependson'],
- ],
- },
-
- attachments => {
- FIELDS => [
- attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- modification_time => {TYPE => 'DATETIME', NOTNULL => 1},
- description => {TYPE => 'TINYTEXT', NOTNULL => 1},
- mimetype => {TYPE => 'TINYTEXT', NOTNULL => 1},
- ispatch => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- filename => {TYPE => 'varchar(100)', NOTNULL => 1},
- submitter_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- isobsolete => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- attach_size => {TYPE => 'INT4', NOTNULL => 1,
- DEFAULT => 0},
- ],
- INDEXES => [
- attachments_bug_id_idx => ['bug_id'],
- attachments_creation_ts_idx => ['creation_ts'],
- attachments_modification_time_idx => ['modification_time'],
- attachments_submitter_id_idx => ['submitter_id', 'bug_id'],
- attachments_ispatch_idx => ['ispatch'],
- ],
- },
- attach_data => {
- FIELDS => [
- id => {TYPE => 'INT3', NOTNULL => 1,
- PRIMARYKEY => 1,
- REFERENCES => {TABLE => 'attachments',
- COLUMN => 'attach_id',
- DELETE => 'CASCADE'}},
- thedata => {TYPE => 'LONGBLOB', NOTNULL => 1},
- ],
- },
-
- duplicates => {
- FIELDS => [
- dupe_of => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- dupe => {TYPE => 'INT3', NOTNULL => 1,
- PRIMARYKEY => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- ],
- },
-
- bug_see_also => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- value => {TYPE => 'varchar(255)', NOTNULL => 1},
- class => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
- ],
- INDEXES => [
- bug_see_also_bug_id_idx => {FIELDS => [qw(bug_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # Auditing
- # --------
-
- audit_log => {
- FIELDS => [
- user_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- class => {TYPE => 'varchar(255)', NOTNULL => 1},
- object_id => {TYPE => 'INT4', NOTNULL => 1},
- field => {TYPE => 'varchar(64)', NOTNULL => 1},
- removed => {TYPE => 'MEDIUMTEXT'},
- added => {TYPE => 'MEDIUMTEXT'},
- at_time => {TYPE => 'DATETIME', NOTNULL => 1},
- ],
- INDEXES => [
- audit_log_class_idx => ['class', 'at_time'],
- ],
- },
-
- # Keywords
- # --------
-
- keyworddefs => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- is_active => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- keyworddefs_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- keywords => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- keywordid => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'keyworddefs',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
-
- ],
- INDEXES => [
- keywords_bug_id_idx => {FIELDS => [qw(bug_id keywordid)],
- TYPE => 'UNIQUE'},
- keywords_keywordid_idx => ['keywordid'],
- ],
- },
-
- # Flags
- # -----
-
- # "flags" stores one record for each flag on each bug/attachment.
- flags => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- type_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'flagtypes',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- status => {TYPE => 'char(1)', NOTNULL => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- attach_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'attachments',
- COLUMN => 'attach_id',
- DELETE => 'CASCADE'}},
- creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
- modification_date => {TYPE => 'DATETIME'},
- setter_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- requestee_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- ],
- INDEXES => [
- flags_bug_id_idx => [qw(bug_id attach_id)],
- flags_setter_id_idx => ['setter_id'],
- flags_requestee_id_idx => ['requestee_id'],
- flags_type_id_idx => ['type_id'],
- ],
- },
-
- # "flagtypes" defines the types of flags that can be set.
- flagtypes => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(50)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- cc_list => {TYPE => 'varchar(200)'},
- target_type => {TYPE => 'char(1)', NOTNULL => 1,
- DEFAULT => "'b'"},
- is_active => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- is_requestable => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_requesteeble => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_multiplicable => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- grant_group_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'SET NULL'}},
- request_group_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'SET NULL'}},
- ],
- },
-
- # "flaginclusions" and "flagexclusions" specify the products/components
- # a bug/attachment must belong to in order for flags of a given type
- # to be set for them.
- flaginclusions => {
- FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'flagtypes',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- product_id => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- flaginclusions_type_id_idx => { FIELDS => [qw(type_id product_id component_id)],
- TYPE => 'UNIQUE' },
- ],
- },
-
- flagexclusions => {
- FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'flagtypes',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- product_id => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- flagexclusions_type_id_idx => { FIELDS => [qw(type_id product_id component_id)],
- TYPE => 'UNIQUE' },
- ],
- },
-
- # General Field Information
- # -------------------------
-
- fielddefs => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- type => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => FIELD_TYPE_UNKNOWN},
- custom => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- description => {TYPE => 'TINYTEXT', NOTNULL => 1},
- mailhead => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1},
- obsolete => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- enter_bug => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- buglist => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- visibility_field_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- value_field_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- reverse_desc => {TYPE => 'TINYTEXT'},
- is_mandatory => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_numeric => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- fielddefs_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- fielddefs_sortkey_idx => ['sortkey'],
- fielddefs_value_field_id_idx => ['value_field_id'],
- fielddefs_is_mandatory_idx => ['is_mandatory'],
- ],
- },
-
- # Field Visibility Information
- # -------------------------
-
- field_visibility => {
- FIELDS => [
- field_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- value_id => {TYPE => 'INT2', NOTNULL => 1}
- ],
- INDEXES => [
- field_visibility_field_id_idx => {
- FIELDS => [qw(field_id value_id)],
- TYPE => 'UNIQUE'
- },
- ],
- },
-
- # Per-product Field Values
- # ------------------------
-
- versions => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- versions_product_id_idx => {FIELDS => [qw(product_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- milestones => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- value => {TYPE => 'varchar(20)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- milestones_product_id_idx => {FIELDS => [qw(product_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # Global Field Values
- # -------------------
-
- bug_status => {
- FIELDS => [
- @{ dclone(FIELD_TABLE_SCHEMA->{FIELDS}) },
- is_open => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
-
- ],
- INDEXES => [
- bug_status_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- bug_status_sortkey_idx => ['sortkey', 'value'],
- bug_status_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- resolution => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- resolution_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- resolution_sortkey_idx => ['sortkey', 'value'],
- resolution_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- bug_severity => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- bug_severity_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- bug_severity_sortkey_idx => ['sortkey', 'value'],
- bug_severity_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- priority => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- priority_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- priority_sortkey_idx => ['sortkey', 'value'],
- priority_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- rep_platform => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- rep_platform_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- rep_platform_sortkey_idx => ['sortkey', 'value'],
- rep_platform_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- op_sys => {
- FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
- INDEXES => [
- op_sys_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- op_sys_sortkey_idx => ['sortkey', 'value'],
- op_sys_visibility_value_id_idx => ['visibility_value_id'],
- ],
- },
-
- status_workflow => {
- FIELDS => [
- # On bug creation, there is no old value.
- old_status => {TYPE => 'INT2',
- REFERENCES => {TABLE => 'bug_status',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- new_status => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'bug_status',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- require_comment => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0},
- ],
- INDEXES => [
- status_workflow_idx => {FIELDS => ['old_status', 'new_status'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # USER INFO
- # ---------
-
- # General User Information
- # ------------------------
-
- profiles => {
- FIELDS => [
- userid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- login_name => {TYPE => 'varchar(255)', NOTNULL => 1},
- cryptpassword => {TYPE => 'varchar(128)'},
- realname => {TYPE => 'varchar(255)', NOTNULL => 1,
- DEFAULT => "''"},
- nickname => {TYPE => 'varchar(255)', NOTNULL => 1,
- DEFAULT => "''"},
- disabledtext => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- disable_mail => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- mybugslink => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- extern_id => {TYPE => 'varchar(64)'},
- is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- last_seen_date => {TYPE => 'DATETIME'},
- password_change_required => { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' },
- password_change_reason => { TYPE => 'varchar(64)' },
- mfa => {TYPE => 'varchar(8)', DEFAULT => "''" },
- mfa_required_date => {TYPE => 'DATETIME'},
- ],
- INDEXES => [
- profiles_login_name_idx => {FIELDS => ['login_name'],
- TYPE => 'UNIQUE'},
- profiles_extern_id_idx => {FIELDS => ['extern_id'],
- TYPE => 'UNIQUE'},
- profiles_nickname_idx => ['nickname'],
- profiles_realname_ft_idx => {FIELDS => ['realname'],
- TYPE => 'FULLTEXT'},
- ],
- },
-
- profile_search => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_list => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- list_order => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- profile_search_user_id_idx => [qw(user_id)],
- ],
- },
-
- profiles_activity => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- profiles_when => {TYPE => 'DATETIME', NOTNULL => 1},
- fieldid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- oldvalue => {TYPE => 'TINYTEXT'},
- newvalue => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- profiles_activity_userid_idx => ['userid'],
- profiles_activity_profiles_when_idx => ['profiles_when'],
- profiles_activity_fieldid_idx => ['fieldid'],
- ],
- },
-
- profile_mfa => {
- FIELDS => [
- id => { TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- user_id => { TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => { TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE' } },
- name => { TYPE => 'varchar(16)', NOTNULL => 1 },
- value => { TYPE => 'varchar(255)' },
- ],
- INDEXES => [
- profile_mfa_userid_name_idx => { FIELDS => [ 'user_id', 'name' ], TYPE => 'UNIQUE' },
- ],
- },
-
- email_setting => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- relationship => {TYPE => 'INT1', NOTNULL => 1},
- event => {TYPE => 'INT1', NOTNULL => 1},
- ],
- INDEXES => [
- email_setting_user_id_idx =>
- {FIELDS => [qw(user_id relationship event)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- email_bug_ignore => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- email_bug_ignore_user_id_idx => {FIELDS => [qw(user_id bug_id)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- watch => {
- FIELDS => [
- watcher => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- watched => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- watch_watcher_idx => {FIELDS => [qw(watcher watched)],
- TYPE => 'UNIQUE'},
- watch_watched_idx => ['watched'],
- ],
- },
-
- namedqueries => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- query => {TYPE => 'LONGTEXT', NOTNULL => 1},
- ],
- INDEXES => [
- namedqueries_userid_idx => {FIELDS => [qw(userid name)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- namedqueries_link_in_footer => {
- FIELDS => [
- namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'namedqueries',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- namedqueries_link_in_footer_id_idx => {FIELDS => [qw(namedquery_id user_id)],
- TYPE => 'UNIQUE'},
- namedqueries_link_in_footer_userid_idx => ['user_id'],
- ],
- },
-
- tag => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- tag_user_id_idx => {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'},
- ],
- },
-
- bug_tag => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- tag_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'tag',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- bug_tag_bug_id_idx => {FIELDS => [qw(bug_id tag_id)], TYPE => 'UNIQUE'},
- ],
- },
-
- component_cc => {
-
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'components',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- component_cc_user_id_idx => {FIELDS => [qw(component_id user_id)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # Authentication
- # --------------
-
- logincookies => {
- FIELDS => [
- cookie => {TYPE => 'varchar(22)', NOTNULL => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ipaddr => {TYPE => 'varchar(40)'},
- lastused => {TYPE => 'DATETIME', NOTNULL => 1},
- id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
- restrict_ipaddr => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0},
- ],
- INDEXES => [
- logincookies_lastused_idx => ['lastused'],
- logincookies_cookie_idx => {FIELDS => ['cookie'], TYPE => 'UNIQUE'},
- ],
- },
-
- login_failure => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- login_time => {TYPE => 'DATETIME', NOTNULL => 1},
- ip_addr => {TYPE => 'varchar(40)', NOTNULL => 1},
- ],
- INDEXES => [
- # We do lookups by every item in the table simultaneously, but
- # having an index with all three items would be the same size as
- # the table. So instead we have an index on just the smallest item,
- # to speed lookups.
- login_failure_user_id_idx => ['user_id'],
- ],
- },
-
-
- # "tokens" stores the tokens users receive when a password or email
- # change is requested. Tokens provide an extra measure of security
- # for these changes.
- tokens => {
- FIELDS => [
- userid => {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- issuedate => {TYPE => 'DATETIME', NOTNULL => 1} ,
- token => {TYPE => 'varchar(22)', NOTNULL => 1,
- PRIMARYKEY => 1},
- tokentype => {TYPE => 'varchar(16)', NOTNULL => 1} ,
- eventdata => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- tokens_userid_idx => ['userid'],
- ],
- },
-
- token_data => {
- FIELDS => [
- id => { TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1 },
- token => { TYPE => 'varchar(22)', NOTNULL => 1,
- REFERENCES => { TABLE => 'tokens', COLUMN => 'token', DELETE => 'CASCADE' }},
- extra_data => { TYPE => 'MEDIUMTEXT', NOTNULL => 1 },
- ],
- INDEXES => [
- token_data_idx => { FIELDS => ['token'], TYPE => 'UNIQUE' },
- ],
- },
-
- # GROUPS
- # ------
-
- groups => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(255)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- isbuggroup => {TYPE => 'BOOLEAN', NOTNULL => 1},
- userregexp => {TYPE => 'TINYTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- icon_url => {TYPE => 'TINYTEXT'},
- owner_user_id => {TYPE => 'INT3',
- REFERENCES => {
- TABLE => 'profiles',
- COLUMN => 'userid'}},
- idle_member_removal => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}
- ],
- INDEXES => [
- groups_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},
- ],
- },
-
- group_control_map => {
- FIELDS => [
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- entry => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- membercontrol => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => CONTROLMAPNA},
- othercontrol => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => CONTROLMAPNA},
- canedit => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- editbugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- canconfirm => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- group_control_map_product_id_idx =>
- {FIELDS => [qw(product_id group_id)], TYPE => 'UNIQUE'},
- group_control_map_group_id_idx => ['group_id'],
- ],
- },
-
- # "user_group_map" determines the groups that a user belongs to
- # directly or due to regexp and which groups can be blessed by a user.
- #
- # grant_type:
- # if GRANT_DIRECT - record was explicitly granted
- # if GRANT_DERIVED - record was derived from expanding a group hierarchy
- # if GRANT_REGEXP - record was created by evaluating a regexp
- user_group_map => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- isbless => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- grant_type => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => GRANT_DIRECT},
- ],
- INDEXES => [
- user_group_map_user_id_idx =>
- {FIELDS => [qw(user_id group_id grant_type isbless)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # This table determines which groups are made a member of another
- # group, given the ability to bless another group, or given
- # visibility to another groups existence and membership
- # grant_type:
- # if GROUP_MEMBERSHIP - member groups are made members of grantor
- # if GROUP_BLESS - member groups may grant membership in grantor
- # if GROUP_VISIBLE - member groups may see grantor group
- group_group_map => {
- FIELDS => [
- member_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- grantor_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- grant_type => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => GROUP_MEMBERSHIP},
- ],
- INDEXES => [
- group_group_map_member_id_idx =>
- {FIELDS => [qw(member_id grantor_id grant_type)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # This table determines which groups a user must be a member of
- # in order to see a bug.
- bug_group_map => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- bug_group_map_bug_id_idx =>
- {FIELDS => [qw(bug_id group_id)], TYPE => 'UNIQUE'},
- bug_group_map_group_id_idx => ['group_id'],
- ],
- },
-
- # This table determines which groups a user must be a member of
- # in order to see a named query somebody else shares.
- namedquery_group_map => {
- FIELDS => [
- namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'namedqueries',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- namedquery_group_map_namedquery_id_idx =>
- {FIELDS => [qw(namedquery_id)], TYPE => 'UNIQUE'},
- namedquery_group_map_group_id_idx => ['group_id'],
- ],
- },
-
- category_group_map => {
- FIELDS => [
- category_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'series_categories',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- group_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'groups',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- category_group_map_category_id_idx =>
- {FIELDS => [qw(category_id group_id)], TYPE => 'UNIQUE'},
- ],
- },
-
-
- # PRODUCTS
- # --------
-
- classifications => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
- ],
- INDEXES => [
- classifications_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- products => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- classification_id => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '1',
- REFERENCES => {TABLE => 'classifications',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 1},
- defaultmilestone => {TYPE => 'varchar(20)',
- NOTNULL => 1, DEFAULT => "'---'"},
- allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- products_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- components => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'products',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- initialowner => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- initialqacontact => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- triage_owner_id => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- ],
- INDEXES => [
- components_product_id_idx => {FIELDS => [qw(product_id name)],
- TYPE => 'UNIQUE'},
- components_name_idx => ['name'],
- ],
- },
-
- # CHARTS
- # ------
-
- series => {
- FIELDS => [
- series_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- creator => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- category => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'series_categories',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- subcategory => {TYPE => 'INT2', NOTNULL => 1,
- REFERENCES => {TABLE => 'series_categories',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- frequency => {TYPE => 'INT2', NOTNULL => 1},
- query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- is_public => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- series_creator_idx => ['creator'],
- series_category_idx => {FIELDS => [qw(category subcategory name)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- series_data => {
- FIELDS => [
- series_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'series',
- COLUMN => 'series_id',
- DELETE => 'CASCADE'}},
- series_date => {TYPE => 'DATETIME', NOTNULL => 1},
- series_value => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- series_data_series_id_idx =>
- {FIELDS => [qw(series_id series_date)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- series_categories => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- ],
- INDEXES => [
- series_categories_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- # WHINE SYSTEM
- # ------------
-
- whine_queries => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- eventid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'whine_events',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- query_name => {TYPE => 'varchar(64)', NOTNULL => 1,
- DEFAULT => "''"},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- onemailperbug => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- title => {TYPE => 'varchar(128)', NOTNULL => 1,
- DEFAULT => "''"},
- ],
- INDEXES => [
- whine_queries_eventid_idx => ['eventid'],
- ],
- },
-
- whine_schedules => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- eventid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'whine_events',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- run_day => {TYPE => 'varchar(32)'},
- run_time => {TYPE => 'varchar(32)'},
- run_next => {TYPE => 'DATETIME'},
- mailto => {TYPE => 'INT3', NOTNULL => 1},
- mailto_type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
- ],
- INDEXES => [
- whine_schedules_run_next_idx => ['run_next'],
- whine_schedules_eventid_idx => ['eventid'],
- ],
- },
-
- whine_events => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- owner_userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- subject => {TYPE => 'varchar(128)'},
- body => {TYPE => 'MEDIUMTEXT'},
- mailifnobugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- },
-
- # QUIPS
- # -----
-
- quips => {
- FIELDS => [
- quipid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- quip => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- approved => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- },
-
- # SETTINGS
- # --------
- # setting - each global setting will have exactly one entry
- # in this table.
- # setting_value - stores the list of acceptable values for each
- # setting, and a sort index that controls the order
- # in which the values are displayed.
- # profile_setting - If a user has chosen to use a value other than the
- # global default for a given setting, it will be
- # stored in this table. Note: even if a setting is
- # later changed so is_enabled = false, the stored
- # value will remain in case it is ever enabled again.
- #
- setting => {
- FIELDS => [
- name => {TYPE => 'varchar(32)', NOTNULL => 1,
- PRIMARYKEY => 1},
- default_value => {TYPE => 'varchar(32)', NOTNULL => 1},
- is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- subclass => {TYPE => 'varchar(32)'},
- category => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'General'"}
- ],
- },
-
- setting_value => {
- FIELDS => [
- name => {TYPE => 'varchar(32)', NOTNULL => 1,
- REFERENCES => {TABLE => 'setting',
- COLUMN => 'name',
- DELETE => 'CASCADE'}},
- value => {TYPE => 'varchar(32)', NOTNULL => 1},
- sortindex => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- setting_value_nv_unique_idx => {FIELDS => [qw(name value)],
- TYPE => 'UNIQUE'},
- setting_value_ns_unique_idx => {FIELDS => [qw(name sortindex)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- profile_setting => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- setting_name => {TYPE => 'varchar(32)', NOTNULL => 1,
- REFERENCES => {TABLE => 'setting',
- COLUMN => 'name',
- DELETE => 'CASCADE'}},
- setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
- ],
- INDEXES => [
- profile_setting_value_unique_idx => {FIELDS => [qw(user_id setting_name)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- email_rates => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- recipient => {TYPE => 'varchar(255)', NOTNULL => 1},
- message_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- ],
- INDEXES => [
- email_rates_idx => [qw(recipient message_ts)],
- email_rates_message_ts_idx => ['message_ts'],
- ],
- },
-
- # THESCHWARTZ TABLES
- # ------------------
- # Note: In the standard TheSchwartz schema, most integers are unsigned,
- # but we didn't implement unsigned ints for Bugzilla schemas, so we
- # just create signed ints, which should be fine.
-
- ts_funcmap => {
- FIELDS => [
- funcid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
- funcname => {TYPE => 'varchar(255)', NOTNULL => 1},
- ],
- INDEXES => [
- ts_funcmap_funcname_idx => {FIELDS => ['funcname'],
- TYPE => 'UNIQUE'},
- ],
- },
-
- ts_job => {
- FIELDS => [
- # In a standard TheSchwartz schema, this is a BIGINT, but we
- # don't have those and I didn't want to add them just for this.
- jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- funcid => {TYPE => 'INT4', NOTNULL => 1},
- # In standard TheSchwartz, this is a MEDIUMBLOB.
- arg => {TYPE => 'LONGBLOB'},
- uniqkey => {TYPE => 'varchar(255)'},
- insert_time => {TYPE => 'INT4'},
- run_after => {TYPE => 'INT4', NOTNULL => 1},
- grabbed_until => {TYPE => 'INT4', NOTNULL => 1},
- priority => {TYPE => 'INT2'},
- coalesce => {TYPE => 'varchar(255)'},
- ],
- INDEXES => [
- ts_job_funcid_idx => {FIELDS => [qw(funcid uniqkey)],
- TYPE => 'UNIQUE'},
- # In a standard TheSchewartz schema, these both go in the other
- # direction, but there's no reason to have three indexes that
- # all start with the same column, and our naming scheme doesn't
- # allow it anyhow.
- ts_job_run_after_idx => [qw(run_after funcid)],
- ts_job_coalesce_idx => [qw(coalesce funcid)],
- ],
- },
-
- ts_note => {
- FIELDS => [
- # This is a BIGINT in standard TheSchwartz schemas.
- jobid => {TYPE => 'INT4', NOTNULL => 1},
- notekey => {TYPE => 'varchar(255)'},
- value => {TYPE => 'LONGBLOB'},
- ],
- INDEXES => [
- ts_note_jobid_idx => {FIELDS => [qw(jobid notekey)],
- TYPE => 'UNIQUE'},
- ],
- },
-
- ts_error => {
- FIELDS => [
- error_time => {TYPE => 'INT4', NOTNULL => 1},
- jobid => {TYPE => 'INT4', NOTNULL => 1},
- message => {TYPE => 'varchar(255)', NOTNULL => 1},
- funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
- ],
- INDEXES => [
- ts_error_funcid_idx => [qw(funcid error_time)],
- ts_error_error_time_idx => ['error_time'],
- ts_error_jobid_idx => ['jobid'],
- ],
- },
-
- ts_exitstatus => {
- FIELDS => [
- jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
- status => {TYPE => 'INT2'},
- completion_time => {TYPE => 'INT4'},
- delete_after => {TYPE => 'INT4'},
- ],
- INDEXES => [
- ts_exitstatus_funcid_idx => ['funcid'],
- ts_exitstatus_delete_after_idx => ['delete_after'],
- ],
- },
-
- # SCHEMA STORAGE
- # --------------
-
- bz_schema => {
- FIELDS => [
- schema_data => {TYPE => 'LONGBLOB', NOTNULL => 1},
- version => {TYPE => 'decimal(3,2)', NOTNULL => 1},
- ],
- },
-
- bug_user_last_visit => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- last_visit_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- ],
- INDEXES => [
- bug_user_last_visit_idx => {FIELDS => ['user_id', 'bug_id'],
- TYPE => 'UNIQUE'},
- bug_user_last_visit_last_visit_ts_idx => ['last_visit_ts'],
- ],
- },
-
- user_api_keys => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- api_key => {TYPE => 'varchar(40)', NOTNULL => 1},
- description => {TYPE => 'varchar(255)'},
- revoked => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- last_used => {TYPE => 'DATETIME'},
- last_used_ip => {TYPE => 'varchar(40)'},
- app_id => {TYPE => 'varchar(64)'},
- ],
- INDEXES => [
- user_api_keys_api_key_idx => {FIELDS => ['api_key'], TYPE => 'UNIQUE'},
- user_api_keys_user_id_idx => ['user_id'],
- user_api_keys_user_id_app_id_idx => ['user_id', 'app_id'],
- ],
- },
-
- user_request_log => {
- FIELDS => [
- id => {TYPE => 'INTSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- user_id => {TYPE => 'INT3', NOTNULL => 1 },
- ip_address => {TYPE => 'varchar(40)', NOTNULL => 1},
- user_agent => {TYPE => 'TINYTEXT', NOTNULL => 1},
- timestamp => {TYPE => 'DATETIME', NOTNULL => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 0},
- attach_id => {TYPE => 'INT4', NOTNULL => 0},
- request_url => {TYPE => 'TINYTEXT', NOTNULL => 1},
- method => {TYPE => 'TINYTEXT', NOTNULL => 1},
- action => {TYPE => 'varchar(20)', NOTNULL => 1},
- server => {TYPE => 'varchar(7)', NOTNULL => 1},
- ],
- INDEXES => [
- user_user_request_log_user_id_idx => ['user_id'],
- ],
- },
-};
+ bugs_activity => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ attach_id => {
+ TYPE => 'INT3',
+ REFERENCES =>
+ {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ fieldid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}
+ },
+ added => {TYPE => 'varchar(255)'},
+ removed => {TYPE => 'varchar(255)'},
+ comment_id => {
+ TYPE => 'INT4',
+ REFERENCES =>
+ {TABLE => 'longdescs', COLUMN => 'comment_id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ bugs_activity_bug_id_idx => ['bug_id'],
+ bugs_activity_who_idx => ['who'],
+ bugs_activity_bug_when_idx => ['bug_when'],
+ bugs_activity_fieldid_idx => ['fieldid'],
+ bugs_activity_added_idx => ['added'],
+ bugs_activity_removed_idx => ['removed'],
+ ],
+ },
-# Foreign Keys are added in Bugzilla::DB::bz_add_field_tables
-use constant MULTI_SELECT_VALUE_TABLE => {
+ cc => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ cc_bug_id_idx => {FIELDS => [qw(bug_id who)], TYPE => 'UNIQUE'},
+ cc_who_idx => ['who'],
+ ],
+ },
+
+ longdescs => {
+ FIELDS => [
+ comment_id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ work_time => {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'},
+ thetext => {TYPE => 'LONGTEXT', NOTNULL => 1},
+ isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ already_wrapped => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ extra_data => {TYPE => 'varchar(255)'},
+ is_markdown => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}
+ ],
+ INDEXES => [
+ longdescs_bug_id_idx => ['bug_id'],
+ longdescs_who_idx => [qw(who bug_id)],
+ longdescs_bug_when_idx => ['bug_when'],
+ ],
+ },
+
+ longdescs_tags => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ comment_id => {
+ TYPE => 'INT4',
+ REFERENCES =>
+ {TABLE => 'longdescs', COLUMN => 'comment_id', DELETE => 'CASCADE'}
+ },
+ tag => {TYPE => 'varchar(24)', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [longdescs_tags_idx => {FIELDS => ['comment_id', 'tag'], TYPE => 'UNIQUE'},],
+ },
+
+ longdescs_tags_weights => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ tag => {TYPE => 'varchar(24)', NOTNULL => 1},
+ weight => {TYPE => 'INT3', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [longdescs_tags_weights_tag_idx => {FIELDS => ['tag'], TYPE => 'UNIQUE'},],
+ },
+
+ longdescs_tags_activity => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ comment_id => {
+ TYPE => 'INT4',
+ REFERENCES =>
+ {TABLE => 'longdescs', COLUMN => 'comment_id', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ added => {TYPE => 'varchar(24)'},
+ removed => {TYPE => 'varchar(24)'},
+ ],
+ INDEXES => [longdescs_tags_activity_bug_id_idx => ['bug_id'],],
+ },
+
+ dependencies => {
+ FIELDS => [
+ blocked => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ dependson => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ dependencies_blocked_idx =>
+ {FIELDS => [qw(blocked dependson)], TYPE => 'UNIQUE'},
+ dependencies_dependson_idx => ['dependson'],
+ ],
+ },
+
+ attachments => {
+ FIELDS => [
+ attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ modification_time => {TYPE => 'DATETIME', NOTNULL => 1},
+ description => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ mimetype => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ ispatch => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ filename => {TYPE => 'varchar(100)', NOTNULL => 1},
+ submitter_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ isobsolete => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ attach_size => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
+ ],
+ INDEXES => [
+ attachments_bug_id_idx => ['bug_id'],
+ attachments_creation_ts_idx => ['creation_ts'],
+ attachments_modification_time_idx => ['modification_time'],
+ attachments_submitter_id_idx => ['submitter_id', 'bug_id'],
+ attachments_ispatch_idx => ['ispatch'],
+ ],
+ },
+ attach_data => {
+ FIELDS => [
+ id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES =>
+ {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
+ },
+ thedata => {TYPE => 'LONGBLOB', NOTNULL => 1},
+ ],
+ },
+
+ duplicates => {
+ FIELDS => [
+ dupe_of => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ dupe => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ ],
+ },
+
+ bug_see_also => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ value => {TYPE => 'varchar(255)', NOTNULL => 1},
+ class => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ ],
+ INDEXES => [
+ bug_see_also_bug_id_idx => {FIELDS => [qw(bug_id value)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Auditing
+ # --------
+
+ audit_log => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'SET NULL'}
+ },
+ class => {TYPE => 'varchar(255)', NOTNULL => 1},
+ object_id => {TYPE => 'INT4', NOTNULL => 1},
+ field => {TYPE => 'varchar(64)', NOTNULL => 1},
+ removed => {TYPE => 'MEDIUMTEXT'},
+ added => {TYPE => 'MEDIUMTEXT'},
+ at_time => {TYPE => 'DATETIME', NOTNULL => 1},
+ ],
+ INDEXES => [audit_log_class_idx => ['class', 'at_time'],],
+ },
+
+ # Keywords
+ # --------
+
+ keyworddefs => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ is_active => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [keyworddefs_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ keywords => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ keywordid => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'keyworddefs', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+
+ ],
+ INDEXES => [
+ keywords_bug_id_idx => {FIELDS => [qw(bug_id keywordid)], TYPE => 'UNIQUE'},
+ keywords_keywordid_idx => ['keywordid'],
+ ],
+ },
+
+ # Flags
+ # -----
+
+ # "flags" stores one record for each flag on each bug/attachment.
+ flags => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ type_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ status => {TYPE => 'char(1)', NOTNULL => 1},
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ attach_id => {
+ TYPE => 'INT3',
+ REFERENCES =>
+ {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'}
+ },
+ creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
+ modification_date => {TYPE => 'DATETIME'},
+ setter_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ requestee_id =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}},
+ ],
+ INDEXES => [
+ flags_bug_id_idx => [qw(bug_id attach_id)],
+ flags_setter_id_idx => ['setter_id'],
+ flags_requestee_id_idx => ['requestee_id'],
+ flags_type_id_idx => ['type_id'],
+ ],
+ },
+
+ # "flagtypes" defines the types of flags that can be set.
+ flagtypes => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(50)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ cc_list => {TYPE => 'varchar(200)'},
+ target_type => {TYPE => 'char(1)', NOTNULL => 1, DEFAULT => "'b'"},
+ is_active => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ is_requestable => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ is_requesteeble => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ is_multiplicable => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ grant_group_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'SET NULL'}
+ },
+ request_group_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'SET NULL'}
+ },
+ ],
+ },
+
+ # "flaginclusions" and "flagexclusions" specify the products/components
+ # a bug/attachment must belong to in order for flags of a given type
+ # to be set for them.
+ flaginclusions => {
+ FIELDS => [
+ type_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ product_id => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ component_id => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'components', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ flaginclusions_type_id_idx =>
+ {FIELDS => [qw(type_id product_id component_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ flagexclusions => {
+ FIELDS => [
+ type_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ product_id => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ component_id => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'components', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ flagexclusions_type_id_idx =>
+ {FIELDS => [qw(type_id product_id component_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # General Field Information
+ # -------------------------
+
+ fielddefs => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => FIELD_TYPE_UNKNOWN},
+ custom => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ description => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ mailhead => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1},
+ obsolete => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ enter_bug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ buglist => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ visibility_field_id =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
+ value_field_id =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
+ reverse_desc => {TYPE => 'TINYTEXT'},
+ is_mandatory => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ is_numeric => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ INDEXES => [
+ fielddefs_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},
+ fielddefs_sortkey_idx => ['sortkey'],
+ fielddefs_value_field_id_idx => ['value_field_id'],
+ fielddefs_is_mandatory_idx => ['is_mandatory'],
+ ],
+ },
+
+ # Field Visibility Information
+ # -------------------------
+
+ field_visibility => {
+ FIELDS => [
+ field_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ value_id => {TYPE => 'INT2', NOTNULL => 1}
+ ],
+ INDEXES => [
+ field_visibility_field_id_idx =>
+ {FIELDS => [qw(field_id value_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Per-product Field Values
+ # ------------------------
+
+ versions => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [
+ versions_product_id_idx => {FIELDS => [qw(product_id value)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ milestones => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ value => {TYPE => 'varchar(20)', NOTNULL => 1},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [
+ milestones_product_id_idx =>
+ {FIELDS => [qw(product_id value)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Global Field Values
+ # -------------------
+
+ bug_status => {
+ FIELDS => [
+ @{dclone(FIELD_TABLE_SCHEMA->{FIELDS})},
+ is_open => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+
+ ],
+ INDEXES => [
+ bug_status_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ bug_status_sortkey_idx => ['sortkey', 'value'],
+ bug_status_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ resolution => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ resolution_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ resolution_sortkey_idx => ['sortkey', 'value'],
+ resolution_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ bug_severity => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ bug_severity_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ bug_severity_sortkey_idx => ['sortkey', 'value'],
+ bug_severity_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ priority => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ priority_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ priority_sortkey_idx => ['sortkey', 'value'],
+ priority_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ rep_platform => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ rep_platform_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ rep_platform_sortkey_idx => ['sortkey', 'value'],
+ rep_platform_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ op_sys => {
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
+ INDEXES => [
+ op_sys_value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ op_sys_sortkey_idx => ['sortkey', 'value'],
+ op_sys_visibility_value_id_idx => ['visibility_value_id'],
+ ],
+ },
+
+ status_workflow => {
+ FIELDS => [
+
+ # On bug creation, there is no old value.
+ old_status => {
+ TYPE => 'INT2',
+ REFERENCES => {TABLE => 'bug_status', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ new_status => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bug_status', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ require_comment => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0},
+ ],
+ INDEXES => [
+ status_workflow_idx =>
+ {FIELDS => ['old_status', 'new_status'], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # USER INFO
+ # ---------
+
+ # General User Information
+ # ------------------------
+
+ profiles => {
+ FIELDS => [
+ userid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ login_name => {TYPE => 'varchar(255)', NOTNULL => 1},
+ cryptpassword => {TYPE => 'varchar(128)'},
+ realname => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ nickname => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ disabledtext => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"},
+ disable_mail => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ mybugslink => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ extern_id => {TYPE => 'varchar(64)'},
+ is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ last_seen_date => {TYPE => 'DATETIME'},
+ password_change_required =>
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ password_change_reason => {TYPE => 'varchar(64)'},
+ mfa => {TYPE => 'varchar(8)', DEFAULT => "''"},
+ mfa_required_date => {TYPE => 'DATETIME'},
+ ],
+ INDEXES => [
+ profiles_login_name_idx => {FIELDS => ['login_name'], TYPE => 'UNIQUE'},
+ profiles_extern_id_idx => {FIELDS => ['extern_id'], TYPE => 'UNIQUE'},
+ profiles_nickname_idx => ['nickname'],
+ profiles_realname_ft_idx => {FIELDS => ['realname'], TYPE => 'FULLTEXT'},
+ ],
+ },
+
+ profile_search => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ bug_list => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ list_order => {TYPE => 'MEDIUMTEXT'},
+ ],
+ INDEXES => [profile_search_user_id_idx => [qw(user_id)],],
+ },
+
+ profiles_activity => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ profiles_when => {TYPE => 'DATETIME', NOTNULL => 1},
+ fieldid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}
+ },
+ oldvalue => {TYPE => 'TINYTEXT'},
+ newvalue => {TYPE => 'TINYTEXT'},
+ ],
+ INDEXES => [
+ profiles_activity_userid_idx => ['userid'],
+ profiles_activity_profiles_when_idx => ['profiles_when'],
+ profiles_activity_fieldid_idx => ['fieldid'],
+ ],
+ },
+
+ profile_mfa => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ name => {TYPE => 'varchar(16)', NOTNULL => 1},
+ value => {TYPE => 'varchar(255)'},
+ ],
+ INDEXES => [
+ profile_mfa_userid_name_idx =>
+ {FIELDS => ['user_id', 'name'], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ email_setting => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ relationship => {TYPE => 'INT1', NOTNULL => 1},
+ event => {TYPE => 'INT1', NOTNULL => 1},
+ ],
+ INDEXES => [
+ email_setting_user_id_idx =>
+ {FIELDS => [qw(user_id relationship event)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ email_bug_ignore => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ email_bug_ignore_user_id_idx =>
+ {FIELDS => [qw(user_id bug_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ watch => {
+ FIELDS => [
+ watcher => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ watched => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ watch_watcher_idx => {FIELDS => [qw(watcher watched)], TYPE => 'UNIQUE'},
+ watch_watched_idx => ['watched'],
+ ],
+ },
+
+ namedqueries => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ query => {TYPE => 'LONGTEXT', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [namedqueries_userid_idx => {FIELDS => [qw(userid name)], TYPE => 'UNIQUE'},],
+ },
+
+ namedqueries_link_in_footer => {
+ FIELDS => [
+ namedquery_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'namedqueries', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ namedqueries_link_in_footer_id_idx =>
+ {FIELDS => [qw(namedquery_id user_id)], TYPE => 'UNIQUE'},
+ namedqueries_link_in_footer_userid_idx => ['user_id'],
+ ],
+ },
+
+ tag => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES =>
+ [tag_user_id_idx => {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'},],
+ },
+
+ bug_tag => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ tag_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'tag', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES =>
+ [bug_tag_bug_id_idx => {FIELDS => [qw(bug_id tag_id)], TYPE => 'UNIQUE'},],
+ },
+
+ component_cc => {
+
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ component_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'components', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ component_cc_user_id_idx =>
+ {FIELDS => [qw(component_id user_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Authentication
+ # --------------
+
+ logincookies => {
+ FIELDS => [
+ cookie => {TYPE => 'varchar(22)', NOTNULL => 1},
+ userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ ipaddr => {TYPE => 'varchar(40)'},
+ lastused => {TYPE => 'DATETIME', NOTNULL => 1},
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ restrict_ipaddr => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0},
+ ],
+ INDEXES => [
+ logincookies_lastused_idx => ['lastused'],
+ logincookies_cookie_idx => {FIELDS => ['cookie'], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ login_failure => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ login_time => {TYPE => 'DATETIME', NOTNULL => 1},
+ ip_addr => {TYPE => 'varchar(40)', NOTNULL => 1},
+ ],
+ INDEXES => [
+
+ # We do lookups by every item in the table simultaneously, but
+ # having an index with all three items would be the same size as
+ # the table. So instead we have an index on just the smallest item,
+ # to speed lookups.
+ login_failure_user_id_idx => ['user_id'],
+ ],
+ },
+
+
+ # "tokens" stores the tokens users receive when a password or email
+ # change is requested. Tokens provide an extra measure of security
+ # for these changes.
+ tokens => {
+ FIELDS => [
+ userid => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ issuedate => {TYPE => 'DATETIME', NOTNULL => 1},
+ token => {TYPE => 'varchar(22)', NOTNULL => 1, PRIMARYKEY => 1},
+ tokentype => {TYPE => 'varchar(16)', NOTNULL => 1},
+ eventdata => {TYPE => 'TINYTEXT'},
+ ],
+ INDEXES => [tokens_userid_idx => ['userid'],],
+ },
+
+ token_data => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ token => {
+ TYPE => 'varchar(22)',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'tokens', COLUMN => 'token', DELETE => 'CASCADE'}
+ },
+ extra_data => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ ],
+ INDEXES => [token_data_idx => {FIELDS => ['token'], TYPE => 'UNIQUE'},],
+ },
+
+ # GROUPS
+ # ------
+
+ groups => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(255)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isbuggroup => {TYPE => 'BOOLEAN', NOTNULL => 1},
+ userregexp => {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ icon_url => {TYPE => 'TINYTEXT'},
+ owner_user_id =>
+ {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}},
+ idle_member_removal => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}
+ ],
+ INDEXES => [groups_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ group_control_map => {
+ FIELDS => [
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ entry => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ membercontrol => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA},
+ othercontrol => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA},
+ canedit => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ editbugs => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ canconfirm => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ INDEXES => [
+ group_control_map_product_id_idx =>
+ {FIELDS => [qw(product_id group_id)], TYPE => 'UNIQUE'},
+ group_control_map_group_id_idx => ['group_id'],
+ ],
+ },
+
+ # "user_group_map" determines the groups that a user belongs to
+ # directly or due to regexp and which groups can be blessed by a user.
+ #
+ # grant_type:
+ # if GRANT_DIRECT - record was explicitly granted
+ # if GRANT_DERIVED - record was derived from expanding a group hierarchy
+ # if GRANT_REGEXP - record was created by evaluating a regexp
+ user_group_map => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ isbless => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ grant_type => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => GRANT_DIRECT},
+ ],
+ INDEXES => [
+ user_group_map_user_id_idx =>
+ {FIELDS => [qw(user_id group_id grant_type isbless)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # This table determines which groups are made a member of another
+ # group, given the ability to bless another group, or given
+ # visibility to another groups existence and membership
+ # grant_type:
+ # if GROUP_MEMBERSHIP - member groups are made members of grantor
+ # if GROUP_BLESS - member groups may grant membership in grantor
+ # if GROUP_VISIBLE - member groups may see grantor group
+ group_group_map => {
+ FIELDS => [
+ member_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ grantor_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ grant_type => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => GROUP_MEMBERSHIP},
+ ],
+ INDEXES => [
+ group_group_map_member_id_idx =>
+ {FIELDS => [qw(member_id grantor_id grant_type)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # This table determines which groups a user must be a member of
+ # in order to see a bug.
+ bug_group_map => {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ bug_group_map_bug_id_idx => {FIELDS => [qw(bug_id group_id)], TYPE => 'UNIQUE'},
+ bug_group_map_group_id_idx => ['group_id'],
+ ],
+ },
+
+ # This table determines which groups a user must be a member of
+ # in order to see a named query somebody else shares.
+ namedquery_group_map => {
+ FIELDS => [
+ namedquery_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'namedqueries', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ namedquery_group_map_namedquery_id_idx =>
+ {FIELDS => [qw(namedquery_id)], TYPE => 'UNIQUE'},
+ namedquery_group_map_group_id_idx => ['group_id'],
+ ],
+ },
+
+ category_group_map => {
+ FIELDS => [
+ category_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES =>
+ {TABLE => 'series_categories', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ group_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ ],
+ INDEXES => [
+ category_group_map_category_id_idx =>
+ {FIELDS => [qw(category_id group_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+
+ # PRODUCTS
+ # --------
+
+ classifications => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ description => {TYPE => 'MEDIUMTEXT'},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ ],
+ INDEXES =>
+ [classifications_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ products => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ classification_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ DEFAULT => '1',
+ REFERENCES => {TABLE => 'classifications', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1},
+ defaultmilestone => {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"},
+ allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ INDEXES => [products_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ components => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ product_id => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'products', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ initialowner => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}
+ },
+ initialqacontact => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'SET NULL'}
+ },
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ triage_owner_id => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'SET NULL'}
+ },
+ ],
+ INDEXES => [
+ components_product_id_idx =>
+ {FIELDS => [qw(product_id name)], TYPE => 'UNIQUE'},
+ components_name_idx => ['name'],
+ ],
+ },
+
+ # CHARTS
+ # ------
+
+ series => {
+ FIELDS => [
+ series_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ creator => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ category => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES =>
+ {TABLE => 'series_categories', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ subcategory => {
+ TYPE => 'INT2',
+ NOTNULL => 1,
+ REFERENCES =>
+ {TABLE => 'series_categories', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ frequency => {TYPE => 'INT2', NOTNULL => 1},
+ query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ is_public => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ INDEXES => [
+ series_creator_idx => ['creator'],
+ series_category_idx =>
+ {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ series_data => {
+ FIELDS => [
+ series_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'series', COLUMN => 'series_id', DELETE => 'CASCADE'}
+ },
+ series_date => {TYPE => 'DATETIME', NOTNULL => 1},
+ series_value => {TYPE => 'INT3', NOTNULL => 1},
+ ],
+ INDEXES => [
+ series_data_series_id_idx =>
+ {FIELDS => [qw(series_id series_date)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ series_categories => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [series_categories_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},],
+ },
+
+ # WHINE SYSTEM
+ # ------------
+
+ whine_queries => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ eventid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'whine_events', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ query_name => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ onemailperbug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ title => {TYPE => 'varchar(128)', NOTNULL => 1, DEFAULT => "''"},
+ ],
+ INDEXES => [whine_queries_eventid_idx => ['eventid'],],
+ },
+
+ whine_schedules => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ eventid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'whine_events', COLUMN => 'id', DELETE => 'CASCADE'}
+ },
+ run_day => {TYPE => 'varchar(32)'},
+ run_time => {TYPE => 'varchar(32)'},
+ run_next => {TYPE => 'DATETIME'},
+ mailto => {TYPE => 'INT3', NOTNULL => 1},
+ mailto_type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
+ ],
+ INDEXES => [
+ whine_schedules_run_next_idx => ['run_next'],
+ whine_schedules_eventid_idx => ['eventid'],
+ ],
+ },
+
+ whine_events => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ owner_userid => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ subject => {TYPE => 'varchar(128)'},
+ body => {TYPE => 'MEDIUMTEXT'},
+ mailifnobugs => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ ],
+ },
+
+ # QUIPS
+ # -----
+
+ quips => {
+ FIELDS => [
+ quipid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ userid => {
+ TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'SET NULL'}
+ },
+ quip => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ approved => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ ],
+ },
+
+ # SETTINGS
+ # --------
+ # setting - each global setting will have exactly one entry
+ # in this table.
+ # setting_value - stores the list of acceptable values for each
+ # setting, and a sort index that controls the order
+ # in which the values are displayed.
+ # profile_setting - If a user has chosen to use a value other than the
+ # global default for a given setting, it will be
+ # stored in this table. Note: even if a setting is
+ # later changed so is_enabled = false, the stored
+ # value will remain in case it is ever enabled again.
+ #
+ setting => {
+ FIELDS => [
+ name => {TYPE => 'varchar(32)', NOTNULL => 1, PRIMARYKEY => 1},
+ default_value => {TYPE => 'varchar(32)', NOTNULL => 1},
+ is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+ subclass => {TYPE => 'varchar(32)'},
+ category => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "'General'"}
+ ],
+ },
+
+ setting_value => {
+ FIELDS => [
+ name => {
+ TYPE => 'varchar(32)',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'setting', COLUMN => 'name', DELETE => 'CASCADE'}
+ },
+ value => {TYPE => 'varchar(32)', NOTNULL => 1},
+ sortindex => {TYPE => 'INT2', NOTNULL => 1},
+ ],
+ INDEXES => [
+ setting_value_nv_unique_idx => {FIELDS => [qw(name value)], TYPE => 'UNIQUE'},
+ setting_value_ns_unique_idx =>
+ {FIELDS => [qw(name sortindex)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ profile_setting => {
+ FIELDS => [
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ setting_name => {
+ TYPE => 'varchar(32)',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'setting', COLUMN => 'name', DELETE => 'CASCADE'}
+ },
+ setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
+ ],
+ INDEXES => [
+ profile_setting_value_unique_idx =>
+ {FIELDS => [qw(user_id setting_name)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ email_rates => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ recipient => {TYPE => 'varchar(255)', NOTNULL => 1},
+ message_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ ],
+ INDEXES => [
+ email_rates_idx => [qw(recipient message_ts)],
+ email_rates_message_ts_idx => ['message_ts'],
+ ],
+ },
+
+ # THESCHWARTZ TABLES
+ # ------------------
+ # Note: In the standard TheSchwartz schema, most integers are unsigned,
+ # but we didn't implement unsigned ints for Bugzilla schemas, so we
+ # just create signed ints, which should be fine.
+
+ ts_funcmap => {
+ FIELDS => [
+ funcid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ funcname => {TYPE => 'varchar(255)', NOTNULL => 1},
+ ],
+ INDEXES =>
+ [ts_funcmap_funcname_idx => {FIELDS => ['funcname'], TYPE => 'UNIQUE'},],
+ },
+
+ ts_job => {
+ FIELDS => [
+
+ # In a standard TheSchwartz schema, this is a BIGINT, but we
+ # don't have those and I didn't want to add them just for this.
+ jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1},
+
+ # In standard TheSchwartz, this is a MEDIUMBLOB.
+ arg => {TYPE => 'LONGBLOB'},
+ uniqkey => {TYPE => 'varchar(255)'},
+ insert_time => {TYPE => 'INT4'},
+ run_after => {TYPE => 'INT4', NOTNULL => 1},
+ grabbed_until => {TYPE => 'INT4', NOTNULL => 1},
+ priority => {TYPE => 'INT2'},
+ coalesce => {TYPE => 'varchar(255)'},
+ ],
+ INDEXES => [
+ ts_job_funcid_idx => {FIELDS => [qw(funcid uniqkey)], TYPE => 'UNIQUE'},
+
+ # In a standard TheSchewartz schema, these both go in the other
+ # direction, but there's no reason to have three indexes that
+ # all start with the same column, and our naming scheme doesn't
+ # allow it anyhow.
+ ts_job_run_after_idx => [qw(run_after funcid)],
+ ts_job_coalesce_idx => [qw(coalesce funcid)],
+ ],
+ },
+
+ ts_note => {
+ FIELDS => [
+
+ # This is a BIGINT in standard TheSchwartz schemas.
+ jobid => {TYPE => 'INT4', NOTNULL => 1},
+ notekey => {TYPE => 'varchar(255)'},
+ value => {TYPE => 'LONGBLOB'},
+ ],
+ INDEXES =>
+ [ts_note_jobid_idx => {FIELDS => [qw(jobid notekey)], TYPE => 'UNIQUE'},],
+ },
+
+ ts_error => {
+ FIELDS => [
+ error_time => {TYPE => 'INT4', NOTNULL => 1},
+ jobid => {TYPE => 'INT4', NOTNULL => 1},
+ message => {TYPE => 'varchar(255)', NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
+ ],
+ INDEXES => [
+ ts_error_funcid_idx => [qw(funcid error_time)],
+ ts_error_error_time_idx => ['error_time'],
+ ts_error_jobid_idx => ['jobid'],
+ ],
+ },
+
+ ts_exitstatus => {
FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
+ status => {TYPE => 'INT2'},
+ completion_time => {TYPE => 'INT4'},
+ delete_after => {TYPE => 'INT4'},
],
INDEXES => [
- bug_id_idx => {FIELDS => [qw( bug_id value)], TYPE => 'UNIQUE'},
+ ts_exitstatus_funcid_idx => ['funcid'],
+ ts_exitstatus_delete_after_idx => ['delete_after'],
],
+ },
+
+ # SCHEMA STORAGE
+ # --------------
+
+ bz_schema => {
+ FIELDS => [
+ schema_data => {TYPE => 'LONGBLOB', NOTNULL => 1},
+ version => {TYPE => 'decimal(3,2)', NOTNULL => 1},
+ ],
+ },
+
+ bug_user_last_visit => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'}
+ },
+ last_visit_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ ],
+ INDEXES => [
+ bug_user_last_visit_idx => {FIELDS => ['user_id', 'bug_id'], TYPE => 'UNIQUE'},
+ bug_user_last_visit_last_visit_ts_idx => ['last_visit_ts'],
+ ],
+ },
+
+ user_api_keys => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}
+ },
+ api_key => {TYPE => 'varchar(40)', NOTNULL => 1},
+ description => {TYPE => 'varchar(255)'},
+ revoked => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
+ last_used => {TYPE => 'DATETIME'},
+ last_used_ip => {TYPE => 'varchar(40)'},
+ app_id => {TYPE => 'varchar(64)'},
+ ],
+ INDEXES => [
+ user_api_keys_api_key_idx => {FIELDS => ['api_key'], TYPE => 'UNIQUE'},
+ user_api_keys_user_id_idx => ['user_id'],
+ user_api_keys_user_id_app_id_idx => ['user_id', 'app_id'],
+ ],
+ },
+
+ user_request_log => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {TYPE => 'INT3', NOTNULL => 1},
+ ip_address => {TYPE => 'varchar(40)', NOTNULL => 1},
+ user_agent => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ timestamp => {TYPE => 'DATETIME', NOTNULL => 1},
+ bug_id => {TYPE => 'INT3', NOTNULL => 0},
+ attach_id => {TYPE => 'INT4', NOTNULL => 0},
+ request_url => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ method => {TYPE => 'TINYTEXT', NOTNULL => 1},
+ action => {TYPE => 'varchar(20)', NOTNULL => 1},
+ server => {TYPE => 'varchar(7)', NOTNULL => 1},
+ ],
+ INDEXES => [user_user_request_log_user_id_idx => ['user_id'],],
+ },
+};
+
+# Foreign Keys are added in Bugzilla::DB::bz_add_field_tables
+use constant MULTI_SELECT_VALUE_TABLE => {
+ FIELDS => [
+ bug_id => {TYPE => 'INT3', NOTNULL => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ ],
+ INDEXES => [bug_id_idx => {FIELDS => [qw( bug_id value)], TYPE => 'UNIQUE'},],
};
#--------------------------------------------------------------------------
@@ -1871,30 +1821,31 @@ sub new {
=cut
- my $this = shift;
- my $class = ref($this) || $this;
- my $driver = shift;
+ my $this = shift;
+ my $class = ref($this) || $this;
+ my $driver = shift;
- if ($driver) {
- (my $subclass = $driver) =~ s/^(\S)/\U$1/;
- $class .= '::' . $subclass;
- try {
- require_module($class);
- }
- catch {
- die "The $class class could not be found ($subclass not supported?): $_";
- };
+ if ($driver) {
+ (my $subclass = $driver) =~ s/^(\S)/\U$1/;
+ $class .= '::' . $subclass;
+ try {
+ require_module($class);
}
- die "$class is an abstract base class. Instantiate a subclass instead."
- if ($class eq __PACKAGE__);
+ catch {
+ die "The $class class could not be found ($subclass not supported?): $_";
+ };
+ }
+ die "$class is an abstract base class. Instantiate a subclass instead."
+ if ($class eq __PACKAGE__);
+
+ my $self = {};
+ bless $self, $class;
+ $self = $self->_initialize(@_);
- my $self = {};
- bless $self, $class;
- $self = $self->_initialize(@_);
+ return ($self);
- return($self);
+} #eosub--new
-} #eosub--new
#--------------------------------------------------------------------------
sub _initialize {
@@ -1917,33 +1868,34 @@ sub _initialize {
=cut
- my $self = shift;
- my $abstract_schema = shift;
+ my $self = shift;
+ my $abstract_schema = shift;
- if (!$abstract_schema) {
- # While ABSTRACT_SCHEMA cannot be modified, $abstract_schema can be.
- # So, we dclone it to prevent anything from mucking with the constant.
- $abstract_schema = dclone(ABSTRACT_SCHEMA);
+ if (!$abstract_schema) {
- # Let extensions add tables, but make sure they can't modify existing
- # tables. If we don't lock/unlock keys, lock_value complains.
- lock_keys(%$abstract_schema);
- foreach my $table (keys %{ABSTRACT_SCHEMA()}) {
- lock_value(%$abstract_schema, $table)
- if exists $abstract_schema->{$table};
- }
- unlock_keys(%$abstract_schema);
- Bugzilla::Hook::process('db_schema_abstract_schema',
- { schema => $abstract_schema });
- unlock_hash(%$abstract_schema);
+ # While ABSTRACT_SCHEMA cannot be modified, $abstract_schema can be.
+ # So, we dclone it to prevent anything from mucking with the constant.
+ $abstract_schema = dclone(ABSTRACT_SCHEMA);
+
+ # Let extensions add tables, but make sure they can't modify existing
+ # tables. If we don't lock/unlock keys, lock_value complains.
+ lock_keys(%$abstract_schema);
+ foreach my $table (keys %{ABSTRACT_SCHEMA()}) {
+ lock_value(%$abstract_schema, $table) if exists $abstract_schema->{$table};
}
+ unlock_keys(%$abstract_schema);
+ Bugzilla::Hook::process('db_schema_abstract_schema',
+ {schema => $abstract_schema});
+ unlock_hash(%$abstract_schema);
+ }
+
+ $self->{schema} = dclone($abstract_schema);
+ $self->{abstract_schema} = $abstract_schema;
- $self->{schema} = dclone($abstract_schema);
- $self->{abstract_schema} = $abstract_schema;
+ return $self;
- return $self;
+} #eosub--_initialize
-} #eosub--_initialize
#--------------------------------------------------------------------------
sub _adjust_schema {
@@ -1959,36 +1911,41 @@ sub _adjust_schema {
=cut
- my $self = shift;
-
- # The _initialize method has already set up the db_specific hash with
- # the information on how to implement the abstract data types for the
- # instantiated DBMS-specific subclass.
- my $db_specific = $self->{db_specific};
-
- # Loop over each table in the abstract database schema.
- foreach my $table (keys %{ $self->{schema} }) {
- my %fields = (@{ $self->{schema}{$table}{FIELDS} });
- # Loop over the field definitions in each table.
- foreach my $field_def (values %fields) {
- # If the field type is an abstract data type defined in the
- # $db_specific hash, replace it with the DBMS-specific data type
- # that implements it.
- if (exists($db_specific->{$field_def->{TYPE}})) {
- $field_def->{TYPE} = $db_specific->{$field_def->{TYPE}};
- }
- # Replace abstract default values (such as 'TRUE' and 'FALSE')
- # with their database-specific implementations.
- if (exists($field_def->{DEFAULT})
- && exists($db_specific->{$field_def->{DEFAULT}})) {
- $field_def->{DEFAULT} = $db_specific->{$field_def->{DEFAULT}};
- }
- }
+ my $self = shift;
+
+ # The _initialize method has already set up the db_specific hash with
+ # the information on how to implement the abstract data types for the
+ # instantiated DBMS-specific subclass.
+ my $db_specific = $self->{db_specific};
+
+ # Loop over each table in the abstract database schema.
+ foreach my $table (keys %{$self->{schema}}) {
+ my %fields = (@{$self->{schema}{$table}{FIELDS}});
+
+ # Loop over the field definitions in each table.
+ foreach my $field_def (values %fields) {
+
+ # If the field type is an abstract data type defined in the
+ # $db_specific hash, replace it with the DBMS-specific data type
+ # that implements it.
+ if (exists($db_specific->{$field_def->{TYPE}})) {
+ $field_def->{TYPE} = $db_specific->{$field_def->{TYPE}};
+ }
+
+ # Replace abstract default values (such as 'TRUE' and 'FALSE')
+ # with their database-specific implementations.
+ if ( exists($field_def->{DEFAULT})
+ && exists($db_specific->{$field_def->{DEFAULT}}))
+ {
+ $field_def->{DEFAULT} = $db_specific->{$field_def->{DEFAULT}};
+ }
}
+ }
+
+ return $self;
- return $self;
+} #eosub--_adjust_schema
-} #eosub--_adjust_schema
#--------------------------------------------------------------------------
sub get_type_ddl {
@@ -2022,30 +1979,34 @@ C<ALTER TABLE> SQL statement
=cut
- my $self = shift;
- my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ };
- my $type = $finfo->{TYPE};
- confess "A valid TYPE was not specified for this column (got "
- . Dumper($finfo) . ")" unless ($type);
-
- my $default = $finfo->{DEFAULT};
- # Replace any abstract default value (such as 'TRUE' or 'FALSE')
- # with its database-specific implementation.
- if ( defined $default && exists($self->{db_specific}->{$default}) ) {
- $default = $self->{db_specific}->{$default};
- }
+ my $self = shift;
+ my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : {@_};
+ my $type = $finfo->{TYPE};
+ confess "A valid TYPE was not specified for this column (got "
+ . Dumper($finfo) . ")"
+ unless ($type);
+
+ my $default = $finfo->{DEFAULT};
+
+ # Replace any abstract default value (such as 'TRUE' or 'FALSE')
+ # with its database-specific implementation.
+ if (defined $default && exists($self->{db_specific}->{$default})) {
+ $default = $self->{db_specific}->{$default};
+ }
+
+ my $type_ddl = $self->convert_type($type);
+
+ # DEFAULT attribute must appear before any column constraints
+ # (e.g., NOT NULL), for Oracle
+ $type_ddl .= " DEFAULT $default" if (defined($default));
- my $type_ddl = $self->convert_type($type);
- # DEFAULT attribute must appear before any column constraints
- # (e.g., NOT NULL), for Oracle
- $type_ddl .= " DEFAULT $default" if (defined($default));
- # PRIMARY KEY must appear before NOT NULL for SQLite.
- $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
- $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
+ # PRIMARY KEY must appear before NOT NULL for SQLite.
+ $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
+ $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
- return($type_ddl);
+ return ($type_ddl);
-} #eosub--get_type_ddl
+} #eosub--get_type_ddl
sub get_fk_ddl {
@@ -2079,78 +2040,80 @@ is undefined.
=cut
- my ($self, $table, $column, $references) = @_;
- return "" if !$references;
+ my ($self, $table, $column, $references) = @_;
+ return "" if !$references;
- my $update = $references->{UPDATE} || 'CASCADE';
- my $delete = $references->{DELETE} || 'RESTRICT';
- my $to_table = $references->{TABLE} || confess "No table in reference";
- my $to_column = $references->{COLUMN} || confess "No column in reference";
- my $fk_name = $self->_get_fk_name($table, $column, $references);
+ my $update = $references->{UPDATE} || 'CASCADE';
+ my $delete = $references->{DELETE} || 'RESTRICT';
+ my $to_table = $references->{TABLE} || confess "No table in reference";
+ my $to_column = $references->{COLUMN} || confess "No column in reference";
+ my $fk_name = $self->_get_fk_name($table, $column, $references);
- return "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
- . " REFERENCES $to_table($to_column)\n"
- . " ON UPDATE $update ON DELETE $delete";
+ return
+ "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
+ . " REFERENCES $to_table($to_column)\n"
+ . " ON UPDATE $update ON DELETE $delete";
}
# Generates a name for a Foreign Key. It's separate from get_fk_ddl
# so that certain databases can override it (for shorter identifiers or
# other reasons).
sub _get_fk_name {
- my ($self, $table, $column, $references) = @_;
- my $to_table = $references->{TABLE};
- my $to_column = $references->{COLUMN};
- my $name = "fk_${table}_${column}_${to_table}_${to_column}";
+ my ($self, $table, $column, $references) = @_;
+ my $to_table = $references->{TABLE};
+ my $to_column = $references->{COLUMN};
+ my $name = "fk_${table}_${column}_${to_table}_${to_column}";
- if (length($name) > $self->MAX_IDENTIFIER_LEN) {
- $name = 'fk_' . $self->_hash_identifier($name);
- }
+ if (length($name) > $self->MAX_IDENTIFIER_LEN) {
+ $name = 'fk_' . $self->_hash_identifier($name);
+ }
- return $name;
+ return $name;
}
sub _hash_identifier {
- my ($invocant, $value) = @_;
- # We do -7 to allow prefixes like "idx_" or "fk_", or perhaps something
- # longer in the future.
- return substr(md5_hex($value), 0, $invocant->MAX_IDENTIFIER_LEN - 7);
+ my ($invocant, $value) = @_;
+
+ # We do -7 to allow prefixes like "idx_" or "fk_", or perhaps something
+ # longer in the future.
+ return substr(md5_hex($value), 0, $invocant->MAX_IDENTIFIER_LEN - 7);
}
sub get_add_fks_sql {
- my ($self, $table, $column_fks) = @_;
-
- my @add = $self->_column_fks_to_ddl($table, $column_fks);
-
- my @sql;
- if ($self->MULTIPLE_FKS_IN_ALTER) {
- my $alter = "ALTER TABLE $table ADD " . join(', ADD ', @add);
- push(@sql, $alter);
+ my ($self, $table, $column_fks) = @_;
+
+ my @add = $self->_column_fks_to_ddl($table, $column_fks);
+
+ my @sql;
+ if ($self->MULTIPLE_FKS_IN_ALTER) {
+ my $alter = "ALTER TABLE $table ADD " . join(', ADD ', @add);
+ push(@sql, $alter);
+ }
+ else {
+ foreach my $fk_string (@add) {
+ push(@sql, "ALTER TABLE $table ADD $fk_string");
}
- else {
- foreach my $fk_string (@add) {
- push(@sql, "ALTER TABLE $table ADD $fk_string");
- }
- }
- return @sql;
+ }
+ return @sql;
}
sub _column_fks_to_ddl {
- my ($self, $table, $column_fks) = @_;
- my @ddl;
- foreach my $column (keys %$column_fks) {
- my $def = $column_fks->{$column};
- my $fk_string = $self->get_fk_ddl($table, $column, $def);
- push(@ddl, $fk_string);
- }
- return @ddl;
+ my ($self, $table, $column_fks) = @_;
+ my @ddl;
+ foreach my $column (keys %$column_fks) {
+ my $def = $column_fks->{$column};
+ my $fk_string = $self->get_fk_ddl($table, $column, $def);
+ push(@ddl, $fk_string);
+ }
+ return @ddl;
}
sub get_drop_fk_sql {
- my ($self, $table, $column, $references) = @_;
- my $fk_name = $self->_get_fk_name($table, $column, $references);
+ my ($self, $table, $column, $references) = @_;
+ my $fk_name = $self->_get_fk_name($table, $column, $references);
- return ("ALTER TABLE $table DROP CONSTRAINT $fk_name");
+ return ("ALTER TABLE $table DROP CONSTRAINT $fk_name");
}
sub convert_type {
@@ -2161,8 +2124,8 @@ Converts a TYPE from the L</ABSTRACT_SCHEMA> format into the real SQL type.
=cut
- my ($self, $type) = @_;
- return $self->{db_specific}->{$type} || $type;
+ my ($self, $type) = @_;
+ return $self->{db_specific}->{$type} || $type;
}
sub get_column {
@@ -2179,16 +2142,16 @@ sub get_column {
=cut
- my($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- if (exists $self->{schema}->{$table}) {
- my %fields = (@{ $self->{schema}{$table}{FIELDS} });
- return $fields{$column};
- }
- return undef;
-} #eosub--get_column
+ # Prevent a possible dereferencing of an undef hash, if the
+ # table doesn't exist.
+ if (exists $self->{schema}->{$table}) {
+ my %fields = (@{$self->{schema}{$table}{FIELDS}});
+ return $fields{$column};
+ }
+ return undef;
+} #eosub--get_column
sub get_table_list {
@@ -2203,8 +2166,8 @@ sub get_table_list {
=cut
- my $self = shift;
- return sort keys %{$self->{schema}};
+ my $self = shift;
+ return sort keys %{$self->{schema}};
}
sub get_table_columns {
@@ -2218,34 +2181,33 @@ sub get_table_columns {
=cut
- my($self, $table) = @_;
- my @ddl = ();
+ my ($self, $table) = @_;
+ my @ddl = ();
- my $thash = $self->{schema}{$table};
- die "Table $table does not exist in the database schema."
- unless (ref($thash));
+ my $thash = $self->{schema}{$table};
+ die "Table $table does not exist in the database schema." unless (ref($thash));
- my @columns = ();
- my @fields = @{ $thash->{FIELDS} };
- while (@fields) {
- push(@columns, shift(@fields));
- shift(@fields);
- }
+ my @columns = ();
+ my @fields = @{$thash->{FIELDS}};
+ while (@fields) {
+ push(@columns, shift(@fields));
+ shift(@fields);
+ }
- return @columns;
+ return @columns;
-} #eosub--get_table_columns
+} #eosub--get_table_columns
sub get_table_indexes_abstract {
- my ($self, $table) = @_;
- my $table_def = $self->get_table_abstract($table);
- my %indexes = @{$table_def->{INDEXES} || []};
- return \%indexes;
+ my ($self, $table) = @_;
+ my $table_def = $self->get_table_abstract($table);
+ my %indexes = @{$table_def->{INDEXES} || []};
+ return \%indexes;
}
sub get_create_database_sql {
- my ($self, $name) = @_;
- return ("CREATE DATABASE $name");
+ my ($self, $name) = @_;
+ return ("CREATE DATABASE $name");
}
sub get_table_ddl {
@@ -2262,30 +2224,29 @@ sub get_table_ddl {
=cut
- my($self, $table) = @_;
- my @ddl = ();
+ my ($self, $table) = @_;
+ my @ddl = ();
- die "Table $table does not exist in the database schema."
- unless (ref($self->{schema}{$table}));
+ die "Table $table does not exist in the database schema."
+ unless (ref($self->{schema}{$table}));
- my $create_table = $self->_get_create_table_ddl($table);
- push(@ddl, $create_table) if $create_table;
+ my $create_table = $self->_get_create_table_ddl($table);
+ push(@ddl, $create_table) if $create_table;
- my @indexes = @{ $self->{schema}{$table}{INDEXES} || [] };
- while (@indexes) {
- my $index_name = shift(@indexes);
- my $index_info = shift(@indexes);
- my $index_sql = $self->get_add_index_ddl($table, $index_name,
- $index_info);
- push(@ddl, $index_sql) if $index_sql;
- }
+ my @indexes = @{$self->{schema}{$table}{INDEXES} || []};
+ while (@indexes) {
+ my $index_name = shift(@indexes);
+ my $index_info = shift(@indexes);
+ my $index_sql = $self->get_add_index_ddl($table, $index_name, $index_info);
+ push(@ddl, $index_sql) if $index_sql;
+ }
- push(@ddl, @{ $self->{schema}{$table}{DB_EXTRAS} })
- if (ref($self->{schema}{$table}{DB_EXTRAS}));
+ push(@ddl, @{$self->{schema}{$table}{DB_EXTRAS}})
+ if (ref($self->{schema}{$table}{DB_EXTRAS}));
- return @ddl;
+ return @ddl;
-} #eosub--get_table_ddl
+} #eosub--get_table_ddl
sub _get_create_table_ddl {
@@ -2298,28 +2259,27 @@ sub _get_create_table_ddl {
=cut
- my($self, $table) = @_;
-
- my $thash = $self->{schema}{$table};
- die "Table $table does not exist in the database schema."
- unless ref $thash;
-
- my (@col_lines, @fk_lines);
- my @fields = @{ $thash->{FIELDS} };
- while (@fields) {
- my $field = shift(@fields);
- my $finfo = shift(@fields);
- push(@col_lines, "\t$field\t" . $self->get_type_ddl($finfo));
- if ($self->FK_ON_CREATE and $finfo->{REFERENCES}) {
- my $fk = $finfo->{REFERENCES};
- my $fk_ddl = $self->get_fk_ddl($table, $field, $fk);
- push(@fk_lines, $fk_ddl);
- }
+ my ($self, $table) = @_;
+
+ my $thash = $self->{schema}{$table};
+ die "Table $table does not exist in the database schema." unless ref $thash;
+
+ my (@col_lines, @fk_lines);
+ my @fields = @{$thash->{FIELDS}};
+ while (@fields) {
+ my $field = shift(@fields);
+ my $finfo = shift(@fields);
+ push(@col_lines, "\t$field\t" . $self->get_type_ddl($finfo));
+ if ($self->FK_ON_CREATE and $finfo->{REFERENCES}) {
+ my $fk = $finfo->{REFERENCES};
+ my $fk_ddl = $self->get_fk_ddl($table, $field, $fk);
+ push(@fk_lines, $fk_ddl);
}
+ }
- my $sql = "CREATE TABLE $table (\n" . join(",\n", @col_lines, @fk_lines)
- . "\n)";
- return $sql
+ my $sql
+ = "CREATE TABLE $table (\n" . join(",\n", @col_lines, @fk_lines) . "\n)";
+ return $sql;
}
@@ -2337,16 +2297,17 @@ sub _get_create_index_ddl {
=cut
- my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+ my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+
+ my $sql = "CREATE ";
+ $sql .= "$index_type " if ($index_type && $index_type eq 'UNIQUE');
+ $sql
+ .= "INDEX $index_name ON $table_name \(" . join(", ", @$index_fields) . "\)";
- my $sql = "CREATE ";
- $sql .= "$index_type " if ($index_type && $index_type eq 'UNIQUE');
- $sql .= "INDEX $index_name ON $table_name \(" .
- join(", ", @$index_fields) . "\)";
+ return ($sql);
- return($sql);
+} #eosub--_get_create_index_ddl
-} #eosub--_get_create_index_ddl
#--------------------------------------------------------------------------
sub get_add_column_ddl {
@@ -2365,22 +2326,25 @@ sub get_add_column_ddl {
=cut
- my ($self, $table, $column, $definition, $init_value) = @_;
- my @statements;
- push(@statements, "ALTER TABLE $table ". $self->ADD_COLUMN ." $column " .
- $self->get_type_ddl($definition));
-
- # XXX - Note that although this works for MySQL, most databases will fail
- # before this point, if we haven't set a default.
- (push(@statements, "UPDATE $table SET $column = $init_value"))
- if defined $init_value;
-
- if (defined $definition->{REFERENCES}) {
- push(@statements, $self->get_add_fks_sql($table, { $column =>
- $definition->{REFERENCES} }));
- }
-
- return (@statements);
+ my ($self, $table, $column, $definition, $init_value) = @_;
+ my @statements;
+ push(@statements,
+ "ALTER TABLE $table "
+ . $self->ADD_COLUMN
+ . " $column "
+ . $self->get_type_ddl($definition));
+
+ # XXX - Note that although this works for MySQL, most databases will fail
+ # before this point, if we haven't set a default.
+ (push(@statements, "UPDATE $table SET $column = $init_value"))
+ if defined $init_value;
+
+ if (defined $definition->{REFERENCES}) {
+ push(@statements,
+ $self->get_add_fks_sql($table, {$column => $definition->{REFERENCES}}));
+ }
+
+ return (@statements);
}
sub get_add_index_ddl {
@@ -2401,20 +2365,21 @@ sub get_add_index_ddl {
=cut
- my ($self, $table, $name, $definition) = @_;
+ my ($self, $table, $name, $definition) = @_;
- my ($index_fields, $index_type);
- # Index defs can be arrays or hashes
- if (ref($definition) eq 'HASH') {
- $index_fields = $definition->{FIELDS};
- $index_type = $definition->{TYPE};
- } else {
- $index_fields = $definition;
- $index_type = '';
- }
+ my ($index_fields, $index_type);
- return $self->_get_create_index_ddl($table, $name, $index_fields,
- $index_type);
+ # Index defs can be arrays or hashes
+ if (ref($definition) eq 'HASH') {
+ $index_fields = $definition->{FIELDS};
+ $index_type = $definition->{TYPE};
+ }
+ else {
+ $index_fields = $definition;
+ $index_type = '';
+ }
+
+ return $self->_get_create_index_ddl($table, $name, $index_fields, $index_type);
}
sub get_alter_column_ddl {
@@ -2437,85 +2402,88 @@ sub get_alter_column_ddl {
=cut
- my $self = shift;
- my ($table, $column, $new_def, $set_nulls_to) = @_;
-
- my @statements;
- my $old_def = $self->get_column_abstract($table, $column);
- my $specific = $self->{db_specific};
-
- # If the types have changed, we have to deal with that.
- if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
- push(@statements, $self->_get_alter_type_sql($table, $column,
- $new_def, $old_def));
- }
-
- my $default = $new_def->{DEFAULT};
- my $default_old = $old_def->{DEFAULT};
-
- if (defined $default) {
- $default = $specific->{$default} if exists $specific->{$default};
- }
- # This first condition prevents "uninitialized value" errors.
- if (!defined $default && !defined $default_old) {
- # Do Nothing
- }
- # If we went from having a default to not having one
- elsif (!defined $default && defined $default_old) {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " DROP DEFAULT");
- }
- # If we went from no default to a default, or we changed the default.
- elsif ( (defined $default && !defined $default_old) ||
- ($default ne $default_old) )
- {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column "
- . " SET DEFAULT $default");
- }
-
- # If we went from NULL to NOT NULL.
- if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
- push(@statements, $self->_set_nulls_sql(@_));
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " SET NOT NULL");
- }
- # If we went from NOT NULL to NULL
- elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " DROP NOT NULL");
- }
-
- # If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
- if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
- push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
- }
- # If we went from being a PK to not being a PK
- elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
- push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
- }
-
- return @statements;
+ my $self = shift;
+ my ($table, $column, $new_def, $set_nulls_to) = @_;
+
+ my @statements;
+ my $old_def = $self->get_column_abstract($table, $column);
+ my $specific = $self->{db_specific};
+
+ # If the types have changed, we have to deal with that.
+ if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
+ push(@statements,
+ $self->_get_alter_type_sql($table, $column, $new_def, $old_def));
+ }
+
+ my $default = $new_def->{DEFAULT};
+ my $default_old = $old_def->{DEFAULT};
+
+ if (defined $default) {
+ $default = $specific->{$default} if exists $specific->{$default};
+ }
+
+ # This first condition prevents "uninitialized value" errors.
+ if (!defined $default && !defined $default_old) {
+
+ # Do Nothing
+ }
+
+ # If we went from having a default to not having one
+ elsif (!defined $default && defined $default_old) {
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column" . " DROP DEFAULT");
+ }
+
+ # If we went from no default to a default, or we changed the default.
+ elsif ((defined $default && !defined $default_old)
+ || ($default ne $default_old))
+ {
+ push(@statements,
+ "ALTER TABLE $table ALTER COLUMN $column " . " SET DEFAULT $default");
+ }
+
+ # If we went from NULL to NOT NULL.
+ if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
+ push(@statements, $self->_set_nulls_sql(@_));
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column" . " SET NOT NULL");
+ }
+
+ # If we went from NOT NULL to NULL
+ elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column" . " DROP NOT NULL");
+ }
+
+ # If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
+ if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
+ push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
+ }
+
+ # If we went from being a PK to not being a PK
+ elsif ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
+ push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
+ }
+
+ return @statements;
}
# Helps handle any fields that were NULL before, if we have a default,
# when doing an ALTER COLUMN.
sub _set_nulls_sql {
- my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
- my $default = $new_def->{DEFAULT};
- # If we have a set_nulls_to, that overrides the DEFAULT
- # (although nobody would usually specify both a default and
- # a set_nulls_to.)
- $default = $set_nulls_to if defined $set_nulls_to;
- if (defined $default) {
- my $specific = $self->{db_specific};
- $default = $specific->{$default} if exists $specific->{$default};
- }
- my @sql;
- if (defined $default) {
- push(@sql, "UPDATE $table SET $column = $default"
- . " WHERE $column IS NULL");
- }
- return @sql;
+ my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+ my $default = $new_def->{DEFAULT};
+
+ # If we have a set_nulls_to, that overrides the DEFAULT
+ # (although nobody would usually specify both a default and
+ # a set_nulls_to.)
+ $default = $set_nulls_to if defined $set_nulls_to;
+ if (defined $default) {
+ my $specific = $self->{db_specific};
+ $default = $specific->{$default} if exists $specific->{$default};
+ }
+ my @sql;
+ if (defined $default) {
+ push(@sql, "UPDATE $table SET $column = $default" . " WHERE $column IS NULL");
+ }
+ return @sql;
}
sub get_drop_index_ddl {
@@ -2529,11 +2497,11 @@ sub get_drop_index_ddl {
=cut
- my ($self, $table, $name) = @_;
+ my ($self, $table, $name) = @_;
- # Although ANSI SQL-92 doesn't specify a method of dropping an index,
- # many DBs support this syntax.
- return ("DROP INDEX $name");
+ # Although ANSI SQL-92 doesn't specify a method of dropping an index,
+ # many DBs support this syntax.
+ return ("DROP INDEX $name");
}
sub get_drop_column_ddl {
@@ -2547,8 +2515,8 @@ sub get_drop_column_ddl {
=cut
- my ($self, $table, $column) = @_;
- return ("ALTER TABLE $table DROP COLUMN $column");
+ my ($self, $table, $column) = @_;
+ return ("ALTER TABLE $table DROP COLUMN $column");
}
=item C<get_drop_table_ddl($table)>
@@ -2560,8 +2528,8 @@ sub get_drop_column_ddl {
=cut
sub get_drop_table_ddl {
- my ($self, $table) = @_;
- return ("DROP TABLE $table");
+ my ($self, $table) = @_;
+ return ("DROP TABLE $table");
}
sub get_rename_column_ddl {
@@ -2579,8 +2547,8 @@ sub get_rename_column_ddl {
=cut
- die "ANSI SQL has no way to rename a column, and your database driver\n"
- . " has not implemented a method.";
+ die "ANSI SQL has no way to rename a column, and your database driver\n"
+ . " has not implemented a method.";
}
@@ -2610,8 +2578,8 @@ Gets SQL to rename a table in the database.
=cut
- my ($self, $old_name, $new_name) = @_;
- return ("ALTER TABLE $old_name RENAME TO $new_name");
+ my ($self, $old_name, $new_name) = @_;
+ return ("ALTER TABLE $old_name RENAME TO $new_name");
}
=item C<delete_table($name)>
@@ -2624,13 +2592,13 @@ Gets SQL to rename a table in the database.
=cut
sub delete_table {
- my ($self, $name) = @_;
+ my ($self, $name) = @_;
- die "Attempted to delete nonexistent table '$name'." unless
- $self->get_table_abstract($name);
+ die "Attempted to delete nonexistent table '$name'."
+ unless $self->get_table_abstract($name);
- delete $self->{abstract_schema}->{$name};
- delete $self->{schema}->{$name};
+ delete $self->{abstract_schema}->{$name};
+ delete $self->{schema}->{$name};
}
sub get_column_abstract {
@@ -2647,15 +2615,15 @@ sub get_column_abstract {
=cut
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- if ($self->get_table_abstract($table)) {
- my %fields = (@{ $self->{abstract_schema}{$table}{FIELDS} });
- return $fields{$column};
- }
- return undef;
+ # Prevent a possible dereferencing of an undef hash, if the
+ # table doesn't exist.
+ if ($self->get_table_abstract($table)) {
+ my %fields = (@{$self->{abstract_schema}{$table}{FIELDS}});
+ return $fields{$column};
+ }
+ return undef;
}
=item C<get_indexes_on_column_abstract($table, $column)>
@@ -2673,29 +2641,31 @@ sub get_column_abstract {
=cut
sub get_indexes_on_column_abstract {
- my ($self, $table, $column) = @_;
- my %ret_hash;
-
- my $table_def = $self->get_table_abstract($table);
- if ($table_def && exists $table_def->{INDEXES}) {
- my %indexes = (@{ $table_def->{INDEXES} });
- foreach my $index_name (keys %indexes) {
- my $col_list;
- # Get the column list, depending on whether the index
- # is in hashref or arrayref format.
- if (ref($indexes{$index_name}) eq 'HASH') {
- $col_list = $indexes{$index_name}->{FIELDS};
- } else {
- $col_list = $indexes{$index_name};
- }
-
- if(grep($_ eq $column, @$col_list)) {
- $ret_hash{$index_name} = dclone($indexes{$index_name});
- }
- }
+ my ($self, $table, $column) = @_;
+ my %ret_hash;
+
+ my $table_def = $self->get_table_abstract($table);
+ if ($table_def && exists $table_def->{INDEXES}) {
+ my %indexes = (@{$table_def->{INDEXES}});
+ foreach my $index_name (keys %indexes) {
+ my $col_list;
+
+ # Get the column list, depending on whether the index
+ # is in hashref or arrayref format.
+ if (ref($indexes{$index_name}) eq 'HASH') {
+ $col_list = $indexes{$index_name}->{FIELDS};
+ }
+ else {
+ $col_list = $indexes{$index_name};
+ }
+
+ if (grep($_ eq $column, @$col_list)) {
+ $ret_hash{$index_name} = dclone($indexes{$index_name});
+ }
}
+ }
- return %ret_hash;
+ return %ret_hash;
}
sub get_index_abstract {
@@ -2711,16 +2681,16 @@ sub get_index_abstract {
=cut
- my ($self, $table, $index) = @_;
+ my ($self, $table, $index) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- my $index_table = $self->get_table_abstract($table);
- if ($index_table && exists $index_table->{INDEXES}) {
- my %indexes = (@{ $index_table->{INDEXES} });
- return $indexes{$index};
- }
- return undef;
+ # Prevent a possible dereferencing of an undef hash, if the
+ # table doesn't exist.
+ my $index_table = $self->get_table_abstract($table);
+ if ($index_table && exists $index_table->{INDEXES}) {
+ my %indexes = (@{$index_table->{INDEXES}});
+ return $indexes{$index};
+ }
+ return undef;
}
=item C<get_table_abstract($table)>
@@ -2734,8 +2704,8 @@ sub get_index_abstract {
=cut
sub get_table_abstract {
- my ($self, $table) = @_;
- return $self->{abstract_schema}->{$table};
+ my ($self, $table) = @_;
+ return $self->{abstract_schema}->{$table};
}
=item C<add_table($name, \%definition)>
@@ -2751,22 +2721,20 @@ sub get_table_abstract {
=cut
sub add_table {
- my ($self, $name, $definition) = @_;
- (die "Table already exists: $name")
- if exists $self->{abstract_schema}->{$name};
- if ($definition) {
- $self->{abstract_schema}->{$name} = dclone($definition);
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
- }
- else {
- $self->{abstract_schema}->{$name} = {FIELDS => []};
- $self->{schema}->{$name} = {FIELDS => []};
- }
+ my ($self, $name, $definition) = @_;
+ (die "Table already exists: $name") if exists $self->{abstract_schema}->{$name};
+ if ($definition) {
+ $self->{abstract_schema}->{$name} = dclone($definition);
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
+ }
+ else {
+ $self->{abstract_schema}->{$name} = {FIELDS => []};
+ $self->{schema}->{$name} = {FIELDS => []};
+ }
}
-
sub rename_table {
=item C<rename_table>
@@ -2776,10 +2744,10 @@ Renames a table from C<$old_name> to C<$new_name> in this Schema object.
=cut
- my ($self, $old_name, $new_name) = @_;
- my $table = $self->get_table_abstract($old_name);
- $self->delete_table($old_name);
- $self->add_table($new_name, $table);
+ my ($self, $old_name, $new_name) = @_;
+ my $table = $self->get_table_abstract($old_name);
+ $self->delete_table($old_name);
+ $self->add_table($new_name, $table);
}
sub delete_column {
@@ -2794,17 +2762,18 @@ sub delete_column {
=cut
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- my $abstract_fields = $self->{abstract_schema}{$table}{FIELDS};
- my $name_position = firstidx { $_ eq $column } @$abstract_fields;
- die "Attempted to delete nonexistent column ${table}.${column}"
- if $name_position == -1;
- # Delete the key/value pair from the array.
- splice(@$abstract_fields, $name_position, 2);
+ my $abstract_fields = $self->{abstract_schema}{$table}{FIELDS};
+ my $name_position = firstidx { $_ eq $column } @$abstract_fields;
+ die "Attempted to delete nonexistent column ${table}.${column}"
+ if $name_position == -1;
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
+ # Delete the key/value pair from the array.
+ splice(@$abstract_fields, $name_position, 2);
+
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
}
sub rename_column {
@@ -2820,11 +2789,11 @@ sub rename_column {
=cut
- my ($self, $table, $old_name, $new_name) = @_;
- my $def = $self->get_column_abstract($table, $old_name);
- die "Renaming a column that doesn't exist" if !$def;
- $self->delete_column($table, $old_name);
- $self->set_column($table, $new_name, $def);
+ my ($self, $table, $old_name, $new_name) = @_;
+ my $def = $self->get_column_abstract($table, $old_name);
+ die "Renaming a column that doesn't exist" if !$def;
+ $self->delete_column($table, $old_name);
+ $self->set_column($table, $new_name, $def);
}
sub set_column {
@@ -2845,10 +2814,10 @@ sub set_column {
=cut
- my ($self, $table, $column, $new_def) = @_;
+ my ($self, $table, $column, $new_def) = @_;
- my $fields = $self->{abstract_schema}{$table}{FIELDS};
- $self->_set_object($table, $column, $new_def, $fields);
+ my $fields = $self->{abstract_schema}{$table}{FIELDS};
+ $self->_set_object($table, $column, $new_def, $fields);
}
=item C<set_fk($table, $column \%fk_def)>
@@ -2858,19 +2827,20 @@ Sets the C<REFERENCES> item on the specified column.
=cut
sub set_fk {
- my ($self, $table, $column, $fk_def) = @_;
- # Don't want to modify the source def before we explicitly set it below.
- # This is just us being extra-cautious.
- my $column_def = dclone($self->get_column_abstract($table, $column));
- die "Tried to set an fk on $table.$column, but that column doesn't exist"
- if !$column_def;
- if ($fk_def) {
- $column_def->{REFERENCES} = $fk_def;
- }
- else {
- delete $column_def->{REFERENCES};
- }
- $self->set_column($table, $column, $column_def);
+ my ($self, $table, $column, $fk_def) = @_;
+
+ # Don't want to modify the source def before we explicitly set it below.
+ # This is just us being extra-cautious.
+ my $column_def = dclone($self->get_column_abstract($table, $column));
+ die "Tried to set an fk on $table.$column, but that column doesn't exist"
+ if !$column_def;
+ if ($fk_def) {
+ $column_def->{REFERENCES} = $fk_def;
+ }
+ else {
+ delete $column_def->{REFERENCES};
+ }
+ $self->set_column($table, $column, $column_def);
}
sub set_index {
@@ -2891,36 +2861,39 @@ sub set_index {
=cut
- my ($self, $table, $name, $definition) = @_;
+ my ($self, $table, $name, $definition) = @_;
- if ( exists $self->{abstract_schema}{$table}
- && !exists $self->{abstract_schema}{$table}{INDEXES} ) {
- $self->{abstract_schema}{$table}{INDEXES} = [];
- }
+ if (exists $self->{abstract_schema}{$table}
+ && !exists $self->{abstract_schema}{$table}{INDEXES})
+ {
+ $self->{abstract_schema}{$table}{INDEXES} = [];
+ }
- my $indexes = $self->{abstract_schema}{$table}{INDEXES};
- $self->_set_object($table, $name, $definition, $indexes);
+ my $indexes = $self->{abstract_schema}{$table}{INDEXES};
+ $self->_set_object($table, $name, $definition, $indexes);
}
# A private helper for set_index and set_column.
# This does the actual "work" of those two functions.
# $array_to_change is an arrayref.
sub _set_object {
- my ($self, $table, $name, $definition, $array_to_change) = @_;
+ my ($self, $table, $name, $definition, $array_to_change) = @_;
- my $obj_position = (firstidx { $_ eq $name } @$array_to_change) + 1;
- # If the object doesn't exist, then add it.
- if (!$obj_position) {
- push(@$array_to_change, $name);
- push(@$array_to_change, $definition);
- }
- # We're modifying an existing object in the Schema.
- else {
- splice(@$array_to_change, $obj_position, 1, $definition);
- }
+ my $obj_position = (firstidx { $_ eq $name } @$array_to_change) + 1;
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
+ # If the object doesn't exist, then add it.
+ if (!$obj_position) {
+ push(@$array_to_change, $name);
+ push(@$array_to_change, $definition);
+ }
+
+ # We're modifying an existing object in the Schema.
+ else {
+ splice(@$array_to_change, $obj_position, 1, $definition);
+ }
+
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
}
=item C<delete_index($table, $name)>
@@ -2938,16 +2911,17 @@ sub _set_object {
=cut
sub delete_index {
- my ($self, $table, $name) = @_;
-
- my $indexes = $self->{abstract_schema}{$table}{INDEXES};
- my $name_position = firstidx { $_ eq $name } @$indexes;
- die "Attempted to delete nonexistent index $name on the $table table"
- if $name_position == -1;
- # Delete the key/value pair from the array.
- splice(@$indexes, $name_position, 2);
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
+ my ($self, $table, $name) = @_;
+
+ my $indexes = $self->{abstract_schema}{$table}{INDEXES};
+ my $name_position = firstidx { $_ eq $name } @$indexes;
+ die "Attempted to delete nonexistent index $name on the $table table"
+ if $name_position == -1;
+
+ # Delete the key/value pair from the array.
+ splice(@$indexes, $name_position, 2);
+ $self->{schema} = dclone($self->{abstract_schema});
+ $self->_adjust_schema();
}
sub columns_equal {
@@ -2965,24 +2939,24 @@ sub columns_equal {
=cut
- my $self = shift;
- my $col_one = dclone(shift);
- my $col_two = dclone(shift);
+ my $self = shift;
+ my $col_one = dclone(shift);
+ my $col_two = dclone(shift);
- $col_one->{TYPE} = uc($col_one->{TYPE});
- $col_two->{TYPE} = uc($col_two->{TYPE});
+ $col_one->{TYPE} = uc($col_one->{TYPE});
+ $col_two->{TYPE} = uc($col_two->{TYPE});
- # We don't care about foreign keys when comparing column definitions.
- delete $col_one->{REFERENCES};
- delete $col_two->{REFERENCES};
+ # We don't care about foreign keys when comparing column definitions.
+ delete $col_one->{REFERENCES};
+ delete $col_two->{REFERENCES};
- my @col_one_array = %$col_one;
- my @col_two_array = %$col_two;
+ my @col_one_array = %$col_one;
+ my @col_two_array = %$col_two;
- my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
+ my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
- # If there are no differences between the arrays, then they are equal.
- return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
+ # If there are no differences between the arrays, then they are equal.
+ return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
}
@@ -3006,18 +2980,18 @@ sub columns_equal {
=cut
sub serialize_abstract {
- my ($self) = @_;
+ my ($self) = @_;
- # Make it ok to eval
- local $Data::Dumper::Purity = 1;
+ # Make it ok to eval
+ local $Data::Dumper::Purity = 1;
- # Avoid cross-refs
- local $Data::Dumper::Deepcopy = 1;
+ # Avoid cross-refs
+ local $Data::Dumper::Deepcopy = 1;
- # Always sort keys to allow textual compare
- local $Data::Dumper::Sortkeys = 1;
+ # Always sort keys to allow textual compare
+ local $Data::Dumper::Sortkeys = 1;
- return Dumper($self->{abstract_schema});
+ return Dumper($self->{abstract_schema});
}
=item C<deserialize_abstract($serialized, $version)>
@@ -3036,36 +3010,34 @@ sub serialize_abstract {
=cut
sub deserialize_abstract {
- my ($class, $serialized, $version) = @_;
-
- my $thawed_hash;
- if ($version < 2) {
- $thawed_hash = thaw($serialized);
- }
- else {
- my $cpt = new Safe;
- $cpt->reval($serialized) ||
- die "Unable to restore cached schema: " . $@;
- $thawed_hash = ${$cpt->varglob('VAR1')};
- }
-
- # Version 2 didn't have the "created" key for REFERENCES items.
- if ($version < 3) {
- my $standard = $class->new()->{abstract_schema};
- foreach my $table_name (keys %$thawed_hash) {
- my %standard_fields =
- @{ $standard->{$table_name}->{FIELDS} || [] };
- my $table = $thawed_hash->{$table_name};
- my %fields = @{ $table->{FIELDS} || [] };
- while (my ($field, $def) = each %fields) {
- if (exists $def->{REFERENCES}) {
- $def->{REFERENCES}->{created} = 1;
- }
- }
+ my ($class, $serialized, $version) = @_;
+
+ my $thawed_hash;
+ if ($version < 2) {
+ $thawed_hash = thaw($serialized);
+ }
+ else {
+ my $cpt = new Safe;
+ $cpt->reval($serialized) || die "Unable to restore cached schema: " . $@;
+ $thawed_hash = ${$cpt->varglob('VAR1')};
+ }
+
+ # Version 2 didn't have the "created" key for REFERENCES items.
+ if ($version < 3) {
+ my $standard = $class->new()->{abstract_schema};
+ foreach my $table_name (keys %$thawed_hash) {
+ my %standard_fields = @{$standard->{$table_name}->{FIELDS} || []};
+ my $table = $thawed_hash->{$table_name};
+ my %fields = @{$table->{FIELDS} || []};
+ while (my ($field, $def) = each %fields) {
+ if (exists $def->{REFERENCES}) {
+ $def->{REFERENCES}->{created} = 1;
}
+ }
}
+ }
- return $class->new(undef, $thawed_hash);
+ return $class->new(undef, $thawed_hash);
}
#####################################################################
@@ -3093,8 +3065,8 @@ object.
=cut
sub get_empty_schema {
- my ($class) = @_;
- return $class->deserialize_abstract(Dumper({}), SCHEMA_VERSION);
+ my ($class) = @_;
+ return $class->deserialize_abstract(Dumper({}), SCHEMA_VERSION);
}
1;
diff --git a/Bugzilla/DB/Schema/Mysql.pm b/Bugzilla/DB/Schema/Mysql.pm
index 79814140a..0b8ee59c3 100644
--- a/Bugzilla/DB/Schema/Mysql.pm
+++ b/Bugzilla/DB/Schema/Mysql.pm
@@ -34,198 +34,218 @@ use base qw(Bugzilla::DB::Schema);
# THIS CONSTANT IS ONLY USED FOR UPGRADES FROM 2.18 OR EARLIER. DON'T
# UPDATE IT TO MODERN COLUMN NAMES OR DEFINITIONS.
use constant BOOLEAN_MAP => {
- bugs => {everconfirmed => 1, reporter_accessible => 1,
- cclist_accessible => 1, qacontact_accessible => 1,
- assignee_accessible => 1},
- longdescs => {isprivate => 1, already_wrapped => 1},
- attachments => {ispatch => 1, isobsolete => 1, isprivate => 1},
- flags => {is_active => 1},
- flagtypes => {is_active => 1, is_requestable => 1,
- is_requesteeble => 1, is_multiplicable => 1},
- fielddefs => {mailhead => 1, obsolete => 1},
- bug_status => {isactive => 1},
- resolution => {isactive => 1},
- bug_severity => {isactive => 1},
- priority => {isactive => 1},
- rep_platform => {isactive => 1},
- op_sys => {isactive => 1},
- profiles => {mybugslink => 1, newemailtech => 1},
- namedqueries => {linkinfooter => 1, watchfordiffs => 1},
- groups => {isbuggroup => 1, isactive => 1},
- group_control_map => {entry => 1, membercontrol => 1, othercontrol => 1,
- canedit => 1},
- group_group_map => {isbless => 1},
- user_group_map => {isbless => 1, isderived => 1},
- products => {disallownew => 1},
- series => {public => 1},
- whine_queries => {onemailperbug => 1},
- quips => {approved => 1},
- setting => {is_enabled => 1}
+ bugs => {
+ everconfirmed => 1,
+ reporter_accessible => 1,
+ cclist_accessible => 1,
+ qacontact_accessible => 1,
+ assignee_accessible => 1
+ },
+ longdescs => {isprivate => 1, already_wrapped => 1},
+ attachments => {ispatch => 1, isobsolete => 1, isprivate => 1},
+ flags => {is_active => 1},
+ flagtypes => {
+ is_active => 1,
+ is_requestable => 1,
+ is_requesteeble => 1,
+ is_multiplicable => 1
+ },
+ fielddefs => {mailhead => 1, obsolete => 1},
+ bug_status => {isactive => 1},
+ resolution => {isactive => 1},
+ bug_severity => {isactive => 1},
+ priority => {isactive => 1},
+ rep_platform => {isactive => 1},
+ op_sys => {isactive => 1},
+ profiles => {mybugslink => 1, newemailtech => 1},
+ namedqueries => {linkinfooter => 1, watchfordiffs => 1},
+ groups => {isbuggroup => 1, isactive => 1},
+ group_control_map =>
+ {entry => 1, membercontrol => 1, othercontrol => 1, canedit => 1},
+ group_group_map => {isbless => 1},
+ user_group_map => {isbless => 1, isderived => 1},
+ products => {disallownew => 1},
+ series => {public => 1},
+ whine_queries => {onemailperbug => 1},
+ quips => {approved => 1},
+ setting => {is_enabled => 1}
};
# Maps the db_specific hash backwards, for use in column_info_to_column.
use constant REVERSE_MAPPING => {
- # Boolean and the SERIAL fields are handled in column_info_to_column,
- # and so don't have an entry here.
- TINYINT => 'INT1',
- SMALLINT => 'INT2',
- MEDIUMINT => 'INT3',
- INTEGER => 'INT4',
-
- # All the other types have the same name in their abstract version
- # as in their db-specific version, so no reverse mapping is needed.
+
+ # Boolean and the SERIAL fields are handled in column_info_to_column,
+ # and so don't have an entry here.
+ TINYINT => 'INT1',
+ SMALLINT => 'INT2',
+ MEDIUMINT => 'INT3',
+ INTEGER => 'INT4',
+
+ # All the other types have the same name in their abstract version
+ # as in their db-specific version, so no reverse mapping is needed.
};
#------------------------------------------------------------------------------
sub _initialize {
- my $self = shift;
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
- $self = $self->SUPER::_initialize(@_);
+ $self->{db_specific} = {
- $self->{db_specific} = {
+ BOOLEAN => 'tinyint',
+ FALSE => '0',
+ TRUE => '1',
- BOOLEAN => 'tinyint',
- FALSE => '0',
- TRUE => '1',
+ INT1 => 'tinyint',
+ INT2 => 'smallint',
+ INT3 => 'mediumint',
+ INT4 => 'integer',
- INT1 => 'tinyint',
- INT2 => 'smallint',
- INT3 => 'mediumint',
- INT4 => 'integer',
+ SMALLSERIAL => 'smallint auto_increment',
+ MEDIUMSERIAL => 'mediumint auto_increment',
+ INTSERIAL => 'integer auto_increment',
- SMALLSERIAL => 'smallint auto_increment',
- MEDIUMSERIAL => 'mediumint auto_increment',
- INTSERIAL => 'integer auto_increment',
+ TINYTEXT => 'tinytext',
+ MEDIUMTEXT => 'mediumtext',
+ LONGTEXT => 'mediumtext',
+ TEXT => 'text',
- TINYTEXT => 'tinytext',
- MEDIUMTEXT => 'mediumtext',
- LONGTEXT => 'mediumtext',
- TEXT => 'text',
+ LONGBLOB => 'longblob',
- LONGBLOB => 'longblob',
+ NATIVE_DATETIME => 'datetime',
+ DATETIME => 'timestamp',
+ DATE => 'date',
+ };
- NATIVE_DATETIME => 'datetime',
- DATETIME => 'timestamp',
- DATE => 'date',
- };
+ $self->_adjust_schema;
- $self->_adjust_schema;
+ return $self;
- return $self;
+} #eosub--_initialize
-} #eosub--_initialize
#------------------------------------------------------------------------------
sub _get_create_table_ddl {
- # Returns a "create table" SQL statement.
- my($self, $table) = @_;
- my $charset = Bugzilla::DB::Mysql->utf8_charset;
- my $collate = Bugzilla::DB::Mysql->utf8_collate;
- my $row_format = Bugzilla::DB::Mysql->default_row_format($table);
- my @parts = (
- $self->SUPER::_get_create_table_ddl($table),
- 'ENGINE = InnoDB',
- "CHARACTER SET $charset COLLATE $collate",
- "ROW_FORMAT=$row_format",
- );
- return join(' ', @parts);
-} #eosub--_get_create_table_ddl
+
+ # Returns a "create table" SQL statement.
+ my ($self, $table) = @_;
+ my $charset = Bugzilla::DB::Mysql->utf8_charset;
+ my $collate = Bugzilla::DB::Mysql->utf8_collate;
+ my $row_format = Bugzilla::DB::Mysql->default_row_format($table);
+ my @parts = (
+ $self->SUPER::_get_create_table_ddl($table), 'ENGINE = InnoDB',
+ "CHARACTER SET $charset COLLATE $collate", "ROW_FORMAT=$row_format",
+ );
+ return join(' ', @parts);
+} #eosub--_get_create_table_ddl
+
#------------------------------------------------------------------------------
sub _get_create_index_ddl {
- # Extend superclass method to create FULLTEXT indexes on text fields.
- # Returns a "create index" SQL statement.
- my($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+ # Extend superclass method to create FULLTEXT indexes on text fields.
+ # Returns a "create index" SQL statement.
+
+ my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
- my $sql = "CREATE ";
- $sql .= "$index_type " if ($index_type eq 'UNIQUE'
- || $index_type eq 'FULLTEXT');
- $sql .= "INDEX \`$index_name\` ON $table_name \(" .
- join(", ", @$index_fields) . "\)";
+ my $sql = "CREATE ";
+ $sql .= "$index_type "
+ if ($index_type eq 'UNIQUE' || $index_type eq 'FULLTEXT');
+ $sql .= "INDEX \`$index_name\` ON $table_name \("
+ . join(", ", @$index_fields) . "\)";
- return($sql);
+ return ($sql);
+
+} #eosub--_get_create_index_ddl
-} #eosub--_get_create_index_ddl
#--------------------------------------------------------------------
sub get_create_database_sql {
- my ($self, $name) = @_;
- # We only create as utf8 if we have no params (meaning we're doing
- # a new installation) or if the utf8 param is on.
- my $charset = Bugzilla::DB::Mysql->utf8_charset;
- my $collate = Bugzilla::DB::Mysql->utf8_collate;
- return ("CREATE DATABASE $name CHARACTER SET $charset COLLATE $collate");
+ my ($self, $name) = @_;
+
+ # We only create as utf8 if we have no params (meaning we're doing
+ # a new installation) or if the utf8 param is on.
+ my $charset = Bugzilla::DB::Mysql->utf8_charset;
+ my $collate = Bugzilla::DB::Mysql->utf8_collate;
+ return ("CREATE DATABASE $name CHARACTER SET $charset COLLATE $collate");
}
# MySQL has a simpler ALTER TABLE syntax than ANSI.
sub get_alter_column_ddl {
- my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
- my $old_def = $self->get_column($table, $column);
- my %new_def_copy = %$new_def;
- if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
- # If a column stays a primary key do NOT specify PRIMARY KEY in the
- # ALTER TABLE statement. This avoids a MySQL error that two primary
- # keys are not allowed.
- delete $new_def_copy{PRIMARYKEY};
- }
-
- my @statements;
-
- push(@statements, "UPDATE $table SET $column = $set_nulls_to
- WHERE $column IS NULL") if defined $set_nulls_to;
-
- # Calling SET DEFAULT or DROP DEFAULT is *way* faster than calling
- # CHANGE COLUMN, so just do that if we're just changing the default.
- my %old_defaultless = %$old_def;
- my %new_defaultless = %$new_def;
- delete $old_defaultless{DEFAULT};
- delete $new_defaultless{DEFAULT};
- if (!$self->columns_equal($old_def, $new_def)
- && $self->columns_equal(\%new_defaultless, \%old_defaultless))
- {
- if (!defined $new_def->{DEFAULT}) {
- push(@statements,
- "ALTER TABLE $table ALTER COLUMN $column DROP DEFAULT");
- }
- else {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column
- SET DEFAULT " . $new_def->{DEFAULT});
- }
+ my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+ my $old_def = $self->get_column($table, $column);
+ my %new_def_copy = %$new_def;
+ if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
+
+ # If a column stays a primary key do NOT specify PRIMARY KEY in the
+ # ALTER TABLE statement. This avoids a MySQL error that two primary
+ # keys are not allowed.
+ delete $new_def_copy{PRIMARYKEY};
+ }
+
+ my @statements;
+
+ push(
+ @statements, "UPDATE $table SET $column = $set_nulls_to
+ WHERE $column IS NULL"
+ ) if defined $set_nulls_to;
+
+ # Calling SET DEFAULT or DROP DEFAULT is *way* faster than calling
+ # CHANGE COLUMN, so just do that if we're just changing the default.
+ my %old_defaultless = %$old_def;
+ my %new_defaultless = %$new_def;
+ delete $old_defaultless{DEFAULT};
+ delete $new_defaultless{DEFAULT};
+ if (!$self->columns_equal($old_def, $new_def)
+ && $self->columns_equal(\%new_defaultless, \%old_defaultless))
+ {
+ if (!defined $new_def->{DEFAULT}) {
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column DROP DEFAULT");
}
else {
- my $new_ddl = $self->get_type_ddl(\%new_def_copy);
- push(@statements, "ALTER TABLE $table CHANGE COLUMN
- $column $column $new_ddl");
+ push(
+ @statements, "ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT " . $new_def->{DEFAULT}
+ );
}
+ }
+ else {
+ my $new_ddl = $self->get_type_ddl(\%new_def_copy);
+ push(
+ @statements, "ALTER TABLE $table CHANGE COLUMN
+ $column $column $new_ddl"
+ );
+ }
- if ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
- # Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
- push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
- }
+ if ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
+
+ # Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
+ push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
+ }
- return @statements;
+ return @statements;
}
sub get_drop_fk_sql {
- my ($self, $table, $column, $references) = @_;
- my $fk_name = $self->_get_fk_name($table, $column, $references);
- my @sql = ("ALTER TABLE $table DROP FOREIGN KEY $fk_name");
- my $dbh = Bugzilla->dbh;
-
- # MySQL requires, and will create, an index on any column with
- # an FK. It will name it after the fk, which we never do.
- # So if there's an index named after the fk, we also have to delete it.
- if ($dbh->bz_index_info_real($table, $fk_name)) {
- push(@sql, $self->get_drop_index_ddl($table, $fk_name));
- }
-
- return @sql;
+ my ($self, $table, $column, $references) = @_;
+ my $fk_name = $self->_get_fk_name($table, $column, $references);
+ my @sql = ("ALTER TABLE $table DROP FOREIGN KEY $fk_name");
+ my $dbh = Bugzilla->dbh;
+
+ # MySQL requires, and will create, an index on any column with
+ # an FK. It will name it after the fk, which we never do.
+ # So if there's an index named after the fk, we also have to delete it.
+ if ($dbh->bz_index_info_real($table, $fk_name)) {
+ push(@sql, $self->get_drop_index_ddl($table, $fk_name));
+ }
+
+ return @sql;
}
sub get_drop_index_ddl {
- my ($self, $table, $name) = @_;
- return ("DROP INDEX \`$name\` ON $table");
+ my ($self, $table, $name) = @_;
+ return ("DROP INDEX \`$name\` ON $table");
}
# A special function for MySQL, for renaming a lot of indexes.
@@ -235,29 +255,31 @@ sub get_drop_index_ddl {
# that contains the new index name.
# The indexes in %indexes must be in hashref format.
sub get_rename_indexes_ddl {
- my ($self, $table, %indexes) = @_;
- my @keys = keys %indexes or return ();
-
- my $sql = "ALTER TABLE $table ";
-
- foreach my $old_name (@keys) {
- my $name = $indexes{$old_name}->{NAME};
- my $type = $indexes{$old_name}->{TYPE};
- $type ||= 'INDEX';
- my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
- # $old_name needs to be escaped, sometimes, because it was
- # a reserved word.
- $old_name = '`' . $old_name . '`';
- $sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
- }
- # Remove the last comma.
- chop($sql);
- return ($sql);
+ my ($self, $table, %indexes) = @_;
+ my @keys = keys %indexes or return ();
+
+ my $sql = "ALTER TABLE $table ";
+
+ foreach my $old_name (@keys) {
+ my $name = $indexes{$old_name}->{NAME};
+ my $type = $indexes{$old_name}->{TYPE};
+ $type ||= 'INDEX';
+ my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
+
+ # $old_name needs to be escaped, sometimes, because it was
+ # a reserved word.
+ $old_name = '`' . $old_name . '`';
+ $sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
+ }
+
+ # Remove the last comma.
+ chop($sql);
+ return ($sql);
}
sub get_set_serial_sql {
- my ($self, $table, $column, $value) = @_;
- return ("ALTER TABLE $table AUTO_INCREMENT = $value");
+ my ($self, $table, $column, $value) = @_;
+ return ("ALTER TABLE $table AUTO_INCREMENT = $value");
}
# Converts a DBI column_info output to an abstract column definition.
@@ -265,145 +287,158 @@ sub get_set_serial_sql {
# although there's a chance that it will also work properly if called
# elsewhere.
sub column_info_to_column {
- my ($self, $column_info) = @_;
-
- # Unfortunately, we have to break Schema's normal "no database"
- # barrier a few times in this function.
- my $dbh = Bugzilla->dbh;
-
- my $table = $column_info->{TABLE_NAME};
- my $col_name = $column_info->{COLUMN_NAME};
-
- my $column = {};
-
- ($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
-
- if ($column_info->{mysql_is_pri_key}) {
- # In MySQL, if a table has no PK, but it has a UNIQUE index,
- # that index will show up as the PK. So we have to eliminate
- # that possibility.
- # Unfortunately, the only way to definitely solve this is
- # to break Schema's standard of not touching the live database
- # and check if the index called PRIMARY is on that field.
- my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
- if ( $pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}}) ) {
- $column->{PRIMARYKEY} = 1;
- }
- }
+ my ($self, $column_info) = @_;
- # MySQL frequently defines a default for a field even when we
- # didn't explicitly set one. So we have to have some special
- # hacks to determine whether or not we should actually put
- # a default in the abstract schema for this field.
- if (defined $column_info->{COLUMN_DEF}) {
- # The defaults that MySQL inputs automatically are usually
- # something that would be considered "false" by perl, either
- # a 0 or an empty string. (Except for datetime and decimal
- # fields, which have their own special auto-defaults.)
- #
- # Here's how we handle this: If it exists in the schema
- # without a default, then we don't use the default. If it
- # doesn't exist in the schema, then we're either going to
- # be dropping it soon, or it's a custom end-user column, in which
- # case having a bogus default won't harm anything.
- my $schema_column = $self->get_column($table, $col_name);
- unless ( (!$column_info->{COLUMN_DEF}
- || $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
- || $column_info->{COLUMN_DEF} eq '0.00')
- && $schema_column
- && !exists $schema_column->{DEFAULT}) {
-
- my $default = $column_info->{COLUMN_DEF};
- # Schema uses '0' for the defaults for decimal fields.
- $default = 0 if $default =~ /^0\.0+$/;
- # If we're not a number, we're a string and need to be
- # quoted.
- $default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
- $column->{DEFAULT} = $default;
- }
- }
+ # Unfortunately, we have to break Schema's normal "no database"
+ # barrier a few times in this function.
+ my $dbh = Bugzilla->dbh;
- my $type = $column_info->{TYPE_NAME};
+ my $table = $column_info->{TABLE_NAME};
+ my $col_name = $column_info->{COLUMN_NAME};
- # Certain types of columns need the size/precision appended.
- if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
- # This is nicely lowercase and has the size/precision appended.
- $type = $column_info->{mysql_type_name};
- }
+ my $column = {};
- # If we're a tinyint, we could be either a BOOLEAN or an INT1.
- # Only the BOOLEAN_MAP knows the difference.
- elsif ($type eq 'TINYINT' && exists BOOLEAN_MAP->{$table}
- && exists BOOLEAN_MAP->{$table}->{$col_name}) {
- $type = 'BOOLEAN';
- if (exists $column->{DEFAULT}) {
- $column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
- }
- }
+ ($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
- # We also need to check if we're an auto_increment field.
- elsif ($type =~ /INT/) {
- # Unfortunately, the only way to do this in DBI is to query the
- # database, so we have to break the rule here that Schema normally
- # doesn't touch the live DB.
- my $ref_sth = $dbh->prepare(
- "SELECT $col_name FROM $table LIMIT 1");
- $ref_sth->execute;
- if ($ref_sth->{mysql_is_auto_increment}->[0]) {
- if ($type eq 'MEDIUMINT') {
- $type = 'MEDIUMSERIAL';
- }
- elsif ($type eq 'SMALLINT') {
- $type = 'SMALLSERIAL';
- }
- else {
- $type = 'INTSERIAL';
- }
- }
- $ref_sth->finish;
+ if ($column_info->{mysql_is_pri_key}) {
+ # In MySQL, if a table has no PK, but it has a UNIQUE index,
+ # that index will show up as the PK. So we have to eliminate
+ # that possibility.
+ # Unfortunately, the only way to definitely solve this is
+ # to break Schema's standard of not touching the live database
+ # and check if the index called PRIMARY is on that field.
+ my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
+ if ($pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}})) {
+ $column->{PRIMARYKEY} = 1;
}
+ }
+
+ # MySQL frequently defines a default for a field even when we
+ # didn't explicitly set one. So we have to have some special
+ # hacks to determine whether or not we should actually put
+ # a default in the abstract schema for this field.
+ if (defined $column_info->{COLUMN_DEF}) {
+
+ # The defaults that MySQL inputs automatically are usually
+ # something that would be considered "false" by perl, either
+ # a 0 or an empty string. (Except for datetime and decimal
+ # fields, which have their own special auto-defaults.)
+ #
+ # Here's how we handle this: If it exists in the schema
+ # without a default, then we don't use the default. If it
+ # doesn't exist in the schema, then we're either going to
+ # be dropping it soon, or it's a custom end-user column, in which
+ # case having a bogus default won't harm anything.
+ my $schema_column = $self->get_column($table, $col_name);
+ unless (
+ (
+ !$column_info->{COLUMN_DEF}
+ || $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
+ || $column_info->{COLUMN_DEF} eq '0.00'
+ )
+ && $schema_column
+ && !exists $schema_column->{DEFAULT}
+ )
+ {
- # For all other db-specific types, check if they exist in
- # REVERSE_MAPPING and use the type found there.
- if (exists REVERSE_MAPPING->{$type}) {
- $type = REVERSE_MAPPING->{$type};
+ my $default = $column_info->{COLUMN_DEF};
+
+ # Schema uses '0' for the defaults for decimal fields.
+ $default = 0 if $default =~ /^0\.0+$/;
+
+ # If we're not a number, we're a string and need to be
+ # quoted.
+ $default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
+ $column->{DEFAULT} = $default;
+ }
+ }
+
+ my $type = $column_info->{TYPE_NAME};
+
+ # Certain types of columns need the size/precision appended.
+ if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
+
+ # This is nicely lowercase and has the size/precision appended.
+ $type = $column_info->{mysql_type_name};
+ }
+
+ # If we're a tinyint, we could be either a BOOLEAN or an INT1.
+ # Only the BOOLEAN_MAP knows the difference.
+ elsif ($type eq 'TINYINT'
+ && exists BOOLEAN_MAP->{$table}
+ && exists BOOLEAN_MAP->{$table}->{$col_name})
+ {
+ $type = 'BOOLEAN';
+ if (exists $column->{DEFAULT}) {
+ $column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
+ }
+ }
+
+ # We also need to check if we're an auto_increment field.
+ elsif ($type =~ /INT/) {
+
+ # Unfortunately, the only way to do this in DBI is to query the
+ # database, so we have to break the rule here that Schema normally
+ # doesn't touch the live DB.
+ my $ref_sth = $dbh->prepare("SELECT $col_name FROM $table LIMIT 1");
+ $ref_sth->execute;
+ if ($ref_sth->{mysql_is_auto_increment}->[0]) {
+ if ($type eq 'MEDIUMINT') {
+ $type = 'MEDIUMSERIAL';
+ }
+ elsif ($type eq 'SMALLINT') {
+ $type = 'SMALLSERIAL';
+ }
+ else {
+ $type = 'INTSERIAL';
+ }
}
+ $ref_sth->finish;
- $column->{TYPE} = $type;
+ }
- #print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
+ # For all other db-specific types, check if they exist in
+ # REVERSE_MAPPING and use the type found there.
+ if (exists REVERSE_MAPPING->{$type}) {
+ $type = REVERSE_MAPPING->{$type};
+ }
- return $column;
+ $column->{TYPE} = $type;
+
+ #print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
+
+ return $column;
}
sub get_rename_column_ddl {
- my ($self, $table, $old_name, $new_name) = @_;
- my $def = $self->get_type_ddl($self->get_column($table, $old_name));
- # MySQL doesn't like having the PRIMARY KEY statement in a rename.
- $def =~ s/PRIMARY KEY//i;
- return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
+ my ($self, $table, $old_name, $new_name) = @_;
+ my $def = $self->get_type_ddl($self->get_column($table, $old_name));
+
+ # MySQL doesn't like having the PRIMARY KEY statement in a rename.
+ $def =~ s/PRIMARY KEY//i;
+ return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
}
sub get_type_ddl {
- my $self = shift;
- my $type_ddl = $self->SUPER::get_type_ddl(@_);
-
- # TIMESTAMPS as of 5.6.6 still default to
- # 'NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
- # unless explicitly setup in the table definition. This will change in future releases
- # and can be disabled by using 'explicit_defaults_for_timestamp = 1' in my.cnf.
- # So instead, we explicitly setup TIMESTAMP types to not be automatic.
- if ($type_ddl =~ /^timestamp/i) {
- if ($type_ddl !~ /NOT NULL/) {
- $type_ddl .= ' NULL DEFAULT NULL';
- }
- if ($type_ddl =~ /NOT NULL/ && $type_ddl !~ /DEFAULT/) {
- $type_ddl .= ' DEFAULT CURRENT_TIMESTAMP';
- }
+ my $self = shift;
+ my $type_ddl = $self->SUPER::get_type_ddl(@_);
+
+# TIMESTAMPS as of 5.6.6 still default to
+# 'NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
+# unless explicitly setup in the table definition. This will change in future releases
+# and can be disabled by using 'explicit_defaults_for_timestamp = 1' in my.cnf.
+# So instead, we explicitly setup TIMESTAMP types to not be automatic.
+ if ($type_ddl =~ /^timestamp/i) {
+ if ($type_ddl !~ /NOT NULL/) {
+ $type_ddl .= ' NULL DEFAULT NULL';
+ }
+ if ($type_ddl =~ /NOT NULL/ && $type_ddl !~ /DEFAULT/) {
+ $type_ddl .= ' DEFAULT CURRENT_TIMESTAMP';
}
+ }
- return $type_ddl;
+ return $type_ddl;
}
1;
diff --git a/Bugzilla/DB/Schema/Oracle.pm b/Bugzilla/DB/Schema/Oracle.pm
index b67ddfd59..36f957820 100644
--- a/Bugzilla/DB/Schema/Oracle.pm
+++ b/Bugzilla/DB/Schema/Oracle.pm
@@ -21,8 +21,9 @@ use base qw(Bugzilla::DB::Schema);
use Carp qw(confess);
use Bugzilla::Util;
-use constant ADD_COLUMN => 'ADD';
+use constant ADD_COLUMN => 'ADD';
use constant MULTIPLE_FKS_IN_ALTER => 0;
+
# Whether this is true or not, this is what it needs to be in order for
# hash_identifier to maintain backwards compatibility with versions before
# 3.2rc2.
@@ -31,91 +32,95 @@ use constant MAX_IDENTIFIER_LEN => 27;
#------------------------------------------------------------------------------
sub _initialize {
- my $self = shift;
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
- $self = $self->SUPER::_initialize(@_);
+ $self->{db_specific} = {
- $self->{db_specific} = {
+ BOOLEAN => 'integer',
+ FALSE => '0',
+ TRUE => '1',
- BOOLEAN => 'integer',
- FALSE => '0',
- TRUE => '1',
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
- INT1 => 'integer',
- INT2 => 'integer',
- INT3 => 'integer',
- INT4 => 'integer',
+ SMALLSERIAL => 'integer',
+ MEDIUMSERIAL => 'integer',
+ INTSERIAL => 'integer',
- SMALLSERIAL => 'integer',
- MEDIUMSERIAL => 'integer',
- INTSERIAL => 'integer',
+ TINYTEXT => 'varchar(255)',
+ MEDIUMTEXT => 'varchar(4000)',
+ LONGTEXT => 'clob',
- TINYTEXT => 'varchar(255)',
- MEDIUMTEXT => 'varchar(4000)',
- LONGTEXT => 'clob',
+ LONGBLOB => 'blob',
- LONGBLOB => 'blob',
+ DATETIME => 'date',
+ DATE => 'date',
+ };
- DATETIME => 'date',
- DATE => 'date',
- };
+ $self->_adjust_schema;
- $self->_adjust_schema;
+ return $self;
- return $self;
+} #eosub--_initialize
-} #eosub--_initialize
#--------------------------------------------------------------------
sub get_table_ddl {
- my $self = shift;
- my $table = shift;
- unshift @_, $table;
- my @ddl = $self->SUPER::get_table_ddl(@_);
-
- my @fields = @{ $self->{abstract_schema}{$table}{FIELDS} || [] };
- while (@fields) {
- my $field_name = shift @fields;
- my $field_info = shift @fields;
- # Create triggers to deal with empty string.
- if ( $field_info->{TYPE} =~ /varchar|TEXT/i
- && $field_info->{NOTNULL} ) {
- push (@ddl, _get_notnull_trigger_ddl($table, $field_name));
- }
- # Create sequences and triggers to emulate SERIAL datatypes.
- if ( $field_info->{TYPE} =~ /SERIAL/i ) {
- push (@ddl, $self->_get_create_seq_ddl($table, $field_name));
- }
+ my $self = shift;
+ my $table = shift;
+ unshift @_, $table;
+ my @ddl = $self->SUPER::get_table_ddl(@_);
+
+ my @fields = @{$self->{abstract_schema}{$table}{FIELDS} || []};
+ while (@fields) {
+ my $field_name = shift @fields;
+ my $field_info = shift @fields;
+
+ # Create triggers to deal with empty string.
+ if ($field_info->{TYPE} =~ /varchar|TEXT/i && $field_info->{NOTNULL}) {
+ push(@ddl, _get_notnull_trigger_ddl($table, $field_name));
}
- return @ddl;
-} #eosub--get_table_ddl
+ # Create sequences and triggers to emulate SERIAL datatypes.
+ if ($field_info->{TYPE} =~ /SERIAL/i) {
+ push(@ddl, $self->_get_create_seq_ddl($table, $field_name));
+ }
+ }
+ return @ddl;
+
+} #eosub--get_table_ddl
# Extend superclass method to create Oracle Text indexes if index type
# is FULLTEXT from schema. Returns a "create index" SQL statement.
sub _get_create_index_ddl {
- my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
- $index_name = "idx_" . $self->_hash_identifier($index_name);
- if ($index_type eq 'FULLTEXT') {
- my $sql = "CREATE INDEX $index_name ON $table_name ("
- . join(',',@$index_fields)
- . ") INDEXTYPE IS CTXSYS.CONTEXT "
- . " PARAMETERS('LEXER BZ_LEX SYNC(ON COMMIT)')" ;
- return $sql;
- }
-
- return($self->SUPER::_get_create_index_ddl($table_name, $index_name,
- $index_fields, $index_type));
+ my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+ $index_name = "idx_" . $self->_hash_identifier($index_name);
+ if ($index_type eq 'FULLTEXT') {
+ my $sql
+ = "CREATE INDEX $index_name ON $table_name ("
+ . join(',', @$index_fields)
+ . ") INDEXTYPE IS CTXSYS.CONTEXT "
+ . " PARAMETERS('LEXER BZ_LEX SYNC(ON COMMIT)')";
+ return $sql;
+ }
+
+ return ($self->SUPER::_get_create_index_ddl(
+ $table_name, $index_name, $index_fields, $index_type
+ ));
}
sub get_drop_index_ddl {
- my $self = shift;
- my ($table, $name) = @_;
+ my $self = shift;
+ my ($table, $name) = @_;
- $name = 'idx_' . $self->_hash_identifier($name);
- return $self->SUPER::get_drop_index_ddl($table, $name);
+ $name = 'idx_' . $self->_hash_identifier($name);
+ return $self->SUPER::get_drop_index_ddl($table, $name);
}
# Oracle supports the use of FOREIGN KEY integrity constraints
@@ -124,30 +129,31 @@ sub get_drop_index_ddl {
# - Delete CASCADE
# - Delete SET NULL
sub get_fk_ddl {
- my $self = shift;
- my $ddl = $self->SUPER::get_fk_ddl(@_);
+ my $self = shift;
+ my $ddl = $self->SUPER::get_fk_ddl(@_);
- # iThe Bugzilla Oracle driver implements UPDATE via a trigger.
- $ddl =~ s/ON UPDATE \S+//i;
- # RESTRICT is the default for DELETE on Oracle and may not be specified.
- $ddl =~ s/ON DELETE RESTRICT//i;
+ # iThe Bugzilla Oracle driver implements UPDATE via a trigger.
+ $ddl =~ s/ON UPDATE \S+//i;
- return $ddl;
+ # RESTRICT is the default for DELETE on Oracle and may not be specified.
+ $ddl =~ s/ON DELETE RESTRICT//i;
+
+ return $ddl;
}
sub get_add_fks_sql {
- my $self = shift;
- my ($table, $column_fks) = @_;
- my @sql = $self->SUPER::get_add_fks_sql(@_);
-
- foreach my $column (keys %$column_fks) {
- my $fk = $column_fks->{$column};
- next if $fk->{UPDATE} && uc($fk->{UPDATE}) ne 'CASCADE';
- my $fk_name = $self->_get_fk_name($table, $column, $fk);
- my $to_column = $fk->{COLUMN};
- my $to_table = $fk->{TABLE};
-
- my $trigger = <<END;
+ my $self = shift;
+ my ($table, $column_fks) = @_;
+ my @sql = $self->SUPER::get_add_fks_sql(@_);
+
+ foreach my $column (keys %$column_fks) {
+ my $fk = $column_fks->{$column};
+ next if $fk->{UPDATE} && uc($fk->{UPDATE}) ne 'CASCADE';
+ my $fk_name = $self->_get_fk_name($table, $column, $fk);
+ my $to_column = $fk->{COLUMN};
+ my $to_table = $fk->{TABLE};
+
+ my $trigger = <<END;
CREATE OR REPLACE TRIGGER ${fk_name}_UC
AFTER UPDATE OF $to_column ON $to_table
REFERENCING NEW AS NEW OLD AS OLD
@@ -158,350 +164,370 @@ CREATE OR REPLACE TRIGGER ${fk_name}_UC
WHERE $column = :OLD.$to_column;
END ${fk_name}_UC;
END
- push(@sql, $trigger);
- }
+ push(@sql, $trigger);
+ }
- return @sql;
+ return @sql;
}
sub get_drop_fk_sql {
- my $self = shift;
- my ($table, $column, $references) = @_;
- my $fk_name = $self->_get_fk_name(@_);
- my @sql;
- if (!$references->{UPDATE} || $references->{UPDATE} =~ /CASCADE/i) {
- push(@sql, "DROP TRIGGER ${fk_name}_uc");
- }
- push(@sql, $self->SUPER::get_drop_fk_sql(@_));
- return @sql;
+ my $self = shift;
+ my ($table, $column, $references) = @_;
+ my $fk_name = $self->_get_fk_name(@_);
+ my @sql;
+ if (!$references->{UPDATE} || $references->{UPDATE} =~ /CASCADE/i) {
+ push(@sql, "DROP TRIGGER ${fk_name}_uc");
+ }
+ push(@sql, $self->SUPER::get_drop_fk_sql(@_));
+ return @sql;
}
sub _get_fk_name {
- my ($self, $table, $column, $references) = @_;
- my $to_table = $references->{TABLE};
- my $to_column = $references->{COLUMN};
- my $fk_name = "${table}_${column}_${to_table}_${to_column}";
- $fk_name = "fk_" . $self->_hash_identifier($fk_name);
+ my ($self, $table, $column, $references) = @_;
+ my $to_table = $references->{TABLE};
+ my $to_column = $references->{COLUMN};
+ my $fk_name = "${table}_${column}_${to_table}_${to_column}";
+ $fk_name = "fk_" . $self->_hash_identifier($fk_name);
- return $fk_name;
+ return $fk_name;
}
sub get_add_column_ddl {
- my $self = shift;
- my ($table, $column, $definition, $init_value) = @_;
- my @sql;
-
- # Create sequences and triggers to emulate SERIAL datatypes.
- if ($definition->{TYPE} =~ /SERIAL/i) {
- # Clone the definition to not alter the original one.
- my %def = %$definition;
- # Oracle requires to define the column is several steps.
- my $pk = delete $def{PRIMARYKEY};
- my $notnull = delete $def{NOTNULL};
- @sql = $self->SUPER::get_add_column_ddl($table, $column, \%def, $init_value);
- push(@sql, $self->_get_create_seq_ddl($table, $column));
- push(@sql, "UPDATE $table SET $column = ${table}_${column}_SEQ.NEXTVAL");
- push(@sql, "ALTER TABLE $table MODIFY $column NOT NULL") if $notnull;
- push(@sql, "ALTER TABLE $table ADD PRIMARY KEY ($column)") if $pk;
- }
- else {
- @sql = $self->SUPER::get_add_column_ddl(@_);
- # Create triggers to deal with empty string.
- if ($definition->{TYPE} =~ /varchar|TEXT/i && $definition->{NOTNULL}) {
- push(@sql, _get_notnull_trigger_ddl($table, $column));
- }
+ my $self = shift;
+ my ($table, $column, $definition, $init_value) = @_;
+ my @sql;
+
+ # Create sequences and triggers to emulate SERIAL datatypes.
+ if ($definition->{TYPE} =~ /SERIAL/i) {
+
+ # Clone the definition to not alter the original one.
+ my %def = %$definition;
+
+ # Oracle requires to define the column is several steps.
+ my $pk = delete $def{PRIMARYKEY};
+ my $notnull = delete $def{NOTNULL};
+ @sql = $self->SUPER::get_add_column_ddl($table, $column, \%def, $init_value);
+ push(@sql, $self->_get_create_seq_ddl($table, $column));
+ push(@sql, "UPDATE $table SET $column = ${table}_${column}_SEQ.NEXTVAL");
+ push(@sql, "ALTER TABLE $table MODIFY $column NOT NULL") if $notnull;
+ push(@sql, "ALTER TABLE $table ADD PRIMARY KEY ($column)") if $pk;
+ }
+ else {
+ @sql = $self->SUPER::get_add_column_ddl(@_);
+
+ # Create triggers to deal with empty string.
+ if ($definition->{TYPE} =~ /varchar|TEXT/i && $definition->{NOTNULL}) {
+ push(@sql, _get_notnull_trigger_ddl($table, $column));
}
+ }
- return @sql;
+ return @sql;
}
sub get_alter_column_ddl {
- my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
-
- my @statements;
- my $old_def = $self->get_column_abstract($table, $column);
- my $specific = $self->{db_specific};
-
- # If the types have changed, we have to deal with that.
- if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
- push(@statements, $self->_get_alter_type_sql($table, $column,
- $new_def, $old_def));
- }
-
- my $default = $new_def->{DEFAULT};
- my $default_old = $old_def->{DEFAULT};
-
- if (defined $default) {
- $default = $specific->{$default} if exists $specific->{$default};
- }
- # This first condition prevents "uninitialized value" errors.
- if (!defined $default && !defined $default_old) {
- # Do Nothing
- }
- # If we went from having a default to not having one
- elsif (!defined $default && defined $default_old) {
- push(@statements, "ALTER TABLE $table MODIFY $column"
- . " DEFAULT NULL");
- }
- # If we went from no default to a default, or we changed the default.
- elsif ( (defined $default && !defined $default_old) ||
- ($default ne $default_old) )
- {
- push(@statements, "ALTER TABLE $table MODIFY $column "
- . " DEFAULT $default");
- }
-
- # If we went from NULL to NOT NULL.
- if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
- my $setdefault;
- # Handle any fields that were NULL before, if we have a default,
- $setdefault = $default if defined $default;
- # But if we have a set_nulls_to, that overrides the DEFAULT
- # (although nobody would usually specify both a default and
- # a set_nulls_to.)
- $setdefault = $set_nulls_to if defined $set_nulls_to;
- if (defined $setdefault) {
- push(@statements, "UPDATE $table SET $column = $setdefault"
- . " WHERE $column IS NULL");
- }
- push(@statements, "ALTER TABLE $table MODIFY $column"
- . " NOT NULL");
- push (@statements, _get_notnull_trigger_ddl($table, $column))
- if $old_def->{TYPE} =~ /varchar|text/i
- && $new_def->{TYPE} =~ /varchar|text/i;
- }
- # If we went from NOT NULL to NULL
- elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
- push(@statements, "ALTER TABLE $table MODIFY $column"
- . " NULL");
- push(@statements, "DROP TRIGGER ${table}_${column}")
- if $new_def->{TYPE} =~ /varchar|text/i
- && $old_def->{TYPE} =~ /varchar|text/i;
- }
-
- # If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
- if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
- push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
- }
- # If we went from being a PK to not being a PK
- elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
- push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
+ my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+
+ my @statements;
+ my $old_def = $self->get_column_abstract($table, $column);
+ my $specific = $self->{db_specific};
+
+ # If the types have changed, we have to deal with that.
+ if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
+ push(@statements,
+ $self->_get_alter_type_sql($table, $column, $new_def, $old_def));
+ }
+
+ my $default = $new_def->{DEFAULT};
+ my $default_old = $old_def->{DEFAULT};
+
+ if (defined $default) {
+ $default = $specific->{$default} if exists $specific->{$default};
+ }
+
+ # This first condition prevents "uninitialized value" errors.
+ if (!defined $default && !defined $default_old) {
+
+ # Do Nothing
+ }
+
+ # If we went from having a default to not having one
+ elsif (!defined $default && defined $default_old) {
+ push(@statements, "ALTER TABLE $table MODIFY $column" . " DEFAULT NULL");
+ }
+
+ # If we went from no default to a default, or we changed the default.
+ elsif ((defined $default && !defined $default_old)
+ || ($default ne $default_old))
+ {
+ push(@statements, "ALTER TABLE $table MODIFY $column " . " DEFAULT $default");
+ }
+
+ # If we went from NULL to NOT NULL.
+ if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
+ my $setdefault;
+
+ # Handle any fields that were NULL before, if we have a default,
+ $setdefault = $default if defined $default;
+
+ # But if we have a set_nulls_to, that overrides the DEFAULT
+ # (although nobody would usually specify both a default and
+ # a set_nulls_to.)
+ $setdefault = $set_nulls_to if defined $set_nulls_to;
+ if (defined $setdefault) {
+ push(@statements,
+ "UPDATE $table SET $column = $setdefault" . " WHERE $column IS NULL");
}
-
- return @statements;
+ push(@statements, "ALTER TABLE $table MODIFY $column" . " NOT NULL");
+ push(@statements, _get_notnull_trigger_ddl($table, $column))
+ if $old_def->{TYPE} =~ /varchar|text/i && $new_def->{TYPE} =~ /varchar|text/i;
+ }
+
+ # If we went from NOT NULL to NULL
+ elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
+ push(@statements, "ALTER TABLE $table MODIFY $column" . " NULL");
+ push(@statements, "DROP TRIGGER ${table}_${column}")
+ if $new_def->{TYPE} =~ /varchar|text/i && $old_def->{TYPE} =~ /varchar|text/i;
+ }
+
+ # If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
+ if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
+ push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
+ }
+
+ # If we went from being a PK to not being a PK
+ elsif ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
+ push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
+ }
+
+ return @statements;
}
sub _get_alter_type_sql {
- my ($self, $table, $column, $new_def, $old_def) = @_;
- my @statements;
-
- my $type = $new_def->{TYPE};
- $type = $self->{db_specific}->{$type}
- if exists $self->{db_specific}->{$type};
-
- if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
- die("You cannot specify a DEFAULT on a SERIAL-type column.")
- if $new_def->{DEFAULT};
- }
-
- if ( ($old_def->{TYPE} =~ /LONGTEXT/i && $new_def->{TYPE} !~ /LONGTEXT/i)
- || ($old_def->{TYPE} !~ /LONGTEXT/i && $new_def->{TYPE} =~ /LONGTEXT/i)
- ) {
- # LONG to VARCHAR or VARCHAR to LONG is not allowed in Oracle,
- # just a way to work around.
- # Determine whether column_temp is already exist.
- my $dbh=Bugzilla->dbh;
- my $column_exist = $dbh->selectcol_arrayref(
- "SELECT CNAME FROM COL WHERE TNAME = UPPER(?) AND
- CNAME = UPPER(?)", undef,$table,$column . "_temp");
- if(!@$column_exist) {
- push(@statements,
- "ALTER TABLE $table ADD ${column}_temp $type");
- }
- push(@statements, "UPDATE $table SET ${column}_temp = $column");
- push(@statements, "COMMIT");
- push(@statements, "ALTER TABLE $table DROP COLUMN $column");
- push(@statements,
- "ALTER TABLE $table RENAME COLUMN ${column}_temp TO $column");
- } else {
- push(@statements, "ALTER TABLE $table MODIFY $column $type");
- }
-
- if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
- push(@statements, _get_create_seq_ddl($table, $column));
+ my ($self, $table, $column, $new_def, $old_def) = @_;
+ my @statements;
+
+ my $type = $new_def->{TYPE};
+ $type = $self->{db_specific}->{$type} if exists $self->{db_specific}->{$type};
+
+ if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+ die("You cannot specify a DEFAULT on a SERIAL-type column.")
+ if $new_def->{DEFAULT};
+ }
+
+ if ( ($old_def->{TYPE} =~ /LONGTEXT/i && $new_def->{TYPE} !~ /LONGTEXT/i)
+ || ($old_def->{TYPE} !~ /LONGTEXT/i && $new_def->{TYPE} =~ /LONGTEXT/i))
+ {
+ # LONG to VARCHAR or VARCHAR to LONG is not allowed in Oracle,
+ # just a way to work around.
+ # Determine whether column_temp is already exist.
+ my $dbh = Bugzilla->dbh;
+ my $column_exist = $dbh->selectcol_arrayref(
+ "SELECT CNAME FROM COL WHERE TNAME = UPPER(?) AND
+ CNAME = UPPER(?)", undef, $table, $column . "_temp"
+ );
+ if (!@$column_exist) {
+ push(@statements, "ALTER TABLE $table ADD ${column}_temp $type");
}
-
- # If this column is no longer SERIAL, we need to drop the sequence
- # that went along with it.
- if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
- push(@statements, "DROP SEQUENCE ${table}_${column}_SEQ");
- push(@statements, "DROP TRIGGER ${table}_${column}_TR");
- }
-
- # If this column is changed to type TEXT/VARCHAR, we need to deal with
- # empty string.
- if ( $old_def->{TYPE} !~ /varchar|text/i
- && $new_def->{TYPE} =~ /varchar|text/i
- && $new_def->{NOTNULL} )
- {
- push (@statements, _get_notnull_trigger_ddl($table, $column));
- }
- # If this column is no longer TEXT/VARCHAR, we need to drop the trigger
- # that went along with it.
- if ( $old_def->{TYPE} =~ /varchar|text/i
- && $old_def->{NOTNULL}
- && $new_def->{TYPE} !~ /varchar|text/i )
- {
- push(@statements, "DROP TRIGGER ${table}_${column}");
- }
- return @statements;
+ push(@statements, "UPDATE $table SET ${column}_temp = $column");
+ push(@statements, "COMMIT");
+ push(@statements, "ALTER TABLE $table DROP COLUMN $column");
+ push(@statements, "ALTER TABLE $table RENAME COLUMN ${column}_temp TO $column");
+ }
+ else {
+ push(@statements, "ALTER TABLE $table MODIFY $column $type");
+ }
+
+ if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+ push(@statements, _get_create_seq_ddl($table, $column));
+ }
+
+ # If this column is no longer SERIAL, we need to drop the sequence
+ # that went along with it.
+ if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
+ push(@statements, "DROP SEQUENCE ${table}_${column}_SEQ");
+ push(@statements, "DROP TRIGGER ${table}_${column}_TR");
+ }
+
+ # If this column is changed to type TEXT/VARCHAR, we need to deal with
+ # empty string.
+ if ( $old_def->{TYPE} !~ /varchar|text/i
+ && $new_def->{TYPE} =~ /varchar|text/i
+ && $new_def->{NOTNULL})
+ {
+ push(@statements, _get_notnull_trigger_ddl($table, $column));
+ }
+
+ # If this column is no longer TEXT/VARCHAR, we need to drop the trigger
+ # that went along with it.
+ if ( $old_def->{TYPE} =~ /varchar|text/i
+ && $old_def->{NOTNULL}
+ && $new_def->{TYPE} !~ /varchar|text/i)
+ {
+ push(@statements, "DROP TRIGGER ${table}_${column}");
+ }
+ return @statements;
}
sub get_rename_column_ddl {
- my ($self, $table, $old_name, $new_name) = @_;
- if (lc($old_name) eq lc($new_name)) {
- # if the only change is a case change, return an empty list.
- return ();
- }
- my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
- my $def = $self->get_column_abstract($table, $old_name);
- if ($def->{TYPE} =~ /SERIAL/i) {
- # We have to rename the series also, and fix the default of the series.
- my $old_seq = "${table}_${old_name}_SEQ";
- my $new_seq = "${table}_${new_name}_SEQ";
- push(@sql, "RENAME $old_seq TO $new_seq");
- push(@sql, $self->_get_create_trigger_ddl($table, $new_name, $new_seq));
- push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
- }
- if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL} ) {
- push(@sql, _get_notnull_trigger_ddl($table,$new_name));
- push(@sql, "DROP TRIGGER ${table}_${old_name}");
- }
- return @sql;
+ my ($self, $table, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+
+ # if the only change is a case change, return an empty list.
+ return ();
+ }
+ my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
+ my $def = $self->get_column_abstract($table, $old_name);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+
+ # We have to rename the series also, and fix the default of the series.
+ my $old_seq = "${table}_${old_name}_SEQ";
+ my $new_seq = "${table}_${new_name}_SEQ";
+ push(@sql, "RENAME $old_seq TO $new_seq");
+ push(@sql, $self->_get_create_trigger_ddl($table, $new_name, $new_seq));
+ push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
+ }
+ if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL}) {
+ push(@sql, _get_notnull_trigger_ddl($table, $new_name));
+ push(@sql, "DROP TRIGGER ${table}_${old_name}");
+ }
+ return @sql;
}
sub get_drop_column_ddl {
- my $self = shift;
- my ($table, $column) = @_;
- my @sql;
- push(@sql, $self->SUPER::get_drop_column_ddl(@_));
- my $dbh=Bugzilla->dbh;
- my $trigger_name = uc($table . "_" . $column);
- my $exist_trigger = $dbh->selectcol_arrayref(
- "SELECT OBJECT_NAME FROM USER_OBJECTS
- WHERE OBJECT_NAME = ?", undef, $trigger_name);
- if(@$exist_trigger) {
- push(@sql, "DROP TRIGGER $trigger_name");
- }
- # If this column is of type SERIAL, we need to drop the sequence
- # and trigger that went along with it.
- my $def = $self->get_column_abstract($table, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- push(@sql, "DROP SEQUENCE ${table}_${column}_SEQ");
- push(@sql, "DROP TRIGGER ${table}_${column}_TR");
- }
- return @sql;
+ my $self = shift;
+ my ($table, $column) = @_;
+ my @sql;
+ push(@sql, $self->SUPER::get_drop_column_ddl(@_));
+ my $dbh = Bugzilla->dbh;
+ my $trigger_name = uc($table . "_" . $column);
+ my $exist_trigger = $dbh->selectcol_arrayref(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS
+ WHERE OBJECT_NAME = ?", undef, $trigger_name
+ );
+ if (@$exist_trigger) {
+ push(@sql, "DROP TRIGGER $trigger_name");
+ }
+
+ # If this column is of type SERIAL, we need to drop the sequence
+ # and trigger that went along with it.
+ my $def = $self->get_column_abstract($table, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ push(@sql, "DROP SEQUENCE ${table}_${column}_SEQ");
+ push(@sql, "DROP TRIGGER ${table}_${column}_TR");
+ }
+ return @sql;
}
sub get_rename_table_sql {
- my ($self, $old_name, $new_name) = @_;
- if (lc($old_name) eq lc($new_name)) {
- # if the only change is a case change, return an empty list.
- return ();
- }
+ my ($self, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+
+ # if the only change is a case change, return an empty list.
+ return ();
+ }
- my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
- my @columns = $self->get_table_columns($old_name);
- foreach my $column (@columns) {
- my $def = $self->get_column_abstract($old_name, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- # If there's a SERIAL column on this table, we also need
- # to rename the sequence.
- my $old_seq = "${old_name}_${column}_SEQ";
- my $new_seq = "${new_name}_${column}_SEQ";
- push(@sql, "RENAME $old_seq TO $new_seq");
- push(@sql, $self->_get_create_trigger_ddl($new_name, $column, $new_seq));
- push(@sql, "DROP TRIGGER ${old_name}_${column}_TR");
- }
- if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL}) {
- push(@sql, _get_notnull_trigger_ddl($new_name, $column));
- push(@sql, "DROP TRIGGER ${old_name}_${column}");
- }
+ my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
+ my @columns = $self->get_table_columns($old_name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($old_name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+
+ # If there's a SERIAL column on this table, we also need
+ # to rename the sequence.
+ my $old_seq = "${old_name}_${column}_SEQ";
+ my $new_seq = "${new_name}_${column}_SEQ";
+ push(@sql, "RENAME $old_seq TO $new_seq");
+ push(@sql, $self->_get_create_trigger_ddl($new_name, $column, $new_seq));
+ push(@sql, "DROP TRIGGER ${old_name}_${column}_TR");
+ }
+ if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL}) {
+ push(@sql, _get_notnull_trigger_ddl($new_name, $column));
+ push(@sql, "DROP TRIGGER ${old_name}_${column}");
}
+ }
- return @sql;
+ return @sql;
}
sub get_drop_table_ddl {
- my ($self, $name) = @_;
- my @sql;
-
- my @columns = $self->get_table_columns($name);
- foreach my $column (@columns) {
- my $def = $self->get_column_abstract($name, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- # If there's a SERIAL column on this table, we also need
- # to remove the sequence.
- push(@sql, "DROP SEQUENCE ${name}_${column}_SEQ");
- }
+ my ($self, $name) = @_;
+ my @sql;
+
+ my @columns = $self->get_table_columns($name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+
+ # If there's a SERIAL column on this table, we also need
+ # to remove the sequence.
+ push(@sql, "DROP SEQUENCE ${name}_${column}_SEQ");
}
- push(@sql, "DROP TABLE $name CASCADE CONSTRAINTS PURGE");
+ }
+ push(@sql, "DROP TABLE $name CASCADE CONSTRAINTS PURGE");
- return @sql;
+ return @sql;
}
sub _get_notnull_trigger_ddl {
- my ($table, $column) = @_;
-
- my $notnull_sql = "CREATE OR REPLACE TRIGGER "
- . " ${table}_${column}"
- . " BEFORE INSERT OR UPDATE ON ". $table
- . " FOR EACH ROW"
- . " BEGIN "
- . " IF :NEW.". $column ." IS NULL THEN "
- . " SELECT '" . Bugzilla::DB::Oracle->EMPTY_STRING
- . "' INTO :NEW.". $column ." FROM DUAL; "
- . " END IF; "
- . " END ".$table.";";
- return $notnull_sql;
+ my ($table, $column) = @_;
+
+ my $notnull_sql
+ = "CREATE OR REPLACE TRIGGER "
+ . " ${table}_${column}"
+ . " BEFORE INSERT OR UPDATE ON "
+ . $table
+ . " FOR EACH ROW"
+ . " BEGIN "
+ . " IF :NEW."
+ . $column
+ . " IS NULL THEN "
+ . " SELECT '"
+ . Bugzilla::DB::Oracle->EMPTY_STRING
+ . "' INTO :NEW."
+ . $column
+ . " FROM DUAL; "
+ . " END IF; " . " END "
+ . $table . ";";
+ return $notnull_sql;
}
sub _get_create_seq_ddl {
- my ($self, $table, $column, $start_with) = @_;
- $start_with ||= 1;
- my @ddl;
- my $seq_name = "${table}_${column}_SEQ";
- my $seq_sql = "CREATE SEQUENCE $seq_name "
- . " INCREMENT BY 1 "
- . " START WITH $start_with "
- . " NOMAXVALUE "
- . " NOCYCLE "
- . " NOCACHE";
- push (@ddl, $seq_sql);
- push(@ddl, $self->_get_create_trigger_ddl($table, $column, $seq_name));
-
- return @ddl;
+ my ($self, $table, $column, $start_with) = @_;
+ $start_with ||= 1;
+ my @ddl;
+ my $seq_name = "${table}_${column}_SEQ";
+ my $seq_sql
+ = "CREATE SEQUENCE $seq_name "
+ . " INCREMENT BY 1 "
+ . " START WITH $start_with "
+ . " NOMAXVALUE "
+ . " NOCYCLE "
+ . " NOCACHE";
+ push(@ddl, $seq_sql);
+ push(@ddl, $self->_get_create_trigger_ddl($table, $column, $seq_name));
+
+ return @ddl;
}
sub _get_create_trigger_ddl {
- my ($self, $table, $column, $seq_name) = @_;
- my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
- . " BEFORE INSERT ON $table "
- . " FOR EACH ROW "
- . " BEGIN "
- . " SELECT ${seq_name}.NEXTVAL "
- . " INTO :NEW.$column FROM DUAL; "
- . " END;";
- return $serial_sql;
+ my ($self, $table, $column, $seq_name) = @_;
+ my $serial_sql
+ = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
+ . " BEFORE INSERT ON $table "
+ . " FOR EACH ROW "
+ . " BEGIN "
+ . " SELECT ${seq_name}.NEXTVAL "
+ . " INTO :NEW.$column FROM DUAL; " . " END;";
+ return $serial_sql;
}
sub get_set_serial_sql {
- my ($self, $table, $column, $value) = @_;
- my @sql;
- my $seq_name = "${table}_${column}_SEQ";
- push(@sql, "DROP SEQUENCE ${seq_name}");
- push(@sql, $self->_get_create_seq_ddl($table, $column, $value));
- return @sql;
+ my ($self, $table, $column, $value) = @_;
+ my @sql;
+ my $seq_name = "${table}_${column}_SEQ";
+ push(@sql, "DROP SEQUENCE ${seq_name}");
+ push(@sql, $self->_get_create_seq_ddl($table, $column, $value));
+ return @sql;
}
1;
diff --git a/Bugzilla/DB/Schema/Pg.pm b/Bugzilla/DB/Schema/Pg.pm
index 7606faa3d..8af1af8c0 100644
--- a/Bugzilla/DB/Schema/Pg.pm
+++ b/Bugzilla/DB/Schema/Pg.pm
@@ -23,169 +23,191 @@ use Storable qw(dclone);
#------------------------------------------------------------------------------
sub _initialize {
- my $self = shift;
-
- $self = $self->SUPER::_initialize(@_);
-
- # Remove FULLTEXT index types from the schemas.
- foreach my $table (keys %{ $self->{schema} }) {
- if ($self->{schema}{$table}{INDEXES}) {
- foreach my $index (@{ $self->{schema}{$table}{INDEXES} }) {
- if (ref($index) eq 'HASH') {
- delete($index->{TYPE}) if (exists $index->{TYPE}
- && $index->{TYPE} eq 'FULLTEXT');
- }
- }
- foreach my $index (@{ $self->{abstract_schema}{$table}{INDEXES} }) {
- if (ref($index) eq 'HASH') {
- delete($index->{TYPE}) if (exists $index->{TYPE}
- && $index->{TYPE} eq 'FULLTEXT');
- }
- }
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
+
+ # Remove FULLTEXT index types from the schemas.
+ foreach my $table (keys %{$self->{schema}}) {
+ if ($self->{schema}{$table}{INDEXES}) {
+ foreach my $index (@{$self->{schema}{$table}{INDEXES}}) {
+ if (ref($index) eq 'HASH') {
+ delete($index->{TYPE})
+ if (exists $index->{TYPE} && $index->{TYPE} eq 'FULLTEXT');
+ }
+ }
+ foreach my $index (@{$self->{abstract_schema}{$table}{INDEXES}}) {
+ if (ref($index) eq 'HASH') {
+ delete($index->{TYPE})
+ if (exists $index->{TYPE} && $index->{TYPE} eq 'FULLTEXT');
}
+ }
}
+ }
- $self->{db_specific} = {
+ $self->{db_specific} = {
- BOOLEAN => 'smallint',
- FALSE => '0',
- TRUE => '1',
+ BOOLEAN => 'smallint',
+ FALSE => '0',
+ TRUE => '1',
- INT1 => 'integer',
- INT2 => 'integer',
- INT3 => 'integer',
- INT4 => 'integer',
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
- SMALLSERIAL => 'serial unique',
- MEDIUMSERIAL => 'serial unique',
- INTSERIAL => 'serial unique',
+ SMALLSERIAL => 'serial unique',
+ MEDIUMSERIAL => 'serial unique',
+ INTSERIAL => 'serial unique',
- TINYTEXT => 'varchar(255)',
- MEDIUMTEXT => 'text',
- LONGTEXT => 'text',
+ TINYTEXT => 'varchar(255)',
+ MEDIUMTEXT => 'text',
+ LONGTEXT => 'text',
- LONGBLOB => 'bytea',
+ LONGBLOB => 'bytea',
- DATETIME => 'timestamp(0) without time zone',
- DATE => 'date',
- };
+ DATETIME => 'timestamp(0) without time zone',
+ DATE => 'date',
+ };
- $self->_adjust_schema;
+ $self->_adjust_schema;
- return $self;
+ return $self;
+
+} #eosub--_initialize
-} #eosub--_initialize
#--------------------------------------------------------------------
sub get_create_database_sql {
- my ($self, $name) = @_;
- # We only create as utf8 if we have no params (meaning we're doing
- # a new installation) or if the utf8 param is on.
- my $create_utf8 = Bugzilla->params->{'utf8'}
- || !defined Bugzilla->params->{'utf8'};
- my $charset = $create_utf8 ? "ENCODING 'UTF8' TEMPLATE template0" : '';
- return ("CREATE DATABASE $name $charset");
+ my ($self, $name) = @_;
+
+ # We only create as utf8 if we have no params (meaning we're doing
+ # a new installation) or if the utf8 param is on.
+ my $create_utf8
+ = Bugzilla->params->{'utf8'} || !defined Bugzilla->params->{'utf8'};
+ my $charset = $create_utf8 ? "ENCODING 'UTF8' TEMPLATE template0" : '';
+ return ("CREATE DATABASE $name $charset");
}
sub get_rename_column_ddl {
- my ($self, $table, $old_name, $new_name) = @_;
- if (lc($old_name) eq lc($new_name)) {
- # if the only change is a case change, return an empty list, since Pg
- # is case-insensitive and will return an error about a duplicate name
- return ();
- }
- my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
- my $def = $self->get_column_abstract($table, $old_name);
- if ($def->{TYPE} =~ /SERIAL/i) {
- # We have to rename the series also.
- push(@sql, "ALTER SEQUENCE ${table}_${old_name}_seq
- RENAME TO ${table}_${new_name}_seq");
- }
- return @sql;
+ my ($self, $table, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+
+ # if the only change is a case change, return an empty list, since Pg
+ # is case-insensitive and will return an error about a duplicate name
+ return ();
+ }
+ my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
+ my $def = $self->get_column_abstract($table, $old_name);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+
+ # We have to rename the series also.
+ push(
+ @sql, "ALTER SEQUENCE ${table}_${old_name}_seq
+ RENAME TO ${table}_${new_name}_seq"
+ );
+ }
+ return @sql;
}
sub get_rename_table_sql {
- my ($self, $old_name, $new_name) = @_;
- if (lc($old_name) eq lc($new_name)) {
- # if the only change is a case change, return an empty list, since Pg
- # is case-insensitive and will return an error about a duplicate name
- return ();
+ my ($self, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+
+ # if the only change is a case change, return an empty list, since Pg
+ # is case-insensitive and will return an error about a duplicate name
+ return ();
+ }
+
+ my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
+
+ # If there's a SERIAL column on this table, we also need to rename the
+ # sequence.
+ # If there is a PRIMARY KEY, we need to rename it too.
+ my @columns = $self->get_table_columns($old_name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($old_name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ my $old_seq = "${old_name}_${column}_seq";
+ my $new_seq = "${new_name}_${column}_seq";
+ push(@sql, "ALTER SEQUENCE $old_seq RENAME TO $new_seq");
+ push(
+ @sql, "ALTER TABLE $new_name ALTER COLUMN $column
+ SET DEFAULT NEXTVAL('$new_seq')"
+ );
}
-
- my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
-
- # If there's a SERIAL column on this table, we also need to rename the
- # sequence.
- # If there is a PRIMARY KEY, we need to rename it too.
- my @columns = $self->get_table_columns($old_name);
- foreach my $column (@columns) {
- my $def = $self->get_column_abstract($old_name, $column);
- if ($def->{TYPE} =~ /SERIAL/i) {
- my $old_seq = "${old_name}_${column}_seq";
- my $new_seq = "${new_name}_${column}_seq";
- push(@sql, "ALTER SEQUENCE $old_seq RENAME TO $new_seq");
- push(@sql, "ALTER TABLE $new_name ALTER COLUMN $column
- SET DEFAULT NEXTVAL('$new_seq')");
- }
- if ($def->{PRIMARYKEY}) {
- my $old_pk = "${old_name}_pkey";
- my $new_pk = "${new_name}_pkey";
- push(@sql, "ALTER INDEX $old_pk RENAME to $new_pk");
- }
+ if ($def->{PRIMARYKEY}) {
+ my $old_pk = "${old_name}_pkey";
+ my $new_pk = "${new_name}_pkey";
+ push(@sql, "ALTER INDEX $old_pk RENAME to $new_pk");
}
+ }
- return @sql;
+ return @sql;
}
sub get_set_serial_sql {
- my ($self, $table, $column, $value) = @_;
- return ("SELECT setval('${table}_${column}_seq', $value, false)
- FROM $table");
+ my ($self, $table, $column, $value) = @_;
+ return (
+ "SELECT setval('${table}_${column}_seq', $value, false)
+ FROM $table"
+ );
}
sub _get_alter_type_sql {
- my ($self, $table, $column, $new_def, $old_def) = @_;
- my @statements;
-
- my $type = $new_def->{TYPE};
- $type = $self->{db_specific}->{$type}
- if exists $self->{db_specific}->{$type};
-
- if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
- die("You cannot specify a DEFAULT on a SERIAL-type column.")
- if $new_def->{DEFAULT};
- }
-
- $type =~ s/\bserial\b/integer/i;
-
- # On Pg, you don't need UNIQUE if you're a PK--it creates
- # two identical indexes otherwise.
- $type =~ s/unique//i if $new_def->{PRIMARYKEY};
-
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column
- TYPE $type");
-
- if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
- push(@statements, "CREATE SEQUENCE ${table}_${column}_seq
- OWNED BY $table.$column");
- push(@statements, "SELECT setval('${table}_${column}_seq',
+ my ($self, $table, $column, $new_def, $old_def) = @_;
+ my @statements;
+
+ my $type = $new_def->{TYPE};
+ $type = $self->{db_specific}->{$type} if exists $self->{db_specific}->{$type};
+
+ if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+ die("You cannot specify a DEFAULT on a SERIAL-type column.")
+ if $new_def->{DEFAULT};
+ }
+
+ $type =~ s/\bserial\b/integer/i;
+
+ # On Pg, you don't need UNIQUE if you're a PK--it creates
+ # two identical indexes otherwise.
+ $type =~ s/unique//i if $new_def->{PRIMARYKEY};
+
+ push(
+ @statements, "ALTER TABLE $table ALTER COLUMN $column
+ TYPE $type"
+ );
+
+ if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+ push(
+ @statements, "CREATE SEQUENCE ${table}_${column}_seq
+ OWNED BY $table.$column"
+ );
+ push(
+ @statements, "SELECT setval('${table}_${column}_seq',
MAX($table.$column))
- FROM $table");
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column
- SET DEFAULT nextval('${table}_${column}_seq')");
- }
-
- # If this column is no longer SERIAL, we need to drop the sequence
- # that went along with it.
- if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column
- DROP DEFAULT");
- push(@statements, "ALTER SEQUENCE ${table}_${column}_seq
- OWNED BY NONE");
- push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
- }
-
- return @statements;
+ FROM $table"
+ );
+ push(
+ @statements, "ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT nextval('${table}_${column}_seq')"
+ );
+ }
+
+ # If this column is no longer SERIAL, we need to drop the sequence
+ # that went along with it.
+ if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
+ push(
+ @statements, "ALTER TABLE $table ALTER COLUMN $column
+ DROP DEFAULT"
+ );
+ push(
+ @statements, "ALTER SEQUENCE ${table}_${column}_seq
+ OWNED BY NONE"
+ );
+ push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
+ }
+
+ return @statements;
}
1;
diff --git a/Bugzilla/DB/Schema/Sqlite.pm b/Bugzilla/DB/Schema/Sqlite.pm
index 6d524db59..1bd53515a 100644
--- a/Bugzilla/DB/Schema/Sqlite.pm
+++ b/Bugzilla/DB/Schema/Sqlite.pm
@@ -22,37 +22,37 @@ use constant FK_ON_CREATE => 1;
sub _initialize {
- my $self = shift;
+ my $self = shift;
- $self = $self->SUPER::_initialize(@_);
+ $self = $self->SUPER::_initialize(@_);
- $self->{db_specific} = {
- BOOLEAN => 'integer',
- FALSE => '0',
- TRUE => '1',
+ $self->{db_specific} = {
+ BOOLEAN => 'integer',
+ FALSE => '0',
+ TRUE => '1',
- INT1 => 'integer',
- INT2 => 'integer',
- INT3 => 'integer',
- INT4 => 'integer',
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
- SMALLSERIAL => 'SERIAL',
- MEDIUMSERIAL => 'SERIAL',
- INTSERIAL => 'SERIAL',
+ SMALLSERIAL => 'SERIAL',
+ MEDIUMSERIAL => 'SERIAL',
+ INTSERIAL => 'SERIAL',
- TINYTEXT => 'text',
- MEDIUMTEXT => 'text',
- LONGTEXT => 'text',
+ TINYTEXT => 'text',
+ MEDIUMTEXT => 'text',
+ LONGTEXT => 'text',
- LONGBLOB => 'blob',
+ LONGBLOB => 'blob',
- DATETIME => 'DATETIME',
- DATE => 'DATETIME',
- };
+ DATETIME => 'DATETIME',
+ DATE => 'DATETIME',
+ };
- $self->_adjust_schema;
+ $self->_adjust_schema;
- return $self;
+ return $self;
}
@@ -61,83 +61,86 @@ sub _initialize {
#################################
sub _sqlite_create_table {
- my ($self, $table) = @_;
- return scalar Bugzilla->dbh->selectrow_array(
- "SELECT sql FROM sqlite_master WHERE name = ? AND type = 'table'",
- undef, $table);
+ my ($self, $table) = @_;
+ return
+ scalar Bugzilla->dbh->selectrow_array(
+ "SELECT sql FROM sqlite_master WHERE name = ? AND type = 'table'",
+ undef, $table);
}
sub _sqlite_table_lines {
- my $self = shift;
- my $table_sql = $self->_sqlite_create_table(@_);
- $table_sql =~ s/\n*\)$//s;
- # The $ makes this work even if people some day add crazy stuff to their
- # schema like multi-column foreign keys.
- return split(/,\s*$/m, $table_sql);
+ my $self = shift;
+ my $table_sql = $self->_sqlite_create_table(@_);
+ $table_sql =~ s/\n*\)$//s;
+
+ # The $ makes this work even if people some day add crazy stuff to their
+ # schema like multi-column foreign keys.
+ return split(/,\s*$/m, $table_sql);
}
# This does most of the "heavy lifting" of the schema-altering functions.
sub _sqlite_alter_schema {
- my ($self, $table, $create_table, $options) = @_;
-
- # $create_table is sometimes an array in the form that _sqlite_table_lines
- # returns.
- if (ref $create_table) {
- $create_table = join(',', @$create_table) . "\n)";
- }
-
- my $dbh = Bugzilla->dbh;
-
- my $random = generate_random_password(5);
- my $rename_to = "${table}_$random";
-
- my @columns = $dbh->bz_table_columns_real($table);
- push(@columns, $options->{extra_column}) if $options->{extra_column};
- if (my $exclude = $options->{exclude_column}) {
- @columns = grep { $_ ne $exclude } @columns;
+ my ($self, $table, $create_table, $options) = @_;
+
+ # $create_table is sometimes an array in the form that _sqlite_table_lines
+ # returns.
+ if (ref $create_table) {
+ $create_table = join(',', @$create_table) . "\n)";
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ my $random = generate_random_password(5);
+ my $rename_to = "${table}_$random";
+
+ my @columns = $dbh->bz_table_columns_real($table);
+ push(@columns, $options->{extra_column}) if $options->{extra_column};
+ if (my $exclude = $options->{exclude_column}) {
+ @columns = grep { $_ ne $exclude } @columns;
+ }
+ my @insert_cols = @columns;
+ my @select_cols = @columns;
+ if (my $rename = $options->{rename}) {
+ foreach my $from (keys %$rename) {
+ my $to = $rename->{$from};
+ @insert_cols = map { $_ eq $from ? $to : $_ } @insert_cols;
}
- my @insert_cols = @columns;
- my @select_cols = @columns;
- if (my $rename = $options->{rename}) {
- foreach my $from (keys %$rename) {
- my $to = $rename->{$from};
- @insert_cols = map { $_ eq $from ? $to : $_ } @insert_cols;
- }
- }
-
- my $insert_str = join(',', @insert_cols);
- my $select_str = join(',', @select_cols);
- my $copy_sql = "INSERT INTO $table ($insert_str)"
- . " SELECT $select_str FROM $rename_to";
-
- # We have to turn FKs off before doing this. Otherwise, when we rename
- # the table, all of the FKs in the other tables will be automatically
- # updated to point to the renamed table. Note that PRAGMA foreign_keys
- # can only be set outside of a transaction--otherwise it is a no-op.
- if ($dbh->bz_in_transaction) {
- die "can't alter the schema inside of a transaction";
- }
- my @sql = (
- 'PRAGMA foreign_keys = OFF',
- 'BEGIN EXCLUSIVE TRANSACTION',
- @{ $options->{pre_sql} || [] },
- "ALTER TABLE $table RENAME TO $rename_to",
- $create_table,
- $copy_sql,
- "DROP TABLE $rename_to",
- 'COMMIT TRANSACTION',
- 'PRAGMA foreign_keys = ON',
- );
+ }
+
+ my $insert_str = join(',', @insert_cols);
+ my $select_str = join(',', @select_cols);
+ my $copy_sql
+ = "INSERT INTO $table ($insert_str)" . " SELECT $select_str FROM $rename_to";
+
+ # We have to turn FKs off before doing this. Otherwise, when we rename
+ # the table, all of the FKs in the other tables will be automatically
+ # updated to point to the renamed table. Note that PRAGMA foreign_keys
+ # can only be set outside of a transaction--otherwise it is a no-op.
+ if ($dbh->bz_in_transaction) {
+ die "can't alter the schema inside of a transaction";
+ }
+ my @sql = (
+ 'PRAGMA foreign_keys = OFF',
+ 'BEGIN EXCLUSIVE TRANSACTION',
+ @{$options->{pre_sql} || []},
+ "ALTER TABLE $table RENAME TO $rename_to",
+ $create_table,
+ $copy_sql,
+ "DROP TABLE $rename_to",
+ 'COMMIT TRANSACTION',
+ 'PRAGMA foreign_keys = ON',
+ );
}
# For finding a particular column's definition in a CREATE TABLE statement.
sub _sqlite_column_regex {
- my ($column) = @_;
- # 1 = Comma at start
- # 2 = Column name + Space
- # 3 = Definition
- # 4 = Ending comma
- return qr/(^|,)(\s\Q$column\E\s+)(.*?)(,|$)/m;
+ my ($column) = @_;
+
+ # 1 = Comma at start
+ # 2 = Column name + Space
+ # 3 = Definition
+ # 4 = Ending comma
+ return qr/(^|,)(\s\Q$column\E\s+)(.*?)(,|$)/m;
}
#############################
@@ -145,133 +148,137 @@ sub _sqlite_column_regex {
#############################
sub get_create_database_sql {
- # If we get here, it means there was some error creating the
- # database file during bz_create_database in Bugzilla::DB,
- # and we just want to display that error instead of doing
- # anything else.
- Bugzilla->dbh;
- die "Reached an unreachable point";
+
+ # If we get here, it means there was some error creating the
+ # database file during bz_create_database in Bugzilla::DB,
+ # and we just want to display that error instead of doing
+ # anything else.
+ Bugzilla->dbh;
+ die "Reached an unreachable point";
}
sub _get_create_table_ddl {
- my $self = shift;
- my ($table) = @_;
- my $ddl = $self->SUPER::_get_create_table_ddl(@_);
-
- # TheSchwartz uses its own driver to access its tables, meaning
- # that it doesn't understand "COLLATE bugzilla" and in fact
- # SQLite throws an error when TheSchwartz tries to access its
- # own tables, if COLLATE bugzilla is on them. We don't have
- # to fix this elsewhere currently, because we only create
- # TheSchwartz's tables, we never modify them.
- if ($table =~ /^ts_/) {
- $ddl =~ s/ COLLATE bugzilla//g;
- }
- return $ddl;
+ my $self = shift;
+ my ($table) = @_;
+ my $ddl = $self->SUPER::_get_create_table_ddl(@_);
+
+ # TheSchwartz uses its own driver to access its tables, meaning
+ # that it doesn't understand "COLLATE bugzilla" and in fact
+ # SQLite throws an error when TheSchwartz tries to access its
+ # own tables, if COLLATE bugzilla is on them. We don't have
+ # to fix this elsewhere currently, because we only create
+ # TheSchwartz's tables, we never modify them.
+ if ($table =~ /^ts_/) {
+ $ddl =~ s/ COLLATE bugzilla//g;
+ }
+ return $ddl;
}
sub get_type_ddl {
- my $self = shift;
- my $def = dclone($_[0]);
-
- my $ddl = $self->SUPER::get_type_ddl(@_);
- if ($def->{PRIMARYKEY} and $def->{TYPE} =~ /SERIAL/i) {
- $ddl =~ s/\bSERIAL\b/integer/;
- $ddl =~ s/\bPRIMARY KEY\b/PRIMARY KEY AUTOINCREMENT/;
- }
- if ($def->{TYPE} =~ /text/i or $def->{TYPE} =~ /char/i) {
- $ddl .= " COLLATE bugzilla";
- }
- # Don't collate DATETIME fields.
- if ($def->{TYPE} eq 'DATETIME') {
- $ddl =~ s/\bDATETIME\b/text COLLATE BINARY/;
- }
- return $ddl;
+ my $self = shift;
+ my $def = dclone($_[0]);
+
+ my $ddl = $self->SUPER::get_type_ddl(@_);
+ if ($def->{PRIMARYKEY} and $def->{TYPE} =~ /SERIAL/i) {
+ $ddl =~ s/\bSERIAL\b/integer/;
+ $ddl =~ s/\bPRIMARY KEY\b/PRIMARY KEY AUTOINCREMENT/;
+ }
+ if ($def->{TYPE} =~ /text/i or $def->{TYPE} =~ /char/i) {
+ $ddl .= " COLLATE bugzilla";
+ }
+
+ # Don't collate DATETIME fields.
+ if ($def->{TYPE} eq 'DATETIME') {
+ $ddl =~ s/\bDATETIME\b/text COLLATE BINARY/;
+ }
+ return $ddl;
}
sub get_alter_column_ddl {
- my $self = shift;
- my ($table, $column, $new_def, $set_nulls_to) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $table_sql = $self->_sqlite_create_table($table);
- my $new_ddl = $self->get_type_ddl($new_def);
- # When we do ADD COLUMN, columns can show up all on one line separated
- # by commas, so we have to account for that.
- my $column_regex = _sqlite_column_regex($column);
- $table_sql =~ s/$column_regex/$1$2$new_ddl$4/
- || die "couldn't find $column in $table:\n$table_sql";
- my @pre_sql = $self->_set_nulls_sql(@_);
- return $self->_sqlite_alter_schema($table, $table_sql,
- { pre_sql => \@pre_sql });
+ my $self = shift;
+ my ($table, $column, $new_def, $set_nulls_to) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $new_ddl = $self->get_type_ddl($new_def);
+
+ # When we do ADD COLUMN, columns can show up all on one line separated
+ # by commas, so we have to account for that.
+ my $column_regex = _sqlite_column_regex($column);
+ $table_sql =~ s/$column_regex/$1$2$new_ddl$4/
+ || die "couldn't find $column in $table:\n$table_sql";
+ my @pre_sql = $self->_set_nulls_sql(@_);
+ return $self->_sqlite_alter_schema($table, $table_sql, {pre_sql => \@pre_sql});
}
sub get_add_column_ddl {
- my $self = shift;
- my ($table, $column, $definition, $init_value) = @_;
- # SQLite can use the normal ADD COLUMN when:
- # * The column isn't a PK
- if ($definition->{PRIMARYKEY}) {
- if ($definition->{NOTNULL} and $definition->{TYPE} !~ /SERIAL/i) {
- die "You can only add new SERIAL type PKs with SQLite";
- }
- my $table_sql = $self->_sqlite_new_column_sql(@_);
- # This works because _sqlite_alter_schema will exclude the new column
- # in its INSERT ... SELECT statement, meaning that when the "new"
- # table is populated, it will have AUTOINCREMENT values generated
- # for it.
- return $self->_sqlite_alter_schema($table, $table_sql);
- }
- # * The column has a default one way or another. Either it
- # defaults to NULL (it lacks NOT NULL) or it has a DEFAULT
- # clause. Since we also require this when doing bz_add_column (in
- # the way of forcing an init_value for NOT NULL columns with no
- # default), we first set the init_value as the default and then
- # alter the column.
- if ($definition->{NOTNULL} and !defined $definition->{DEFAULT}) {
- my %with_default = %$definition;
- $with_default{DEFAULT} = $init_value;
- my @pre_sql =
- $self->SUPER::get_add_column_ddl($table, $column, \%with_default);
- my $table_sql = $self->_sqlite_new_column_sql(@_);
- return $self->_sqlite_alter_schema($table, $table_sql,
- { pre_sql => \@pre_sql, extra_column => $column });
+ my $self = shift;
+ my ($table, $column, $definition, $init_value) = @_;
+
+ # SQLite can use the normal ADD COLUMN when:
+ # * The column isn't a PK
+ if ($definition->{PRIMARYKEY}) {
+ if ($definition->{NOTNULL} and $definition->{TYPE} !~ /SERIAL/i) {
+ die "You can only add new SERIAL type PKs with SQLite";
}
+ my $table_sql = $self->_sqlite_new_column_sql(@_);
+
+ # This works because _sqlite_alter_schema will exclude the new column
+ # in its INSERT ... SELECT statement, meaning that when the "new"
+ # table is populated, it will have AUTOINCREMENT values generated
+ # for it.
+ return $self->_sqlite_alter_schema($table, $table_sql);
+ }
+
+ # * The column has a default one way or another. Either it
+ # defaults to NULL (it lacks NOT NULL) or it has a DEFAULT
+ # clause. Since we also require this when doing bz_add_column (in
+ # the way of forcing an init_value for NOT NULL columns with no
+ # default), we first set the init_value as the default and then
+ # alter the column.
+ if ($definition->{NOTNULL} and !defined $definition->{DEFAULT}) {
+ my %with_default = %$definition;
+ $with_default{DEFAULT} = $init_value;
+ my @pre_sql = $self->SUPER::get_add_column_ddl($table, $column, \%with_default);
+ my $table_sql = $self->_sqlite_new_column_sql(@_);
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ {pre_sql => \@pre_sql, extra_column => $column});
+ }
- return $self->SUPER::get_add_column_ddl(@_);
+ return $self->SUPER::get_add_column_ddl(@_);
}
sub _sqlite_new_column_sql {
- my ($self, $table, $column, $def) = @_;
- my $table_sql = $self->_sqlite_create_table($table);
- my $new_ddl = $self->get_type_ddl($def);
- my $new_line = "\t$column\t$new_ddl";
- $table_sql =~ s/^(CREATE TABLE \w+ \()/$1\n$new_line,/s
- || die "Can't find start of CREATE TABLE:\n$table_sql";
- return $table_sql;
+ my ($self, $table, $column, $def) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $new_ddl = $self->get_type_ddl($def);
+ my $new_line = "\t$column\t$new_ddl";
+ $table_sql =~ s/^(CREATE TABLE \w+ \()/$1\n$new_line,/s
+ || die "Can't find start of CREATE TABLE:\n$table_sql";
+ return $table_sql;
}
sub get_drop_column_ddl {
- my ($self, $table, $column) = @_;
- my $table_sql = $self->_sqlite_create_table($table);
- my $column_regex = _sqlite_column_regex($column);
- $table_sql =~ s/$column_regex/$1/
- || die "Can't find column $column: $table_sql";
- # Make sure we don't end up with a comma at the end of the definition.
- $table_sql =~ s/,\s+\)$/\n)/s;
- return $self->_sqlite_alter_schema($table, $table_sql,
- { exclude_column => $column });
+ my ($self, $table, $column) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $column_regex = _sqlite_column_regex($column);
+ $table_sql =~ s/$column_regex/$1/
+ || die "Can't find column $column: $table_sql";
+
+ # Make sure we don't end up with a comma at the end of the definition.
+ $table_sql =~ s/,\s+\)$/\n)/s;
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ {exclude_column => $column});
}
sub get_rename_column_ddl {
- my ($self, $table, $old_name, $new_name) = @_;
- my $table_sql = $self->_sqlite_create_table($table);
- my $column_regex = _sqlite_column_regex($old_name);
- $table_sql =~ s/$column_regex/$1\t$new_name\t$3$4/
- || die "Can't find $old_name: $table_sql";
- my %rename = ($old_name => $new_name);
- return $self->_sqlite_alter_schema($table, $table_sql,
- { rename => \%rename });
+ my ($self, $table, $old_name, $new_name) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $column_regex = _sqlite_column_regex($old_name);
+ $table_sql =~ s/$column_regex/$1\t$new_name\t$3$4/
+ || die "Can't find $old_name: $table_sql";
+ my %rename = ($old_name => $new_name);
+ return $self->_sqlite_alter_schema($table, $table_sql, {rename => \%rename});
}
################
@@ -279,24 +286,23 @@ sub get_rename_column_ddl {
################
sub get_add_fks_sql {
- my ($self, $table, $column_fks) = @_;
- my @clauses = $self->_sqlite_table_lines($table);
- my @add = $self->_column_fks_to_ddl($table, $column_fks);
- push(@clauses, @add);
- return $self->_sqlite_alter_schema($table, \@clauses);
+ my ($self, $table, $column_fks) = @_;
+ my @clauses = $self->_sqlite_table_lines($table);
+ my @add = $self->_column_fks_to_ddl($table, $column_fks);
+ push(@clauses, @add);
+ return $self->_sqlite_alter_schema($table, \@clauses);
}
sub get_drop_fk_sql {
- my ($self, $table, $column, $references) = @_;
- my @clauses = $self->_sqlite_table_lines($table);
- my $fk_name = $self->_get_fk_name($table, $column, $references);
+ my ($self, $table, $column, $references) = @_;
+ my @clauses = $self->_sqlite_table_lines($table);
+ my $fk_name = $self->_get_fk_name($table, $column, $references);
- my $line_re = qr/^\s+CONSTRAINT $fk_name /s;
- grep { $line_re } @clauses
- or die "Can't find $fk_name: " . join(',', @clauses);
- @clauses = grep { $_ !~ $line_re } @clauses;
+ my $line_re = qr/^\s+CONSTRAINT $fk_name /s;
+ grep {$line_re} @clauses or die "Can't find $fk_name: " . join(',', @clauses);
+ @clauses = grep { $_ !~ $line_re } @clauses;
- return $self->_sqlite_alter_schema($table, \@clauses);
+ return $self->_sqlite_alter_schema($table, \@clauses);
}
diff --git a/Bugzilla/DB/Sqlite.pm b/Bugzilla/DB/Sqlite.pm
index 81ee7d888..7a97ad06a 100644
--- a/Bugzilla/DB/Sqlite.pm
+++ b/Bugzilla/DB/Sqlite.pm
@@ -45,23 +45,23 @@ sub _sqlite_collate_ci { lc($_[0]) cmp lc($_[1]) }
sub _sqlite_mod { $_[0] % $_[1] }
sub _sqlite_now {
- my $now = DateTime->now(time_zone => Bugzilla->local_timezone);
- return $now->ymd . ' ' . $now->hms;
+ my $now = DateTime->now(time_zone => Bugzilla->local_timezone);
+ return $now->ymd . ' ' . $now->hms;
}
# SQL's POSITION starts its values from 1 instead of 0 (so we add 1).
sub _sqlite_position {
- my ($text, $fragment) = @_;
- if (!defined $text or !defined $fragment) {
- return undef;
- }
- my $pos = index $text, $fragment;
- return $pos + 1;
+ my ($text, $fragment) = @_;
+ if (!defined $text or !defined $fragment) {
+ return undef;
+ }
+ my $pos = index $text, $fragment;
+ return $pos + 1;
}
sub _sqlite_position_ci {
- my ($text, $fragment) = @_;
- return _sqlite_position(lc($text), lc($fragment));
+ my ($text, $fragment) = @_;
+ return _sqlite_position(lc($text), lc($fragment));
}
###############
@@ -69,73 +69,80 @@ sub _sqlite_position_ci {
###############
sub BUILDARGS {
- my ($class, $params) = @_;
- my $db_name = $params->{db_name};
-
- # Let people specify paths intead of data/ for the DB.
- if ($db_name && $db_name ne ':memory:' && $db_name !~ m{[\\/]}) {
- # When the DB is first created, there's a chance that the
- # data directory doesn't exist at all, because the Install::Filesystem
- # code happens after DB creation. So we create the directory ourselves
- # if it doesn't exist.
- my $datadir = bz_locations()->{datadir};
- if (!-d $datadir) {
- mkdir $datadir or warn "$datadir: $!";
- }
- if (!-d "$datadir/db/") {
- mkdir "$datadir/db/" or warn "$datadir/db: $!";
- }
- $db_name = bz_locations()->{datadir} . "/db/$db_name";
+ my ($class, $params) = @_;
+ my $db_name = $params->{db_name};
+
+ # Let people specify paths intead of data/ for the DB.
+ if ($db_name && $db_name ne ':memory:' && $db_name !~ m{[\\/]}) {
+
+ # When the DB is first created, there's a chance that the
+ # data directory doesn't exist at all, because the Install::Filesystem
+ # code happens after DB creation. So we create the directory ourselves
+ # if it doesn't exist.
+ my $datadir = bz_locations()->{datadir};
+ if (!-d $datadir) {
+ mkdir $datadir or warn "$datadir: $!";
+ }
+ if (!-d "$datadir/db/") {
+ mkdir "$datadir/db/" or warn "$datadir/db: $!";
}
+ $db_name = bz_locations()->{datadir} . "/db/$db_name";
+ }
+
+ # construct the DSN from the parameters we got
+ my $dsn = "dbi:SQLite:dbname=$db_name";
- # construct the DSN from the parameters we got
- my $dsn = "dbi:SQLite:dbname=$db_name";
+ my $attrs = {
- my $attrs = {
- # XXX Should we just enforce this to be always on?
- sqlite_unicode => Bugzilla->params->{'utf8'},
- };
+ # XXX Should we just enforce this to be always on?
+ sqlite_unicode => Bugzilla->params->{'utf8'},
+ };
- return { dsn => $dsn, user => '', pass => '', attrs => $attrs };
+ return {dsn => $dsn, user => '', pass => '', attrs => $attrs};
}
sub on_dbi_connected {
- my ($class, $dbh) = @_;
-
- my %pragmas = (
- # Make sure that the sqlite file doesn't grow without bound.
- auto_vacuum => 1,
- encoding => "'UTF-8'",
- foreign_keys => 'ON',
- # We want the latest file format.
- legacy_file_format => 'OFF',
- # This guarantees that we get column names like "foo"
- # instead of "table.foo" in selectrow_hashref.
- short_column_names => 'ON',
- # The write-ahead log mode in SQLite 3.7 gets us better concurrency,
- # but breaks backwards-compatibility with older versions of
- # SQLite. (Which is important because people may also want to use
- # command-line clients to access and back up their DB.) If you need
- # better concurrency and don't need 3.6 compatibility, then you can
- # uncomment this line.
- #journal_mode => "'WAL'",
- );
-
- while (my ($name, $value) = each %pragmas) {
- $dbh->do("PRAGMA $name = $value");
- }
-
- $dbh->sqlite_create_collation('bugzilla', \&_sqlite_collate_ci);
- $dbh->sqlite_create_function('position', 2, \&_sqlite_position);
- $dbh->sqlite_create_function('iposition', 2, \&_sqlite_position_ci);
- # SQLite has a "substr" function, but other DBs call it "SUBSTRING"
- # so that's what we use, and I don't know of any way in SQLite to
- # alias the SQL "substr" function to be called "SUBSTRING".
- $dbh->sqlite_create_function('substring', 3, \&CORE::substr);
- $dbh->sqlite_create_function('mod', 2, \&_sqlite_mod);
- $dbh->sqlite_create_function('now', 0, \&_sqlite_now);
- $dbh->sqlite_create_function('localtimestamp', 1, \&_sqlite_now);
- $dbh->sqlite_create_function('floor', 1, \&POSIX::floor);
+ my ($class, $dbh) = @_;
+
+ my %pragmas = (
+
+ # Make sure that the sqlite file doesn't grow without bound.
+ auto_vacuum => 1,
+ encoding => "'UTF-8'",
+ foreign_keys => 'ON',
+
+ # We want the latest file format.
+ legacy_file_format => 'OFF',
+
+ # This guarantees that we get column names like "foo"
+ # instead of "table.foo" in selectrow_hashref.
+ short_column_names => 'ON',
+
+ # The write-ahead log mode in SQLite 3.7 gets us better concurrency,
+ # but breaks backwards-compatibility with older versions of
+ # SQLite. (Which is important because people may also want to use
+ # command-line clients to access and back up their DB.) If you need
+ # better concurrency and don't need 3.6 compatibility, then you can
+ # uncomment this line.
+ #journal_mode => "'WAL'",
+ );
+
+ while (my ($name, $value) = each %pragmas) {
+ $dbh->do("PRAGMA $name = $value");
+ }
+
+ $dbh->sqlite_create_collation('bugzilla', \&_sqlite_collate_ci);
+ $dbh->sqlite_create_function('position', 2, \&_sqlite_position);
+ $dbh->sqlite_create_function('iposition', 2, \&_sqlite_position_ci);
+
+ # SQLite has a "substr" function, but other DBs call it "SUBSTRING"
+ # so that's what we use, and I don't know of any way in SQLite to
+ # alias the SQL "substr" function to be called "SUBSTRING".
+ $dbh->sqlite_create_function('substring', 3, \&CORE::substr);
+ $dbh->sqlite_create_function('mod', 2, \&_sqlite_mod);
+ $dbh->sqlite_create_function('now', 0, \&_sqlite_now);
+ $dbh->sqlite_create_function('localtimestamp', 1, \&_sqlite_now);
+ $dbh->sqlite_create_function('floor', 1, \&POSIX::floor);
}
###############
@@ -143,85 +150,88 @@ sub on_dbi_connected {
###############
sub sql_position {
- my ($self, $fragment, $text) = @_;
- return "POSITION($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "POSITION($text, $fragment)";
}
sub sql_iposition {
- my ($self, $fragment, $text) = @_;
- return "IPOSITION($text, $fragment)";
+ my ($self, $fragment, $text) = @_;
+ return "IPOSITION($text, $fragment)";
}
# SQLite does not have to GROUP BY the optional columns.
sub sql_group_by {
- my ($self, $needed_columns, $optional_columns) = @_;
- my $expression = "GROUP BY $needed_columns";
- return $expression;
+ my ($self, $needed_columns, $optional_columns) = @_;
+ my $expression = "GROUP BY $needed_columns";
+ return $expression;
}
# XXX SQLite does not support sorting a GROUP_CONCAT, so $sort is unimplemented.
sub sql_group_concat {
- my ($self, $column, $separator, $sort) = @_;
- $separator = $self->quote(', ') if !defined $separator;
- # In SQLite, a GROUP_CONCAT call with a DISTINCT argument can't
- # specify its separator, and has to accept the default of ",".
- if ($column =~ /^DISTINCT/) {
- return "GROUP_CONCAT($column)";
- }
- return "GROUP_CONCAT($column, $separator)";
+ my ($self, $column, $separator, $sort) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+
+ # In SQLite, a GROUP_CONCAT call with a DISTINCT argument can't
+ # specify its separator, and has to accept the default of ",".
+ if ($column =~ /^DISTINCT/) {
+ return "GROUP_CONCAT($column)";
+ }
+ return "GROUP_CONCAT($column, $separator)";
}
sub sql_istring {
- my ($self, $string) = @_;
- return $string;
+ my ($self, $string) = @_;
+ return $string;
}
sub sql_regexp {
- my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
- $real_pattern ||= $pattern;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- $self->bz_check_regexp($real_pattern) if !$nocheck;
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
- return "$expr REGEXP $pattern";
+ return "$expr REGEXP $pattern";
}
sub sql_not_regexp {
- my $self = shift;
- my $re_expression = $self->sql_regexp(@_);
- return "NOT($re_expression)";
+ my $self = shift;
+ my $re_expression = $self->sql_regexp(@_);
+ return "NOT($re_expression)";
}
sub sql_limit {
- my ($self, $limit, $offset) = @_;
-
- if (defined($offset)) {
- return "LIMIT $limit OFFSET $offset";
- } else {
- return "LIMIT $limit";
- }
+ my ($self, $limit, $offset) = @_;
+
+ if (defined($offset)) {
+ return "LIMIT $limit OFFSET $offset";
+ }
+ else {
+ return "LIMIT $limit";
+ }
}
sub sql_from_days {
- my ($self, $days) = @_;
- return "DATETIME($days)";
+ my ($self, $days) = @_;
+ return "DATETIME($days)";
}
sub sql_to_days {
- my ($self, $date) = @_;
- return "JULIANDAY($date)";
+ my ($self, $date) = @_;
+ return "JULIANDAY($date)";
}
sub sql_date_format {
- my ($self, $date, $format) = @_;
- $format = "%Y.%m.%d %H:%M:%S" if !$format;
- $format =~ s/\%i/\%M/g;
- return "STRFTIME(" . $self->quote($format) . ", $date)";
+ my ($self, $date, $format) = @_;
+ $format = "%Y.%m.%d %H:%M:%S" if !$format;
+ $format =~ s/\%i/\%M/g;
+ return "STRFTIME(" . $self->quote($format) . ", $date)";
}
sub sql_date_math {
- my ($self, $date, $operator, $interval, $units) = @_;
- # We do the || thing (concatenation) so that placeholders work properly.
- return "DATETIME($date, '$operator' || $interval || ' $units')";
+ my ($self, $date, $operator, $interval, $units) = @_;
+
+ # We do the || thing (concatenation) so that placeholders work properly.
+ return "DATETIME($date, '$operator' || $interval || ' $units')";
}
###############
@@ -229,56 +239,57 @@ sub sql_date_math {
###############
sub bz_setup_database {
- my $self = shift;
- $self->SUPER::bz_setup_database(@_);
-
- # If we created TheSchwartz tables with COLLATE bugzilla (during the
- # 4.1.x development series) re-create them without it.
- my @tables = $self->bz_table_list();
- my @ts_tables = grep { /^ts_/ } @tables;
- my $drop_ok;
- foreach my $table (@ts_tables) {
- my $create_table =
- $self->_bz_real_schema->_sqlite_create_table($table);
- if ($create_table =~ /COLLATE bugzilla/) {
- if (!$drop_ok) {
- _sqlite_jobqueue_drop_message();
- $drop_ok = 1;
- }
- $self->bz_drop_table($table);
- $self->bz_add_table($table);
- }
+ my $self = shift;
+ $self->SUPER::bz_setup_database(@_);
+
+ # If we created TheSchwartz tables with COLLATE bugzilla (during the
+ # 4.1.x development series) re-create them without it.
+ my @tables = $self->bz_table_list();
+ my @ts_tables = grep {/^ts_/} @tables;
+ my $drop_ok;
+ foreach my $table (@ts_tables) {
+ my $create_table = $self->_bz_real_schema->_sqlite_create_table($table);
+ if ($create_table =~ /COLLATE bugzilla/) {
+ if (!$drop_ok) {
+ _sqlite_jobqueue_drop_message();
+ $drop_ok = 1;
+ }
+ $self->bz_drop_table($table);
+ $self->bz_add_table($table);
}
+ }
}
sub _sqlite_jobqueue_drop_message {
- # This is not translated because this situation will only happen if
- # you are updating from a 4.1.x development version of Bugzilla using
- # SQLite, and we don't want to maintain this string in strings.txt.pl
- # forever for just this one uncommon circumstance.
- print <<END;
+
+ # This is not translated because this situation will only happen if
+ # you are updating from a 4.1.x development version of Bugzilla using
+ # SQLite, and we don't want to maintain this string in strings.txt.pl
+ # forever for just this one uncommon circumstance.
+ print <<END;
WARNING: We have to re-create all the database tables used by jobqueue.pl.
If there are any pending jobs in the database (that is, emails that
haven't been sent), they will be deleted.
END
- unless (Bugzilla->installation_answers->{NO_PAUSE}) {
- print install_string('enter_or_ctrl_c');
- getc;
- }
+ unless (Bugzilla->installation_answers->{NO_PAUSE}) {
+ print install_string('enter_or_ctrl_c');
+ getc;
+ }
}
# XXX This needs to be implemented.
sub bz_explain { }
sub bz_table_list_real {
- my $self = shift;
- my @tables = $self->SUPER::bz_table_list_real(@_);
- # SQLite includes a sqlite_sequence table in every database that isn't
- # one of our real tables. We exclude any table that starts with sqlite_,
- # just to be safe.
- @tables = grep { $_ !~ /^sqlite_/ } @tables;
- return @tables;
+ my $self = shift;
+ my @tables = $self->SUPER::bz_table_list_real(@_);
+
+ # SQLite includes a sqlite_sequence table in every database that isn't
+ # one of our real tables. We exclude any table that starts with sqlite_,
+ # just to be safe.
+ @tables = grep { $_ !~ /^sqlite_/ } @tables;
+ return @tables;
}
1;