From 77691d57c478404d33235d45cb94156efd3a95f2 Mon Sep 17 00:00:00 2001 From: "mkanat%kerio.com" <> Date: Sun, 17 Apr 2005 14:22:41 +0000 Subject: Bug 290402: Functions to support reading-in a Schema object from the database Patch by Max Kanat-Alexander r=Tomas.Kopal, a=justdave --- Bugzilla/DB/Schema/Mysql.pm | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) (limited to 'Bugzilla/DB/Schema/Mysql.pm') diff --git a/Bugzilla/DB/Schema/Mysql.pm b/Bugzilla/DB/Schema/Mysql.pm index cc30246cc..1ea1d285a 100644 --- a/Bugzilla/DB/Schema/Mysql.pm +++ b/Bugzilla/DB/Schema/Mysql.pm @@ -33,6 +33,57 @@ use Bugzilla::Error; use base qw(Bugzilla::DB::Schema); +# This is for column_info_to_column, to know when a tinyint is a +# boolean and when it's really a tinyint. This only has to be accurate +# up to and through 2.19.3, because that's the only time we need +# column_info_to_column. +# +# This is basically a hash of tables/columns, with one entry for each column +# that should be interpreted as a BOOLEAN instead of as an INT1 when +# reading in the Schema from the disk. The values are discarded; I just +# used "1" for simplicity. +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} +}; + +# 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. +}; + #------------------------------------------------------------------------------ sub _initialize { @@ -110,6 +161,123 @@ sub get_drop_index_ddl { return ("DROP INDEX \`$name\` ON $table"); } +# Converts a DBI column_info output to an abstract column definition. +# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk, +# 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; + } + } + + # 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 ddatetime 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; + } + } + + 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; + + } + + # 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}; + } + + $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)); -- cgit v1.2.3-24-g4f1b