123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376 |
- # -*- Mode: perl; indent-tabs-mode: nil -*-
- #
- # The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Terry Weissman <terry@mozilla.org>
- # Dan Mosedale <dmose@mozilla.org>
- # Jacob Steenhagen <jake@bugzilla.org>
- # Bradley Baetz <bbaetz@student.usyd.edu.au>
- # Christopher Aillon <christopher@aillon.com>
- # Tomas Kopal <Tomas.Kopal@altap.cz>
- # Max Kanat-Alexander <mkanat@bugzilla.org>
- # Lance Larsh <lance.larsh@oracle.com>
- package Bugzilla::DB;
- use strict;
- use DBI;
- # Inherit the DB class from DBI::db.
- use base qw(DBI::db);
- use Bugzilla::Constants;
- use Bugzilla::Install::Requirements;
- use Bugzilla::Install::Util qw(vers_cmp);
- use Bugzilla::Install::Localconfig;
- use Bugzilla::Util;
- use Bugzilla::Error;
- use Bugzilla::DB::Schema;
- use List::Util qw(max);
- use Storable qw(dclone);
- #####################################################################
- # Constants
- #####################################################################
- use constant BLOB_TYPE => DBI::SQL_BLOB;
- use constant ISOLATION_LEVEL => 'REPEATABLE READ';
- use constant GROUPBY_REGEXP => '(?:.*\s+AS\s+)?(\w+(\.\w+)?)(?:\s+(ASC|DESC))?$';
- # Set default values for what used to be the enum types. These values
- # are no longer stored in localconfig. If we are upgrading from a
- # Bugzilla with enums to a Bugzilla without enums, we use the
- # enum values.
- #
- # The values that you see here are ONLY DEFAULTS. They are only used
- # the FIRST time you run checksetup.pl, IF you are NOT upgrading from a
- # Bugzilla with enums. After that, they are either controlled through
- # the Bugzilla UI or through the DB.
- use constant ENUM_DEFAULTS => {
- bug_severity => ['blocker', 'critical', 'major', 'normal',
- 'minor', 'trivial', 'enhancement'],
- priority => ["P1","P2","P3","P4","P5"],
- op_sys => ["All","Windows","Mac OS","Linux","Other"],
- rep_platform => ["All","PC","Macintosh","Other"],
- bug_status => ["UNCONFIRMED","NEW","ASSIGNED","REOPENED","RESOLVED",
- "VERIFIED","CLOSED"],
- resolution => ["","FIXED","INVALID","WONTFIX", "DUPLICATE","WORKSFORME",
- "MOVED"],
- };
- #####################################################################
- # Connection Methods
- #####################################################################
- sub connect_shadow {
- my $params = Bugzilla->params;
- die "Tried to connect to non-existent shadowdb"
- unless $params->{'shadowdb'};
- my $lc = Bugzilla->localconfig;
- return _connect($lc->{db_driver}, $params->{"shadowdbhost"},
- $params->{'shadowdb'}, $params->{"shadowdbport"},
- $params->{"shadowdbsock"}, $lc->{db_user}, $lc->{db_pass});
- }
- sub connect_main {
- my $lc = Bugzilla->localconfig;
- return _connect($lc->{db_driver}, $lc->{db_host}, $lc->{db_name}, $lc->{db_port},
- $lc->{db_sock}, $lc->{db_user}, $lc->{db_pass});
- }
- sub _connect {
- my ($driver, $host, $dbname, $port, $sock, $user, $pass) = @_;
- my $pkg_module = DB_MODULE->{lc($driver)}->{db};
- # do the actual import
- eval ("require $pkg_module")
- || die ("'$driver' is not a valid choice for \$db_driver in "
- . " localconfig: " . $@);
- # instantiate the correct DB specific module
- my $dbh = $pkg_module->new($user, $pass, $host, $dbname, $port, $sock);
- return $dbh;
- }
- sub _handle_error {
- require Carp;
- # Cut down the error string to a reasonable size
- $_[0] = substr($_[0], 0, 2000) . ' ... ' . substr($_[0], -2000)
- if length($_[0]) > 4000;
- $_[0] = Carp::longmess($_[0]);
- return 0; # Now let DBI handle raising the error
- }
- sub bz_check_requirements {
- my ($output) = @_;
- my $lc = Bugzilla->localconfig;
- my $db = DB_MODULE->{lc($lc->{db_driver})};
- # Only certain values are allowed for $db_driver.
- if (!defined $db) {
- die "$lc->{db_driver} is not a valid choice for \$db_driver in"
- . bz_locations()->{'localconfig'};
- }
- die("It is not safe to run Bugzilla inside the 'mysql' database.\n"
- . "Please pick a different value for \$db_name in localconfig.")
- if $lc->{db_name} eq 'mysql';
- # Check the existence and version of the DBD that we need.
- my $dbd = $db->{dbd};
- my $sql_server = $db->{name};
- my $sql_want = $db->{db_version};
- unless (have_vers($dbd, $output)) {
- my $command = install_command($dbd);
- my $root = ROOT_USER;
- my $dbd_mod = $dbd->{module};
- my $dbd_ver = $dbd->{version};
- my $version = $dbd_ver ? " $dbd_ver or higher" : '';
- print <<EOT;
- For $sql_server, Bugzilla requires that perl's $dbd_mod $dbd_ver or later be
- installed. To install this module, run the following command (as $root):
- $command
- EOT
- exit;
- }
- # We don't try to connect to the actual database if $db_check is
- # disabled.
- unless ($lc->{db_check}) {
- print "\n" if $output;
- return;
- }
- # And now check the version of the database server itself.
- my $dbh = _get_no_db_connection();
- printf("Checking for %15s %-9s ", $sql_server, "(v$sql_want)")
- if $output;
- my $sql_vers = $dbh->bz_server_version;
- $dbh->disconnect;
- # Check what version of the database server is installed and let
- # the user know if the version is too old to be used with Bugzilla.
- if ( vers_cmp($sql_vers,$sql_want) > -1 ) {
- print "ok: found v$sql_vers\n" if $output;
- } else {
- print <<EOT;
- Your $sql_server v$sql_vers is too old. Bugzilla requires version
- $sql_want or later of $sql_server. Please download and install a
- newer version.
- EOT
- exit;
- }
- print "\n" if $output;
- }
- # Note that this function requires that localconfig exist and
- # be valid.
- sub bz_create_database {
- my $dbh;
- # See if we can connect to the actual Bugzilla database.
- my $conn_success = eval { $dbh = connect_main(); };
- my $db_name = Bugzilla->localconfig->{db_name};
- if (!$conn_success) {
- $dbh = _get_no_db_connection();
- print "Creating database $db_name...\n";
- # Try to create the DB, and if we fail print a friendly error.
- my $success = eval {
- my @sql = $dbh->_bz_schema->get_create_database_sql($db_name);
- # This ends with 1 because this particular do doesn't always
- # return something.
- $dbh->do($_) foreach @sql; 1;
- };
- if (!$success) {
- my $error = $dbh->errstr || $@;
- chomp($error);
- print STDERR "The '$db_name' database could not be created.",
- " The error returned was:\n\n $error\n\n",
- _bz_connect_error_reasons();
- exit;
- }
- }
- $dbh->disconnect;
- }
- # A helper for bz_create_database and bz_check_requirements.
- sub _get_no_db_connection {
- my ($sql_server) = @_;
- my $dbh;
- my $lc = Bugzilla->localconfig;
- my $conn_success = eval {
- $dbh = _connect($lc->{db_driver}, $lc->{db_host}, '', $lc->{db_port},
- $lc->{db_sock}, $lc->{db_user}, $lc->{db_pass});
- };
- if (!$conn_success) {
- my $sql_server = DB_MODULE->{lc($lc->{db_driver})}->{name};
- # Can't use $dbh->errstr because $dbh is undef.
- my $error = $DBI::errstr || $@;
- chomp($error);
- print STDERR "There was an error connecting to $sql_server:\n\n",
- " $error\n\n", _bz_connect_error_reasons();
- exit;
- }
- return $dbh;
- }
- # Just a helper because we have to re-use this text.
- # We don't use this in db_new because it gives away the database
- # username, and db_new errors can show up on CGIs.
- sub _bz_connect_error_reasons {
- my $lc_file = bz_locations()->{'localconfig'};
- my $lc = Bugzilla->localconfig;
- my $db = DB_MODULE->{lc($lc->{db_driver})};
- my $server = $db->{name};
- return <<EOT;
- This might have several reasons:
- * $server is not running.
- * $server is running, but there is a problem either in the
- server configuration or the database access rights. Read the Bugzilla
- Guide in the doc directory. The section about database configuration
- should help.
- * Your password for the '$lc->{db_user}' user, specified in \$db_pass, is
- incorrect, in '$lc_file'.
- * There is a subtle problem with Perl, DBI, or $server. Make
- sure all settings in '$lc_file' are correct. If all else fails, set
- '\$db_check' to 0.
- EOT
- }
- # List of abstract methods we are checking the derived class implements
- our @_abstract_methods = qw(REQUIRED_VERSION PROGRAM_NAME DBD_VERSION
- new sql_regexp sql_not_regexp sql_limit sql_to_days
- sql_date_format sql_interval);
- # This overridden import method will check implementation of inherited classes
- # for missing implementation of abstract methods
- # See http://perlmonks.thepen.com/44265.html
- sub import {
- my $pkg = shift;
- # do not check this module
- if ($pkg ne __PACKAGE__) {
- # make sure all abstract methods are implemented
- foreach my $meth (@_abstract_methods) {
- $pkg->can($meth)
- or croak("Class $pkg does not define method $meth");
- }
- }
- # Now we want to call our superclass implementation.
- # If our superclass is Exporter, which is using caller() to find
- # a namespace to populate, we need to adjust for this extra call.
- # All this can go when we stop using deprecated functions.
- my $is_exporter = $pkg->isa('Exporter');
- $Exporter::ExportLevel++ if $is_exporter;
- $pkg->SUPER::import(@_);
- $Exporter::ExportLevel-- if $is_exporter;
- }
- sub sql_istrcmp {
- my ($self, $left, $right, $op) = @_;
- $op ||= "=";
- return $self->sql_istring($left) . " $op " . $self->sql_istring($right);
- }
- sub sql_istring {
- my ($self, $string) = @_;
- return "LOWER($string)";
- }
- sub sql_iposition {
- my ($self, $fragment, $text) = @_;
- $fragment = $self->sql_istring($fragment);
- $text = $self->sql_istring($text);
- return $self->sql_position($fragment, $text);
- }
- sub sql_position {
- my ($self, $fragment, $text) = @_;
- return "POSITION($fragment IN $text)";
- }
- sub sql_group_by {
- my ($self, $needed_columns, $optional_columns) = @_;
- my $expression = "GROUP BY $needed_columns";
- $expression .= ", " . $optional_columns if $optional_columns;
-
- return $expression;
- }
- sub sql_string_concat {
- my ($self, @params) = @_;
-
- return '(' . join(' || ', @params) . ')';
- }
- sub sql_in {
- my ($self, $column_name, $in_list_ref) = @_;
- return " $column_name IN (" . join(',', @$in_list_ref) . ") ";
- }
- sub sql_fulltext_search {
- my ($self, $column, $text) = @_;
- # This is as close as we can get to doing full text search using
- # standard ANSI SQL, without real full text search support. DB specific
- # modules should override this, as this will be always much slower.
- # make the string lowercase to do case insensitive search
- my $lower_text = lc($text);
- # split the text we search for into separate words
- my @words = split(/\s+/, $lower_text);
- # surround the words with wildcards and SQL quotes so we can use them
- # in LIKE search clauses
- @words = map($self->quote("%$_%"), @words);
- # untaint words, since they are safe to use now that we've quoted them
- map(trick_taint($_), @words);
- # turn the words into a set of LIKE search clauses
- @words = map("LOWER($column) LIKE $_", @words);
- # search for occurrences of all specified words in the column
- return "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
- }
- #####################################################################
- # General Info Methods
- #####################################################################
- # XXX - Needs to be documented.
- sub bz_server_version {
- my ($self) = @_;
- return $self->get_info(18); # SQL_DBMS_VER
- }
- sub bz_last_key {
- my ($self, $table, $column) = @_;
- return $self->last_insert_id(Bugzilla->localconfig->{db_name}, undef,
- $table, $column);
- }
- #####################################################################
- # Database Setup
- #####################################################################
- sub bz_setup_database {
- my ($self) = @_;
- # If we haven't ever stored a serialized schema,
- # set up the bz_schema table and store it.
- $self->_bz_init_schema_storage();
-
- my @desired_tables = $self->_bz_schema->get_table_list();
- foreach my $table_name (@desired_tables) {
- $self->bz_add_table($table_name);
- }
- }
- # This really just exists to get overridden in Bugzilla::DB::Mysql.
- sub bz_enum_initial_values {
- return ENUM_DEFAULTS;
- }
- sub bz_populate_enum_tables {
- my ($self) = @_;
- my $enum_values = $self->bz_enum_initial_values();
- while (my ($table, $values) = each %$enum_values) {
- $self->_bz_populate_enum_table($table, $values);
- }
- }
- sub bz_setup_foreign_keys {
- my ($self) = @_;
- # We use _bz_schema because bz_add_table has removed all REFERENCES
- # items from _bz_real_schema.
- my @tables = $self->_bz_schema->get_table_list();
- foreach my $table (@tables) {
- my @columns = $self->_bz_schema->get_table_columns($table);
- foreach my $column (@columns) {
- my $def = $self->_bz_schema->get_column_abstract($table, $column);
- if ($def->{REFERENCES}) {
- $self->bz_add_fk($table, $column, $def->{REFERENCES});
- }
- }
- }
- }
- # This is used by contrib/bzdbcopy.pl, mostly.
- sub bz_drop_foreign_keys {
- my ($self) = @_;
- my @tables = $self->_bz_real_schema->get_table_list();
- foreach my $table (@tables) {
- my @columns = $self->_bz_real_schema->get_table_columns($table);
- foreach my $column (@columns) {
- $self->bz_drop_fk($table, $column);
- }
- }
- }
- #####################################################################
- # Schema Modification Methods
- #####################################################################
- sub bz_add_column {
- my ($self, $table, $name, $new_def, $init_value) = @_;
- # You can't add a NOT NULL column to a table with
- # no DEFAULT statement, unless you have an init_value.
- # SERIAL types are an exception, though, because they can
- # auto-populate.
- if ( $new_def->{NOTNULL} && !exists $new_def->{DEFAULT}
- && !defined $init_value && $new_def->{TYPE} !~ /SERIAL/)
- {
- ThrowCodeError('column_not_null_without_default',
- { name => "$table.$name" });
- }
- my $current_def = $self->bz_column_info($table, $name);
- if (!$current_def) {
- my @statements = $self->_bz_real_schema->get_add_column_ddl(
- $table, $name, $new_def,
- defined $init_value ? $self->quote($init_value) : undef);
- print get_text('install_column_add',
- { column => $name, table => $table }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- $self->do($sql);
- }
- $self->_bz_real_schema->set_column($table, $name, $new_def);
- $self->_bz_store_real_schema;
- }
- }
- sub bz_add_fk {
- my ($self, $table, $column, $def) = @_;
- my $col_def = $self->bz_column_info($table, $column);
- if (!$col_def->{REFERENCES}) {
- $self->_check_references($table, $column, $def->{TABLE},
- $def->{COLUMN});
- print get_text('install_fk_add',
- { table => $table, column => $column, fk => $def })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- my @sql = $self->_bz_real_schema->get_add_fk_sql($table, $column, $def);
- $self->do($_) foreach @sql;
- $col_def->{REFERENCES} = $def;
- $self->_bz_real_schema->set_column($table, $column, $col_def);
- $self->_bz_store_real_schema;
- }
- }
- sub bz_alter_column {
- my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
- my $current_def = $self->bz_column_info($table, $name);
- if (!$self->_bz_schema->columns_equal($current_def, $new_def)) {
- # You can't change a column to be NOT NULL if you have no DEFAULT
- # and no value for $set_nulls_to, if there are any NULL values
- # in that column.
- if ($new_def->{NOTNULL} &&
- !exists $new_def->{DEFAULT} && !defined $set_nulls_to)
- {
- # Check for NULLs
- my $any_nulls = $self->selectrow_array(
- "SELECT 1 FROM $table WHERE $name IS NULL");
- ThrowCodeError('column_not_null_no_default_alter',
- { name => "$table.$name" }) if ($any_nulls);
- }
- $self->bz_alter_column_raw($table, $name, $new_def, $current_def,
- $set_nulls_to);
- $self->_bz_real_schema->set_column($table, $name, $new_def);
- $self->_bz_store_real_schema;
- }
- }
- # bz_alter_column_raw($table, $name, $new_def, $current_def)
- #
- # Description: A helper function for bz_alter_column.
- # Alters a column in the database
- # without updating any Schema object. Generally
- # should only be called by bz_alter_column.
- # Used when either: (1) You don't yet have a Schema
- # object but you need to alter a column, for some reason.
- # (2) You need to alter a column for some database-specific
- # reason.
- # Params: $table - The name of the table the column is on.
- # $name - The name of the column you're changing.
- # $new_def - The abstract definition that you are changing
- # this column to.
- # $current_def - (optional) The current definition of the
- # column. Will be used in the output message,
- # if given.
- # $set_nulls_to - The same as the param of the same name
- # from bz_alter_column.
- # Returns: nothing
- #
- sub bz_alter_column_raw {
- my ($self, $table, $name, $new_def, $current_def, $set_nulls_to) = @_;
- my @statements = $self->_bz_real_schema->get_alter_column_ddl(
- $table, $name, $new_def,
- defined $set_nulls_to ? $self->quote($set_nulls_to) : undef);
- my $new_ddl = $self->_bz_schema->get_type_ddl($new_def);
- print "Updating column $name in table $table ...\n";
- if (defined $current_def) {
- my $old_ddl = $self->_bz_schema->get_type_ddl($current_def);
- print "Old: $old_ddl\n";
- }
- print "New: $new_ddl\n";
- $self->do($_) foreach (@statements);
- }
- sub bz_add_index {
- my ($self, $table, $name, $definition) = @_;
- my $index_exists = $self->bz_index_info($table, $name);
- if (!$index_exists) {
- $self->bz_add_index_raw($table, $name, $definition);
- $self->_bz_real_schema->set_index($table, $name, $definition);
- $self->_bz_store_real_schema;
- }
- }
- # bz_add_index_raw($table, $name, $silent)
- #
- # Description: A helper function for bz_add_index.
- # Adds an index to the database
- # without updating any Schema object. Generally
- # should only be called by bz_add_index.
- # Used when you don't yet have a Schema
- # object but you need to add an index, for some reason.
- # Params: $table - The name of the table the index is on.
- # $name - The name of the index you're adding.
- # $definition - The abstract index definition, in hashref
- # or arrayref format.
- # $silent - (optional) If specified and true, don't output
- # any message about this change.
- # Returns: nothing
- #
- sub bz_add_index_raw {
- my ($self, $table, $name, $definition, $silent) = @_;
- my @statements = $self->_bz_schema->get_add_index_ddl(
- $table, $name, $definition);
- print "Adding new index '$name' to the $table table ...\n" unless $silent;
- $self->do($_) foreach (@statements);
- }
- sub bz_add_table {
- my ($self, $name) = @_;
- my $table_exists = $self->bz_table_info($name);
- if (!$table_exists) {
- $self->_bz_add_table_raw($name);
- my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
- my %fields = @{$table_def->{FIELDS}};
- foreach my $col (keys %fields) {
- # Foreign Key references have to be added by Install::DB after
- # initial table creation, because column names have changed
- # over history and it's impossible to keep track of that info
- # in ABSTRACT_SCHEMA.
- delete $fields{$col}->{REFERENCES};
- }
- $self->_bz_real_schema->add_table($name, $table_def);
- $self->_bz_store_real_schema;
- }
- }
- # _bz_add_table_raw($name) - Private
- #
- # Description: A helper function for bz_add_table.
- # Creates a table in the database without
- # updating any Schema object. Generally
- # should only be called by bz_add_table and by
- # _bz_init_schema_storage. Used when you don't
- # yet have a Schema object but you need to
- # add a table, for some reason.
- # Params: $name - The name of the table you're creating.
- # The definition for the table is pulled from
- # _bz_schema.
- # Returns: nothing
- #
- sub _bz_add_table_raw {
- my ($self, $name) = @_;
- my @statements = $self->_bz_schema->get_table_ddl($name);
- print "Adding new table $name ...\n" unless i_am_cgi();
- $self->do($_) foreach (@statements);
- }
- sub _bz_add_field_table {
- my ($self, $name, $schema_ref) = @_;
- # We do nothing if the table already exists.
- return if $self->bz_table_info($name);
- # Copy this so that we're not modifying the passed reference.
- # (This avoids modifying a constant in Bugzilla::DB::Schema.)
- my %table_schema = %$schema_ref;
- my %indexes = @{ $table_schema{INDEXES} };
- my %fixed_indexes;
- foreach my $key (keys %indexes) {
- $fixed_indexes{$name . "_" . $key} = $indexes{$key};
- }
- # INDEXES is supposed to be an arrayref, so we have to convert back.
- my @indexes_array = %fixed_indexes;
- $table_schema{INDEXES} = \@indexes_array;
- # We add this to the abstract schema so that bz_add_table can find it.
- $self->_bz_schema->add_table($name, \%table_schema);
- $self->bz_add_table($name);
- }
- sub bz_add_field_tables {
- my ($self, $field) = @_;
-
- $self->_bz_add_field_table($field->name,
- $self->_bz_schema->FIELD_TABLE_SCHEMA);
- if ( $field->type == FIELD_TYPE_MULTI_SELECT ) {
- $self->_bz_add_field_table('bug_' . $field->name,
- $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
- }
- }
- sub bz_drop_field_tables {
- my ($self, $field) = @_;
- if ($field->type == FIELD_TYPE_MULTI_SELECT) {
- $self->bz_drop_table('bug_' . $field->name);
- }
- $self->bz_drop_table($field->name);
- }
- sub bz_drop_column {
- my ($self, $table, $column) = @_;
- my $current_def = $self->bz_column_info($table, $column);
- if ($current_def) {
- my @statements = $self->_bz_real_schema->get_drop_column_ddl(
- $table, $column);
- print get_text('install_column_drop',
- { table => $table, column => $column }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- # Because this is a deletion, we don't want to die hard if
- # we fail because of some local customization. If something
- # is already gone, that's fine with us!
- eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
- }
- $self->_bz_real_schema->delete_column($table, $column);
- $self->_bz_store_real_schema;
- }
- }
- sub bz_drop_fk {
- my ($self, $table, $column) = @_;
- my $col_def = $self->bz_column_info($table, $column);
- if ($col_def && exists $col_def->{REFERENCES}) {
- my $def = $col_def->{REFERENCES};
- print get_text('install_fk_drop',
- { table => $table, column => $column, fk => $def })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- my @sql = $self->_bz_real_schema->get_drop_fk_sql($table,$column,$def);
- $self->do($_) foreach @sql;
- delete $col_def->{REFERENCES};
- $self->_bz_real_schema->set_column($table, $column, $col_def);
- $self->_bz_store_real_schema;
- }
- }
- sub bz_drop_index {
- my ($self, $table, $name) = @_;
- my $index_exists = $self->bz_index_info($table, $name);
- if ($index_exists) {
- $self->bz_drop_index_raw($table, $name);
- $self->_bz_real_schema->delete_index($table, $name);
- $self->_bz_store_real_schema;
- }
- }
- # bz_drop_index_raw($table, $name, $silent)
- #
- # Description: A helper function for bz_drop_index.
- # Drops an index from the database
- # without updating any Schema object. Generally
- # should only be called by bz_drop_index.
- # Used when either: (1) You don't yet have a Schema
- # object but you need to drop an index, for some reason.
- # (2) You need to drop an index that somehow got into the
- # database but doesn't exist in Schema.
- # Params: $table - The name of the table the index is on.
- # $name - The name of the index you're dropping.
- # $silent - (optional) If specified and true, don't output
- # any message about this change.
- # Returns: nothing
- #
- sub bz_drop_index_raw {
- my ($self, $table, $name, $silent) = @_;
- my @statements = $self->_bz_schema->get_drop_index_ddl(
- $table, $name);
- print "Removing index '$name' from the $table table...\n" unless $silent;
- foreach my $sql (@statements) {
- # Because this is a deletion, we don't want to die hard if
- # we fail because of some local customization. If something
- # is already gone, that's fine with us!
- eval { $self->do($sql) } or warn "Failed SQL: [$sql] Error: $@";
- }
- }
- sub bz_drop_table {
- my ($self, $name) = @_;
- my $table_exists = $self->bz_table_info($name);
- if ($table_exists) {
- my @statements = $self->_bz_schema->get_drop_table_ddl($name);
- print get_text('install_table_drop', { name => $name }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- # Because this is a deletion, we don't want to die hard if
- # we fail because of some local customization. If something
- # is already gone, that's fine with us!
- eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
- }
- $self->_bz_real_schema->delete_table($name);
- $self->_bz_store_real_schema;
- }
- }
- sub bz_rename_column {
- my ($self, $table, $old_name, $new_name) = @_;
- my $old_col_exists = $self->bz_column_info($table, $old_name);
- if ($old_col_exists) {
- my $already_renamed = $self->bz_column_info($table, $new_name);
- ThrowCodeError('db_rename_conflict',
- { old => "$table.$old_name",
- new => "$table.$new_name" }) if $already_renamed;
- my @statements = $self->_bz_real_schema->get_rename_column_ddl(
- $table, $old_name, $new_name);
- print get_text('install_column_rename',
- { old => "$table.$old_name", new => "$table.$new_name" })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- $self->do($sql);
- }
- $self->_bz_real_schema->rename_column($table, $old_name, $new_name);
- $self->_bz_store_real_schema;
- }
- }
- sub bz_rename_table {
- my ($self, $old_name, $new_name) = @_;
- my $old_table = $self->bz_table_info($old_name);
- return if !$old_table;
- my $new = $self->bz_table_info($new_name);
- ThrowCodeError('db_rename_conflict', { old => $old_name,
- new => $new_name }) if $new;
- my @sql = $self->_bz_real_schema->get_rename_table_sql($old_name, $new_name);
- print get_text('install_table_rename',
- { old => $old_name, new => $new_name }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- $self->do($_) foreach @sql;
- $self->_bz_real_schema->rename_table($old_name, $new_name);
- $self->_bz_store_real_schema;
- }
- #####################################################################
- # Schema Information Methods
- #####################################################################
- sub _bz_schema {
- my ($self) = @_;
- return $self->{private_bz_schema} if exists $self->{private_bz_schema};
- my @module_parts = split('::', ref $self);
- my $module_name = pop @module_parts;
- $self->{private_bz_schema} = Bugzilla::DB::Schema->new($module_name);
- return $self->{private_bz_schema};
- }
- # _bz_get_initial_schema()
- #
- # Description: A protected method, intended for use only by Bugzilla::DB
- # and subclasses. Used to get the initial Schema that will
- # be written to disk for _bz_init_schema_storage. You probably
- # want to use _bz_schema or _bz_real_schema instead of this
- # method.
- # Params: none
- # Returns: A Schema object that can be serialized and written to disk
- # for _bz_init_schema_storage.
- sub _bz_get_initial_schema {
- my ($self) = @_;
- return $self->_bz_schema->get_empty_schema();
- }
- sub bz_column_info {
- my ($self, $table, $column) = @_;
- my $def = $self->_bz_real_schema->get_column_abstract($table, $column);
- # We dclone it so callers can't modify the Schema.
- $def = dclone($def) if defined $def;
- return $def;
- }
- sub bz_index_info {
- my ($self, $table, $index) = @_;
- my $index_def =
- $self->_bz_real_schema->get_index_abstract($table, $index);
- if (ref($index_def) eq 'ARRAY') {
- $index_def = {FIELDS => $index_def, TYPE => ''};
- }
- return $index_def;
- }
- sub bz_table_info {
- my ($self, $table) = @_;
- return $self->_bz_real_schema->get_table_abstract($table);
- }
- sub bz_table_columns {
- my ($self, $table) = @_;
- return $self->_bz_real_schema->get_table_columns($table);
- }
- sub bz_table_indexes {
- my ($self, $table) = @_;
- my $indexes = $self->_bz_real_schema->get_table_indexes_abstract($table);
- my %return_indexes;
- # We do this so that they're always hashes.
- foreach my $name (keys %$indexes) {
- $return_indexes{$name} = $self->bz_index_info($table, $name);
- }
- return \%return_indexes;
- }
- #####################################################################
- # Protected "Real Database" Schema Information Methods
- #####################################################################
- # Only Bugzilla::DB and subclasses should use these methods.
- # If you need a method that does the same thing as one of these
- # methods, use the version without _real on the end.
- # bz_table_columns_real($table)
- #
- # Description: Returns a list of columns on a given table
- # as the table actually is, on the disk.
- # Params: $table - Name of the table.
- # Returns: An array of column names.
- #
- sub bz_table_columns_real {
- my ($self, $table) = @_;
- my $sth = $self->column_info(undef, undef, $table, '%');
- return @{ $self->selectcol_arrayref($sth, {Columns => [4]}) };
- }
- # bz_table_list_real()
- #
- # Description: Gets a list of tables in the current
- # database, directly from the disk.
- # Params: none
- # Returns: An array containing table names.
- sub bz_table_list_real {
- my ($self) = @_;
- my $table_sth = $self->table_info(undef, undef, undef, "TABLE");
- return @{$self->selectcol_arrayref($table_sth, { Columns => [3] })};
- }
- #####################################################################
- # Transaction Methods
- #####################################################################
- sub bz_in_transaction {
- return $_[0]->{private_bz_transaction_count} ? 1 : 0;
- }
- sub bz_start_transaction {
- my ($self) = @_;
- if ($self->bz_in_transaction) {
- $self->{private_bz_transaction_count}++;
- } else {
- # Turn AutoCommit off and start a new transaction
- $self->begin_work();
- # REPEATABLE READ means "We work on a snapshot of the DB that
- # is created when we execute our first SQL statement." It's
- # what we need in Bugzilla to be safe, for what we do.
- # Different DBs have different defaults for their isolation
- # level, so we just set it here manually.
- $self->do('SET TRANSACTION ISOLATION LEVEL ' . $self->ISOLATION_LEVEL);
- $self->{private_bz_transaction_count} = 1;
- }
- }
- sub bz_commit_transaction {
- my ($self) = @_;
-
- if ($self->{private_bz_transaction_count} > 1) {
- $self->{private_bz_transaction_count}--;
- } elsif ($self->bz_in_transaction) {
- $self->commit();
- $self->{private_bz_transaction_count} = 0;
- } else {
- ThrowCodeError('not_in_transaction');
- }
- }
- sub bz_rollback_transaction {
- my ($self) = @_;
- # Unlike start and commit, if we rollback at any point it happens
- # instantly, even if we're in a nested transaction.
- if (!$self->bz_in_transaction) {
- ThrowCodeError("not_in_transaction");
- } else {
- $self->rollback();
- $self->{private_bz_transaction_count} = 0;
- }
- }
- #####################################################################
- # Subclass Helpers
- #####################################################################
- sub db_new {
- my ($class, $dsn, $user, $pass, $override_attrs) = @_;
- # set up default attributes used to connect to the database
- # (may be overridden by DB driver implementations)
- my $attributes = { RaiseError => 0,
- AutoCommit => 1,
- PrintError => 0,
- ShowErrorStatement => 1,
- HandleError => \&_handle_error,
- TaintIn => 1,
- FetchHashKeyName => 'NAME',
- # Note: NAME_lc causes crash on ActiveState Perl
- # 5.8.4 (see Bug 253696)
- # XXX - This will likely cause problems in DB
- # back ends that twiddle column case (Oracle?)
- };
- if ($override_attrs) {
- foreach my $key (keys %$override_attrs) {
- $attributes->{$key} = $override_attrs->{$key};
- }
- }
- # connect using our known info to the specified db
- my $self = DBI->connect($dsn, $user, $pass, $attributes)
- or die "\nCan't connect to the database.\nError: $DBI::errstr\n"
- . " Is your database installed and up and running?\n Do you have"
- . " the correct username and password selected in localconfig?\n\n";
- # RaiseError was only set to 0 so that we could catch the
- # above "die" condition.
- $self->{RaiseError} = 1;
- bless ($self, $class);
- return $self;
- }
- #####################################################################
- # Private Methods
- #####################################################################
- =begin private
- =head1 PRIVATE METHODS
- These methods really are private. Do not override them in subclasses.
- =over 4
- =item C<_init_bz_schema_storage>
- Description: Initializes the bz_schema table if it contains nothing.
- Params: none
- Returns: nothing
- =cut
- sub _bz_init_schema_storage {
- my ($self) = @_;
- my $table_size;
- eval {
- $table_size =
- $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
- };
- if (!$table_size) {
- my $init_schema = $self->_bz_get_initial_schema;
- my $store_me = $init_schema->serialize_abstract();
- my $schema_version = $init_schema->SCHEMA_VERSION;
- # If table_size is not defined, then we hit an error reading the
- # bz_schema table, which means it probably doesn't exist yet. So,
- # we have to create it. If we failed above for some other reason,
- # we'll see the failure here.
- # However, we must create the table after we do get_initial_schema,
- # because some versions of get_initial_schema read that the table
- # exists and then add it to the Schema, where other versions don't.
- if (!defined $table_size) {
- $self->_bz_add_table_raw('bz_schema');
- }
- print "Initializing the new Schema storage...\n";
- my $sth = $self->prepare("INSERT INTO bz_schema "
- ." (schema_data, version) VALUES (?,?)");
- $sth->bind_param(1, $store_me, $self->BLOB_TYPE);
- $sth->bind_param(2, $schema_version);
- $sth->execute();
- # And now we have to update the on-disk schema to hold the bz_schema
- # table, if the bz_schema table didn't exist when we were called.
- if (!defined $table_size) {
- $self->_bz_real_schema->add_table('bz_schema',
- $self->_bz_schema->get_table_abstract('bz_schema'));
- $self->_bz_store_real_schema;
- }
- }
- # Sanity check
- elsif ($table_size > 1) {
- # We tell them to delete the newer one. Better to have checksetup
- # run migration code too many times than to have it not run the
- # correct migration code at all.
- die "Attempted to initialize the schema but there are already "
- . " $table_size copies of it stored.\nThis should never happen.\n"
- . " Compare the rows of the bz_schema table and delete the "
- . "newer one(s).";
- }
- }
- =item C<_bz_real_schema()>
- Description: Returns a Schema object representing the database
- that is being used in the current installation.
- Params: none
- Returns: A C<Bugzilla::DB::Schema> object representing the database
- as it exists on the disk.
- =cut
- sub _bz_real_schema {
- my ($self) = @_;
- return $self->{private_real_schema} if exists $self->{private_real_schema};
- my ($data, $version) = $self->selectrow_array(
- "SELECT schema_data, version FROM bz_schema");
- (die "_bz_real_schema tried to read the bz_schema table but it's empty!")
- if !$data;
- $self->{private_real_schema} =
- $self->_bz_schema->deserialize_abstract($data, $version);
- return $self->{private_real_schema};
- }
- =item C<_bz_store_real_schema()>
- Description: Stores the _bz_real_schema structures in the database
- for later recovery. Call this function whenever you make
- a change to the _bz_real_schema.
- Params: none
- Returns: nothing
- Precondition: $self->{_bz_real_schema} must exist.
- =back
- =end private
- =cut
- sub _bz_store_real_schema {
- my ($self) = @_;
- # Make sure that there's a schema to update
- my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
- die "Attempted to update the bz_schema table but there's nothing "
- . "there to update. Run checksetup." unless $table_size;
- # We want to store the current object, not one
- # that we read from the database. So we use the actual hash
- # member instead of the subroutine call. If the hash
- # member is not defined, we will (and should) fail.
- my $update_schema = $self->{private_real_schema};
- my $store_me = $update_schema->serialize_abstract();
- my $schema_version = $update_schema->SCHEMA_VERSION;
- my $sth = $self->prepare("UPDATE bz_schema
- SET schema_data = ?, version = ?");
- $sth->bind_param(1, $store_me, $self->BLOB_TYPE);
- $sth->bind_param(2, $schema_version);
- $sth->execute();
- }
- # For bz_populate_enum_tables
- sub _bz_populate_enum_table {
- my ($self, $table, $valuelist) = @_;
- my $sql_table = $self->quote_identifier($table);
- # Check if there are any table entries
- my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM $sql_table");
- # If the table is empty...
- if (!$table_size) {
- my $insert = $self->prepare(
- "INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
- print "Inserting values into the '$table' table:\n";
- my $sortorder = 0;
- my $maxlen = max(map(length($_), @$valuelist)) + 2;
- foreach my $value (@$valuelist) {
- $sortorder += 100;
- printf "%-${maxlen}s sortkey: $sortorder\n", "'$value'";
- $insert->execute($value, $sortorder);
- }
- }
- }
- # This is used before adding a foreign key to a column, to make sure
- # that the database won't fail adding the key.
- sub _check_references {
- my ($self, $table, $column, $foreign_table, $foreign_column) = @_;
- my $bad_values = $self->selectcol_arrayref(
- "SELECT DISTINCT $table.$column
- FROM $table LEFT JOIN $foreign_table
- ON $table.$column = $foreign_table.$foreign_column
- WHERE $foreign_table.$foreign_column IS NULL
- AND $table.$column IS NOT NULL");
- if (@$bad_values) {
- my $values = join(', ', @$bad_values);
- print <<EOT;
- ERROR: There are invalid values for the $column column in the $table
- table. (These values do not exist in the $foreign_table table, in the
- $foreign_column column.)
- Before continuing with checksetup, you will need to fix these values,
- either by deleting these rows from the database, or changing the values
- of $column in $table to point to valid values in $foreign_table.$foreign_column.
- The bad values from the $table.$column column are:
- $values
- EOT
- # I just picked a number above 2, to be considered "abnormal exit."
- exit 3;
- }
- }
- 1;
- __END__
- =head1 NAME
- Bugzilla::DB - Database access routines, using L<DBI>
- =head1 SYNOPSIS
- # Obtain db handle
- use Bugzilla::DB;
- my $dbh = Bugzilla->dbh;
- # prepare a query using DB methods
- my $sth = $dbh->prepare("SELECT " .
- $dbh->sql_date_format("creation_ts", "%Y%m%d") .
- " FROM bugs WHERE bug_status != 'RESOLVED' " .
- $dbh->sql_limit(1));
- # Execute the query
- $sth->execute;
- # Get the results
- my @result = $sth->fetchrow_array;
- # Schema Modification
- $dbh->bz_add_column($table, $name, \%definition, $init_value);
- $dbh->bz_add_index($table, $name, $definition);
- $dbh->bz_add_table($name);
- $dbh->bz_drop_index($table, $name);
- $dbh->bz_drop_table($name);
- $dbh->bz_alter_column($table, $name, \%new_def, $set_nulls_to);
- $dbh->bz_drop_column($table, $column);
- $dbh->bz_rename_column($table, $old_name, $new_name);
- # Schema Information
- my $column = $dbh->bz_column_info($table, $column);
- my $index = $dbh->bz_index_info($table, $index);
- =head1 DESCRIPTION
- Functions in this module allows creation of a database handle to connect
- to the Bugzilla database. This should never be done directly; all users
- should use the L<Bugzilla> module to access the current C<dbh> instead.
- This module also contains methods extending the returned handle with
- functionality which is different between databases allowing for easy
- customization for particular database via inheritance. These methods
- should be always preffered over hard-coding SQL commands.
- =head1 CONSTANTS
- Subclasses of Bugzilla::DB are required to define certain constants. These
- constants are required to be subroutines or "use constant" variables.
- =over
- =item C<BLOB_TYPE>
- The C<\%attr> argument that must be passed to bind_param in order to
- correctly escape a C<LONGBLOB> type.
- =item C<ISOLATION_LEVEL>
- The argument that this database should send to
- C<SET TRANSACTION ISOLATION LEVEL> when starting a transaction. If you
- override this in a subclass, the isolation level you choose should
- be as strict as or more strict than the default isolation level defined in
- L<Bugzilla::DB>.
- =back
- =head1 CONNECTION
- A new database handle to the required database can be created using this
- module. This is normally done by the L<Bugzilla> module, and so these routines
- should not be called from anywhere else.
- =head2 Functions
- =over
- =item C<connect_main>
- =over
- =item B<Description>
- Function to connect to the main database, returning a new database handle.
- =item B<Params>
- =over
- =item C<$no_db_name> (optional) - If true, connect to the database
- server, but don't connect to a specific database. This is only used
- when creating a database. After you create the database, you should
- re-create a new Bugzilla::DB object without using this parameter.
- =back
- =item B<Returns>
- New instance of the DB class
- =back
- =item C<connect_shadow>
- =over
- =item B<Description>
- Function to connect to the shadow database, returning a new database handle.
- This routine C<die>s if no shadow database is configured.
- =item B<Params> (none)
- =item B<Returns>
- A new instance of the DB class
- =back
- =item C<bz_check_requirements>
- =over
- =item B<Description>
- Checks to make sure that you have the correct DBD and database version
- installed for the database that Bugzilla will be using. Prints a message
- and exits if you don't pass the requirements.
- If C<$db_check> is false (from F<localconfig>), we won't check the
- database version.
- =item B<Params>
- =over
- =item C<$output> - C<true> if the function should display informational
- output about what it's doing, such as versions found.
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_create_database>
- =over
- =item B<Description>
- Creates an empty database with the name C<$db_name>, if that database
- doesn't already exist. Prints an error message and exits if we can't
- create the database.
- =item B<Params> (none)
- =item B<Returns> (nothing)
- =back
- =item C<_connect>
- =over
- =item B<Description>
- Internal function, creates and returns a new, connected instance of the
- correct DB class. This routine C<die>s if no driver is specified.
- =item B<Params>
- =over
- =item C<$driver> - name of the database driver to use
- =item C<$host> - host running the database we are connecting to
- =item C<$dbname> - name of the database to connect to
- =item C<$port> - port the database is listening on
- =item C<$sock> - socket the database is listening on
- =item C<$user> - username used to log in to the database
- =item C<$pass> - password used to log in to the database
- =back
- =item B<Returns>
- A new instance of the DB class
- =back
- =item C<_handle_error>
- Function passed to the DBI::connect call for error handling. It shortens the
- error for printing.
- =item C<import>
- Overrides the standard import method to check that derived class
- implements all required abstract methods. Also calls original implementation
- in its super class.
- =back
- =head1 ABSTRACT METHODS
- Note: Methods which can be implemented generically for all DBs are implemented in
- this module. If needed, they can be overridden with DB specific code.
- Methods which do not have standard implementation are abstract and must
- be implemented for all supported databases separately.
- To avoid confusion with standard DBI methods, all methods returning string with
- formatted SQL command have prefix C<sql_>. All other methods have prefix C<bz_>.
- =head2 Constructor
- =over
- =item C<new>
- =over
- =item B<Description>
- Constructor. Abstract method, should be overridden by database specific
- code.
- =item B<Params>
- =over
- =item C<$user> - username used to log in to the database
- =item C<$pass> - password used to log in to the database
- =item C<$host> - host running the database we are connecting to
- =item C<$dbname> - name of the database to connect to
- =item C<$port> - port the database is listening on
- =item C<$sock> - socket the database is listening on
- =back
- =item B<Returns>
- A new instance of the DB class
- =item B<Note>
- The constructor should create a DSN from the parameters provided and
- then call C<db_new()> method of its super class to create a new
- class instance. See L<db_new> description in this module. As per
- DBI documentation, all class variables must be prefixed with
- "private_". See L<DBI>.
- =back
- =back
- =head2 SQL Generation
- =over
- =item C<sql_regexp>
- =over
- =item B<Description>
- Outputs SQL regular expression operator for POSIX regex
- searches (case insensitive) in format suitable for a given
- database.
- Abstract method, should be overridden by database specific code.
- =item B<Params>
- =over
- =item C<$expr> - SQL expression for the text to be searched (scalar)
- =item C<$pattern> - the regular expression to search for (scalar)
- =back
- =item B<Returns>
- Formatted SQL for regular expression search (e.g. REGEXP) (scalar)
- =back
- =item C<sql_not_regexp>
- =over
- =item B<Description>
- Outputs SQL regular expression operator for negative POSIX
- regex searches (case insensitive) in format suitable for a given
- database.
- Abstract method, should be overridden by database specific code.
- =item B<Params>
- =over
- =item C<$expr> - SQL expression for the text to be searched (scalar)
- =item C<$pattern> - the regular expression to search for (scalar)
- =back
- =item B<Returns>
- Formatted SQL for negative regular expression search (e.g. NOT REGEXP)
- (scalar)
- =back
- =item C<sql_limit>
- =over
- =item B<Description>
- Returns SQL syntax for limiting results to some number of rows
- with optional offset if not starting from the begining.
- Abstract method, should be overridden by database specific code.
- =item B<Params>
- =over
- =item C<$limit> - number of rows to return from query (scalar)
- =item C<$offset> - number of rows to skip before counting (scalar)
- =back
- =item B<Returns>
- Formatted SQL for limiting number of rows returned from query
- with optional offset (e.g. LIMIT 1, 1) (scalar)
- =back
- =item C<sql_from_days>
- =over
- =item B<Description>
- Outputs SQL syntax for converting Julian days to date.
- Abstract method, should be overridden by database specific code.
- =item B<Params>
- =over
- =item C<$days> - days to convert to date
- =back
- =item B<Returns>
- Formatted SQL for returning Julian days in dates. (scalar)
- =back
- =item C<sql_to_days>
- =over
- =item B<Description>
- Outputs SQL syntax for converting date to Julian days.
- Abstract method, should be overridden by database specific code.
- =item B<Params>
- =over
- =item C<$date> - date to convert to days
- =back
- =item B<Returns>
- Formatted SQL for returning date fields in Julian days. (scalar)
- =back
- =item C<sql_date_format>
- =over
- =item B<Description>
- Outputs SQL syntax for formatting dates.
- Abstract method, should be overridden by database specific code.
- =item B<Params>
- =over
- =item C<$date> - date or name of date type column (scalar)
- =item C<$format> - format string for date output (scalar)
- (C<%Y> = year, four digits, C<%y> = year, two digits, C<%m> = month,
- C<%d> = day, C<%a> = weekday name, 3 letters, C<%H> = hour 00-23,
- C<%i> = minute, C<%s> = second)
- =back
- =item B<Returns>
- Formatted SQL for date formatting (scalar)
- =back
- =item C<sql_interval>
- =over
- =item B<Description>
- Outputs proper SQL syntax for a time interval function.
- Abstract method, should be overridden by database specific code.
- =item B<Params>
- =over
- =item C<$interval> - the time interval requested (e.g. '30') (integer)
- =item C<$units> - the units the interval is in (e.g. 'MINUTE') (string)
- =back
- =item B<Returns>
- Formatted SQL for interval function (scalar)
- =back
- =item C<sql_position>
- =over
- =item B<Description>
- Outputs proper SQL syntax determining position of a substring
- (fragment) withing a string (text). Note: if the substring or
- text are string constants, they must be properly quoted (e.g. "'pattern'").
- It searches for the string in a case-sensitive manner. If you want to do
- a case-insensitive search, use L</sql_iposition>.
- =item B<Params>
- =over
- =item C<$fragment> - the string fragment we are searching for (scalar)
- =item C<$text> - the text to search (scalar)
- =back
- =item B<Returns>
- Formatted SQL for substring search (scalar)
- =back
- =item C<sql_iposition>
- Just like L</sql_position>, but case-insensitive.
- =item C<sql_group_by>
- =over
- =item B<Description>
- Outputs proper SQL syntax for grouping the result of a query.
- For ANSI SQL databases, we need to group by all columns we are
- querying for (except for columns used in aggregate functions).
- Some databases require (or even allow) to specify only one
- or few columns if the result is uniquely defined. For those
- databases, the default implementation needs to be overloaded.
- =item B<Params>
- =over
- =item C<$needed_columns> - string with comma separated list of columns
- we need to group by to get expected result (scalar)
- =item C<$optional_columns> - string with comma separated list of all
- other columns we are querying for, but which are not in the required list.
- =back
- =item B<Returns>
- Formatted SQL for row grouping (scalar)
- =back
- =item C<sql_string_concat>
- =over
- =item B<Description>
- Returns SQL syntax for concatenating multiple strings (constants
- or values from table columns) together.
- =item B<Params>
- =over
- =item C<@params> - array of column names or strings to concatenate
- =back
- =item B<Returns>
- Formatted SQL for concatenating specified strings
- =back
- =item C<sql_fulltext_search>
- =over
- =item B<Description>
- Returns SQL syntax for performing a full text search for specified text
- on a given column.
- There is a ANSI SQL version of this method implemented using LIKE operator,
- but it's not a real full text search. DB specific modules should override
- this, as this generic implementation will be always much slower. This
- generic implementation returns 'relevance' as 0 for no match, or 1 for a
- match.
- =item B<Params>
- =over
- =item C<$column> - name of column to search (scalar)
- =item C<$text> - text to search for (scalar)
- =back
- =item B<Returns>
- Formatted SQL for full text search
- =back
- =item C<sql_istrcmp>
- =over
- =item B<Description>
- Returns SQL for a case-insensitive string comparison.
- =item B<Params>
- =over
- =item C<$left> - What should be on the left-hand-side of the operation.
- =item C<$right> - What should be on the right-hand-side of the operation.
- =item C<$op> (optional) - What the operation is. Should be a valid ANSI
- SQL comparison operator, such as C<=>, C<E<lt>>, C<LIKE>, etc. Defaults
- to C<=> if not specified.
- =back
- =item B<Returns>
- A SQL statement that will run the comparison in a case-insensitive fashion.
- =item B<Note>
- Uses L</sql_istring>, so it has the same performance concerns.
- Try to avoid using this function unless absolutely necessary.
- Subclass Implementors: Override sql_istring instead of this
- function, most of the time (this function uses sql_istring).
- =back
- =item C<sql_istring>
- =over
- =item B<Description>
- Returns SQL syntax "preparing" a string or text column for case-insensitive
- comparison.
- =item B<Params>
- =over
- =item C<$string> - string to convert (scalar)
- =back
- =item B<Returns>
- Formatted SQL making the string case insensitive.
- =item B<Note>
- The default implementation simply calls LOWER on the parameter.
- If this is used to search on a text column with index, the index
- will not be usually used unless it was created as LOWER(column).
- =back
- =item C<sql_in>
- =over
- =item B<Description>
- Returns SQL syntax for the C<IN ()> operator.
- Only necessary where an C<IN> clause can have more than 1000 items.
- =item B<Params>
- =over
- =item C<$column_name> - Column name (e.g. C<bug_id>)
- =item C<$in_list_ref> - an arrayref containing values for C<IN ()>
- =back
- =item B<Returns>
- Formatted SQL for the C<IN> operator.
- =back
- =back
- =head1 IMPLEMENTED METHODS
- These methods are implemented in Bugzilla::DB, and only need
- to be implemented in subclasses if you need to override them for
- database-compatibility reasons.
- =head2 General Information Methods
- These methods return information about data in the database.
- =over
- =item C<bz_last_key>
- =over
- =item B<Description>
- Returns the last serial number, usually from a previous INSERT.
- Must be executed directly following the relevant INSERT.
- This base implementation uses L<DBI/last_insert_id>. If the
- DBD supports it, it is the preffered way to obtain the last
- serial index. If it is not supported, the DB-specific code
- needs to override this function.
- =item B<Params>
- =over
- =item C<$table> - name of table containing serial column (scalar)
- =item C<$column> - name of column containing serial data type (scalar)
- =back
- =item B<Returns>
- Last inserted ID (scalar)
- =back
- =back
- =head2 Database Setup Methods
- These methods are used by the Bugzilla installation programs to set up
- the database.
- =over
- =item C<bz_populate_enum_tables>
- =over
- =item B<Description>
- For an upgrade or an initial installation, populates the tables that hold
- the legal values for the old "enum" fields: C<bug_severity>,
- C<resolution>, etc. Prints out information if it inserts anything into the
- DB.
- =item B<Params> (none)
- =item B<Returns> (nothing)
- =back
- =back
- =head2 Schema Modification Methods
- These methods modify the current Bugzilla Schema.
- Where a parameter says "Abstract index/column definition", it returns/takes
- information in the formats defined for indexes and columns in
- C<Bugzilla::DB::Schema::ABSTRACT_SCHEMA>.
- =over
- =item C<bz_add_column>
- =over
- =item B<Description>
- Adds a new column to a table in the database. Prints out a brief statement
- that it did so, to stdout. Note that you cannot add a NOT NULL column that
- has no default -- the database won't know what to set all the NULL
- values to.
- =item B<Params>
- =over
- =item C<$table> - the table where the column is being added
- =item C<$name> - the name of the new column
- =item C<\%definition> - Abstract column definition for the new column
- =item C<$init_value> (optional) - An initial value to set the column
- to. Required if your column is NOT NULL and has no DEFAULT set.
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_add_index>
- =over
- =item B<Description>
- Adds a new index to a table in the database. Prints out a brief statement
- that it did so, to stdout. If the index already exists, we will do nothing.
- =item B<Params>
- =over
- =item C<$table> - The table the new index is on.
- =item C<$name> - A name for the new index.
- =item C<$definition> - An abstract index definition. Either a hashref
- or an arrayref.
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_add_table>
- =over
- =item B<Description>
- Creates a new table in the database, based on the definition for that
- table in the abstract schema.
- Note that unlike the other 'add' functions, this does not take a
- definition, but always creates the table as it exists in
- L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>.
- If a table with that name already exists, then this function returns
- silently.
- =item B<Params>
- =over
- =item C<$name> - The name of the table you want to create.
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_drop_index>
- =over
- =item B<Description>
- Removes an index from the database. Prints out a brief statement that it
- did so, to stdout. If the index doesn't exist, we do nothing.
- =item B<Params>
- =over
- =item C<$table> - The table that the index is on.
- =item C<$name> - The name of the index that you want to drop.
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_drop_table>
- =over
- =item B<Description>
- Drops a table from the database. If the table doesn't exist, we just
- return silently.
- =item B<Params>
- =over
- =item C<$name> - The name of the table to drop.
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_alter_column>
- =over
- =item B<Description>
- Changes the data type of a column in a table. Prints out the changes
- being made to stdout. If the new type is the same as the old type,
- the function returns without changing anything.
- =item B<Params>
- =over
- =item C<$table> - the table where the column is
- =item C<$name> - the name of the column you want to change
- =item C<\%new_def> - An abstract column definition for the new
- data type of the columm
- =item C<$set_nulls_to> (Optional) - If you are changing the column
- to be NOT NULL, you probably also want to set any existing NULL columns
- to a particular value. Specify that value here. B<NOTE>: The value should
- not already be SQL-quoted.
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_drop_column>
- =over
- =item B<Description>
- Removes a column from a database table. If the column doesn't exist, we
- return without doing anything. If we do anything, we print a short
- message to C<stdout> about the change.
- =item B<Params>
- =over
- =item C<$table> - The table where the column is
- =item C<$column> - The name of the column you want to drop
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_rename_column>
- =over
- =item B<Description>
- Renames a column in a database table. If the C<$old_name> column
- doesn't exist, we return without doing anything. If C<$old_name>
- and C<$new_name> both already exist in the table specified, we fail.
- =item B<Params>
- =over
- =item C<$table> - The name of the table containing the column
- that you want to rename
- =item C<$old_name> - The current name of the column that you want to rename
- =item C<$new_name> - The new name of the column
- =back
- =item B<Returns> (nothing)
- =back
- =item C<bz_rename_table>
- =over
- =item B<Description>
- Renames a table in the database. Does nothing if the table doesn't exist.
- Throws an error if the old table exists and there is already a table
- with the new name.
- =item B<Params>
- =over
- =item C<$old_name> - The current name of the table.
- =item C<$new_name> - What you're renaming the table to.
- =back
- =item B<Returns> (nothing)
- =back
- =back
- =head2 Schema Information Methods
- These methods return information about the current Bugzilla database
- schema, as it currently exists on the disk.
- Where a parameter says "Abstract index/column definition", it returns/takes
- information in the formats defined for indexes and columns for
- L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>.
- =over
- =item C<bz_column_info>
- =over
- =item B<Description>
- Get abstract column definition.
- =item B<Params>
- =over
- =item C<$table> - The name of the table the column is in.
- =item C<$column> - The name of the column.
- =back
- =item B<Returns>
- An abstract column definition for that column. If the table or column
- does not exist, we return C<undef>.
- =back
- =item C<bz_index_info>
- =over
- =item B<Description>
- Get abstract index definition.
- =item B<Params>
- =over
- =item C<$table> - The table the index is on.
- =item C<$index> - The name of the index.
- =back
- =item B<Returns>
- An abstract index definition for that index, always in hashref format.
- The hashref will always contain the C<TYPE> element, but it will
- be an empty string if it's just a normal index.
- If the index does not exist, we return C<undef>.
- =back
- =back
- =head2 Transaction Methods
- These methods deal with the starting and stopping of transactions
- in the database.
- =over
- =item C<bz_in_transaction>
- Returns C<1> if we are currently in the middle of an uncommitted transaction,
- C<0> otherwise.
- =item C<bz_start_transaction>
- Starts a transaction.
- It is OK to call C<bz_start_transaction> when you are already inside of
- a transaction. However, you must call L</bz_commit_transaction> as many
- times as you called C<bz_start_transaction>, in order for your transaction
- to actually commit.
- Bugzilla uses C<REPEATABLE READ> transactions.
- Returns nothing and takes no parameters.
- =item C<bz_commit_transaction>
- Ends a transaction, commiting all changes. Returns nothing and takes
- no parameters.
- =item C<bz_rollback_transaction>
- Ends a transaction, rolling back all changes. Returns nothing and takes
- no parameters.
- =back
- =head1 SUBCLASS HELPERS
- Methods in this class are intended to be used by subclasses to help them
- with their functions.
- =over
- =item C<db_new>
- =over
- =item B<Description>
- Constructor
- =item B<Params>
- =over
- =item C<$dsn> - database connection string
- =item C<$user> - username used to log in to the database
- =item C<$pass> - password used to log in to the database
- =item C<\%override_attrs> - set of attributes for DB connection (optional).
- You only have to set attributes that you want to be different from
- the default attributes set inside of C<db_new>.
- =back
- =item B<Returns>
- A new instance of the DB class
- =item B<Note>
- The name of this constructor is not C<new>, as that would make
- our check for implementation of C<new> by derived class useless.
- =back
- =back
- =head1 SEE ALSO
- L<DBI>
- L<Bugzilla::Constants/DB_MODULE>
|