From 62b2ec349b01a454420eaadd22caad929521a76e Mon Sep 17 00:00:00 2001 From: "mkanat%kerio.com" <> Date: Thu, 17 Feb 2005 02:11:06 +0000 Subject: Bug 237862: New database layer for cross-database compatibility Patch By Tomas Kopal r=mkanat, a=myk --- Bugzilla/Config.pm | 2 +- Bugzilla/DB.pm | 408 +++++++++++++++++++++++++++++++++++++++++++-------- Bugzilla/DB/Mysql.pm | 162 ++++++++++++++++++++ Bugzilla/DB/Pg.pm | 180 +++++++++++++++++++++++ 4 files changed, 692 insertions(+), 60 deletions(-) create mode 100644 Bugzilla/DB/Mysql.pm create mode 100644 Bugzilla/DB/Pg.pm (limited to 'Bugzilla') diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm index eae2e75d0..5c070e372 100644 --- a/Bugzilla/Config.pm +++ b/Bugzilla/Config.pm @@ -71,7 +71,7 @@ our $webdotdir = "$datadir/webdot"; %Bugzilla::Config::EXPORT_TAGS = ( admin => [qw(GetParamList UpdateParams SetParam WriteParams)], - db => [qw($db_host $db_port $db_name $db_user $db_pass $db_sock)], + db => [qw($db_driver $db_host $db_port $db_name $db_user $db_pass $db_sock)], locations => [qw($libpath $localconfig $datadir $templatedir $webdotdir)], ); Exporter::export_ok_tags('admin', 'db', 'locations'); diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm index c76682e23..bfb2f77f3 100644 --- a/Bugzilla/DB.pm +++ b/Bugzilla/DB.pm @@ -22,6 +22,7 @@ # Jacob Steenhagen # Bradley Baetz # Christopher Aillon +# Tomas Kopal package Bugzilla::DB; @@ -29,7 +30,11 @@ use strict; use DBI; -use base qw(Exporter); +# Inherit the DB class from DBI::db and Exporter +# Note that we inherit from Exporter just to allow the old, deprecated +# interface to work. If it gets removed, the Exporter class can be removed +# from this list. +use base qw(Exporter DBI::db); %Bugzilla::DB::EXPORT_TAGS = ( @@ -42,6 +47,7 @@ Exporter::export_ok_tags('deprecated'); use Bugzilla::Config qw(:DEFAULT :db); use Bugzilla::Util; +use Bugzilla::Error; # All this code is backwards compat fu. As such, its a bit ugly. Note the # circular dependencies on Bugzilla.pm @@ -122,43 +128,29 @@ sub PopGlobalSQLState() { sub connect_shadow { die "Tried to connect to non-existent shadowdb" unless Param('shadowdb'); - my $dsn = "DBI:mysql:host=" . Param("shadowdbhost") . - ";database=" . Param('shadowdb') . ";port=" . Param("shadowdbport"); - - $dsn .= ";mysql_socket=" . Param("shadowdbsock") if Param('shadowdbsock'); - - return _connect($dsn); + return _connect($db_driver, Param("shadowdbhost"), + Param('shadowdb'), Param("shadowdbport"), + Param("shadowdbsock"), $db_user, $db_pass); } sub connect_main { - my $dsn = "DBI:mysql:host=$db_host;database=$db_name;port=$db_port"; - - $dsn .= ";mysql_socket=$db_sock" if $db_sock; - - return _connect($dsn); + return _connect($db_driver, $db_host, $db_name, $db_port, + $db_sock, $db_user, $db_pass); } sub _connect { - my ($dsn) = @_; + my ($driver, $host, $dbname, $port, $sock, $user, $pass) = @_; - # connect using our known info to the specified db - # Apache::DBI will cache this when using mod_perl - my $dbh = DBI->connect($dsn, - '', - '', - { RaiseError => 1, - PrintError => 0, - Username => $db_user, - Password => $db_pass, - 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?) - }); + # DB specific module have the same name as DB driver, here we + # just make sure we are not case sensitive + (my $db_module = $driver) =~ s/(\w+)/\u\L$1/g; + my $pkg_module = "Bugzilla::DB::" . $db_module; + + # do the actual import + eval ("require $pkg_module") || die ($@); + + # instantiate the correct DB specific module + my $dbh = $pkg_module->new($user, $pass, $host, $dbname, $port, $sock); return $dbh; } @@ -173,6 +165,39 @@ sub _handle_error { return 0; # Now let DBI handle raising the error } +# List of abstract methods we are checking the derived class implements +our @_abstract_methods = qw(new sql_regexp sql_not_regexp sql_limit + sql_to_days sql_date_format sql_interval + bz_lock_tables bz_unlock_tables); + +# This overriden 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; +} + +# note that when multiple databases are supported, version number does not +# make sense anymore (as it is DB dependant). This needs to be removed in +# the future and places where it's used fixed. my $cached_server_version; sub server_version { return $cached_server_version if defined($cached_server_version); @@ -183,8 +208,8 @@ sub server_version { return $cached_server_version; } -sub GetFieldDefs { - my $dbh = Bugzilla->dbh; +sub bz_get_field_defs { + my ($self) = @_; my $extra = ""; if (!&::UserInGroup(Param('timetrackinggroup'))) { @@ -193,9 +218,9 @@ sub GetFieldDefs { } my @fields; - my $sth = $dbh->prepare("SELECT name, description - FROM fielddefs $extra - ORDER BY sortkey"); + my $sth = $self->prepare("SELECT name, description + FROM fielddefs $extra + ORDER BY sortkey"); $sth->execute(); while (my $field_ref = $sth->fetchrow_hashref()) { push(@fields, $field_ref); @@ -203,6 +228,82 @@ sub GetFieldDefs { return(@fields); } +sub bz_last_key { + my ($self, $table, $column) = @_; + + return $self->last_insert_id($db_name, undef, $table, $column); +} + +sub bz_start_transaction { + my ($self) = @_; + + if ($self->{private_bz_in_transaction}) { + carp("Can't start transaction within another transaction"); + ThrowCodeError("nested_transaction"); + } else { + # Turn AutoCommit off and start a new transaction + $self->begin_work(); + + $self->{privateprivate_bz_in_transaction} = 1; + } +} + +sub bz_commit_transaction { + my ($self) = @_; + + if (!$self->{private_bz_in_transaction}) { + carp("Can't commit without a transaction"); + ThrowCodeError("not_in_transaction"); + } else { + $self->commit(); + + $self->{private_bz_in_transaction} = 0; + } +} + +sub bz_rollback_transaction { + my ($self) = @_; + + if (!$self->{private_bz_in_transaction}) { + carp("Can't rollback without a transaction"); + ThrowCodeError("not_in_transaction"); + } else { + $self->rollback(); + + $self->{private_bz_in_transaction} = 0; + } +} + +sub db_new { + my ($class, $dsn, $user, $pass, $attributes) = @_; + + # set up default attributes used to connect to the database + # (if not defined by DB specific implementation) + $attributes = { RaiseError => 1, + 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 (!defined($attributes)); + + # connect using our known info to the specified db + # Apache::DBI will cache this when using mod_perl + my $self = DBI->connect($dsn, $user, $pass, $attributes); + + # class variables + $self->{private_bz_in_transaction} = 0; + + bless ($self, $class); + + return $self; +} + 1; __END__ @@ -213,60 +314,249 @@ Bugzilla::DB - Database access routines, using L =head1 SYNOPSIS - # Connection - my $dbh = Bugzilla::DB->connect_main; - my $shadow = Bugzilla::DB->connect_shadow; + # Obtain db handle + use Bugzilla::DB; + my $dbh = Bugzilla->dbh; - # Schema Information - my @fields = Bugzilla::DB::GetFieldDefs(); + # 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; - # Deprecated - SendSQL("SELECT COUNT(*) FROM bugs"); - my $cnt = FetchOneColumn(); + # Schema Information + my @fields = $dbh->bz_get_field_defs(); =head1 DESCRIPTION -This allows creation of a database handle to connect to the Bugzilla database. -This should never be done directly; all users should use the L module -to access the current C instead. +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 module to access the current C 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. Access to the old SendSQL-based database routines are also provided by importing the C<:deprecated> tag. These routines should not be used in new code. -The only functions that should be used by modern, regular Bugzilla code -are the "Schema Information" functions. - =head1 CONNECTION A new database handle to the required database can be created using this module. This is normally done by the L module, and so these routines should not be called from anywhere else. +=head2 Functions + =over 4 =item C -Connects to the main database, returning a new dbh. + Description: Function to connect to the main database, returning a new + database handle. + Params: none + Returns: new instance of the DB class =item C -Connects to the shadow database, returning a new dbh. This routine Cs if -no shadow database is configured. + Description: Function to connect to the shadow database, returning a new + database handle. + This routine Cs if no shadow database is configured. + Params: none + Returns: new instance of the DB class + +=item C<_connect> + + Description: Internal function, creates and returns a new, connected + instance of the correct DB class. + This routine Cs if no driver is specified. + Params: $driver = name of the database driver to use + $host = host running the database we are connecting to + $dbname = name of the database to connect to + $port = port the database is listening on + $sock = socket the database is listening on + $user = username used to log in to the database + $pass = password used to log in to the database + Returns: new instance of the DB class + +=item C<_handle_error> + + Description: Function passed to the DBI::connect call for error handling. + It shortens the error for printing. + +=item C + + Description: 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 SCHEMA INFORMATION +=head2 Methods -Bugzilla::DB also contains routines to get schema information about the -database. +Note: Methods which can be implemented generically for all DBs are implemented in +this module. If needed, they can be overriden 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. All other methods have prefix C. =over 4 -=item C +=item C + + Description: Constructor + Abstract method, should be overriden by database specific code. + Params: $user = username used to log in to the database + $pass = password used to log in to the database + $host = host running the database we are connecting to + $dbname = name of the database to connect to + $port = port the database is listening on + $sock = socket the database is listening on + Returns: new instance of the DB class + Note: The constructor should create a DSN from the parameters provided and + then call C method of its super class to create a new + class instance. See C description in this module. As per + DBI documentation, all class variables must be prefixed with + "private_". See L. + +=item C + + Description: Outputs SQL regular expression operator for POSIX regex + searches in format suitable for a given database. + Abstract method, should be overriden by database specific code. + Params: none + Returns: formatted SQL for regular expression search (e.g. REGEXP) + (scalar) + +=item C + + Description: Outputs SQL regular expression operator for negative POSIX + regex searches in format suitable for a given database. + Abstract method, should be overriden by database specific code. + Params: none + Returns: formatted SQL for negative regular expression search + (e.g. NOT REGEXP) (scalar) + +=item C + + 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 overriden by database specific code. + Params: $limit = number of rows to return from query (scalar) + $offset = number of rows to skip prior counting (scalar) + Returns: formatted SQL for limiting number of rows returned from query + with optional offset (e.g. LIMIT 1, 1) (scalar) + +=item C + + Description: Outputs SQL syntax for converting date to Julian days. + Abstract method, should be overriden by database specific code. + Params: $date = date to convert to days + Returns: formatted SQL for returning date fields in Julian days. (scalar) + +=item C + + Description: Outputs SQL syntax for formatting dates. + Abstract method, should be overriden by database specific code. + Params: $date = date or name of date type column (scalar) + $format = format string for date output (scalar) + (%Y = year, four digits, %y = year, two digits, %m = month, + %d = day, %a = weekday name, 3 letters, %H = hour 00-23, + %i = minute, %s = second) + Returns: formatted SQL for date formatting (scalar) + +=item C + + Description: Outputs proper SQL syntax for a time interval function. + Abstract method, should be overriden by database specific code. + Params: $interval = the time interval requested (e.g. '30 minutes') + (scalar) + Returns: formatted SQL for interval function (scalar) + +=item C + + Description: Performs a table lock operation on specified tables. + If the underlying database supports transactions, it should also + implicitly start a new transaction. + Abstract method, should be overriden by database specific code. + Params: @tables = list of names of tables to lock in MySQL + notation (ex. 'bugs AS bugs2 READ', 'logincookies WRITE') + Returns: none + +=item C + + Description: Performs a table unlock operation + If the underlying database supports transactions, it should also + implicitly commit or rollback the transaction. + Also, this function should allow to be called with the abort flag + set even without locking tables first without raising an error + to simplify error handling. + Abstract method, should be overriden by database specific code. + Params: $abort = true (1) if the operation on locked tables failed + (if transactions are supported, the action will be rolled + back). False (0) or no param if the operation succeeded. + Returns: none + +=item C + + Description: Returns the last serial number, usually from a previous INSERT. + Must be executed directly following the relevant INSERT. + This base implementation uses 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 it with DB specific code. + Params: $table = name of table containing serial column (scalar) + $column = name of column containing serial data type (scalar) + Returns: Last inserted ID (scalar) + +=item C + + Description: Returns a list of all the "bug" fields in Bugzilla. The list + contains hashes, with a 'name' key and a 'description' key. + Params: none + Returns: List of all the "bug" fields + +=item C + + Description: Starts a transaction if supported by the database being used + Params: none + Returns: none + +=item C + + Description: Ends a transaction, commiting all changes, if supported by + the database being used + Params: none + Returns: none + +=item C + + Description: Ends a transaction, rolling back all changes, if supported by + the database being used + Params: none + Returns: none + +=item C + + Description: Constructor + Params: $dsn = database connection string + $user = username used to log in to the database + $pass = password used to log in to the database + $attributes = set of attributes for DB connection (optional) + Returns: new instance of the DB class + Note: the name of this constructor is not new, as that would make + our check for implementation of new() by derived class useles. -Returns a list of all the "bug" fields in Bugzilla. The list contains -hashes, with a 'name' key and a 'description' key. +=back =head1 DEPRECATED ROUTINES diff --git a/Bugzilla/DB/Mysql.pm b/Bugzilla/DB/Mysql.pm new file mode 100644 index 000000000..d2204433b --- /dev/null +++ b/Bugzilla/DB/Mysql.pm @@ -0,0 +1,162 @@ +# -*- 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): Dave Miller +# Gayathri Swaminath +# Jeroen Ruigrok van der Werven +# Dave Lawrence +# Tomas Kopal + +=head1 NAME + +Bugzilla::DB::Mysql - Bugzilla database compatibility layer for MySQL + +=head1 DESCRIPTION + +This module overrides methods of the Bugzilla::DB module with MySQL specific +implementation. It is instantiated by the Bugzilla::DB module and should never +be used directly. + +For interface details see L and L. + +=cut + +package Bugzilla::DB::Mysql; + +use strict; + +use Bugzilla::Error; +use Carp; + +# This module extends the DB interface via inheritance +use base qw(Bugzilla::DB); + +sub new { + my ($class, $user, $pass, $host, $dbname, $port, $sock) = @_; + + # construct the DSN from the parameters we got + my $dsn = "DBI:mysql:host=$host;database=$dbname;port=$port"; + $dsn .= ";mysql_socket=$sock" if $sock; + + my $self = $class->db_new($dsn, $user, $pass); + + # all class local variables stored in DBI derived class needs to have + # a prefix 'private_'. See DBI documentation. + $self->{private_bz_tables_locked} = 0; + + bless ($self, $class); + + return $self; +} + +# 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 ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()'); + + return $last_insert_id; +} + +sub sql_regexp { + return "REGEXP"; +} + +sub sql_not_regexp { + return "NOT REGEXP"; +} + +sub sql_limit { + my ($self, $limit,$offset) = @_; + + if (defined($offset)) { + return "LIMIT $offset, $limit"; + } else { + return "LIMIT $limit"; + } +} + +sub sql_to_days { + my ($self, $date) = @_; + + return "TO_DAYS($date)"; +} + +sub sql_date_format { + my ($self, $date, $format) = @_; + + $format = "%Y.%m.%d %H:%i:%s" if !$format; + + return "DATE_FORMAT($date, " . $self->quote($format) . ")"; +} + +sub sql_interval { + my ($self, $interval) = @_; + + return "INTERVAL $interval"; +} + +sub bz_lock_tables { + my ($self, @tables) = @_; + + # Check first if there was no lock before + if ($self->{private_bz_tables_locked}) { + carp("Tables already locked"); + ThrowCodeError("already_locked"); + } else { + $self->do('LOCK TABLE ' . join(', ', @tables)); + + $self->{private_bz_tables_locked} = 1; + } +} + +sub bz_unlock_tables { + my ($self, $abort) = @_; + + # Check first if there was previous matching lock + if (!$self->{private_bz_tables_locked}) { + # Abort is allowed even without previous lock for error handling + return if $abort; + carp("No matching lock"); + ThrowCodeError("no_matching_lock"); + } else { + $self->do("UNLOCK TABLES"); + + $self->{private_bz_tables_locked} = 0; + } +} + +# As Bugzilla currently runs on MyISAM storage, which does not supprt +# transactions, these functions die when called. +# Maybe we should just ignore these calls for now, but as we are not +# using transactions in MySQL yet, this just hints the developers. +sub bz_start_transaction { + die("Attempt to start transaction on DB without transaction support"); +} + +sub bz_commit_transaction { + die("Attempt to commit transaction on DB without transaction support"); +} + +sub bz_rollback_transaction { + die("Attempt to rollback transaction on DB without transaction support"); +} + +1; diff --git a/Bugzilla/DB/Pg.pm b/Bugzilla/DB/Pg.pm new file mode 100644 index 000000000..a23c38666 --- /dev/null +++ b/Bugzilla/DB/Pg.pm @@ -0,0 +1,180 @@ +# -*- 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): Dave Miller +# Gayathri Swaminath +# Jeroen Ruigrok van der Werven +# Dave Lawrence +# Tomas Kopal + +=head1 NAME + +Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL + +=head1 DESCRIPTION + +This module overrides methods of the Bugzilla::DB module with PostgreSQL +specific implementation. It is instantiated by the Bugzilla::DB module +and should never be used directly. + +For interface details see L and L. + +=cut + +package Bugzilla::DB::Pg; + +use strict; + +use Bugzilla::Error; +use Carp; + +# This module extends the DB interface via inheritance +use base qw(Bugzilla::DB); + +sub new { + my ($class, $user, $pass, $host, $dbname, $port) = @_; + + # construct the DSN from the parameters we got + my $dsn = "DBI:Pg:host=$host;dbname=$dbname;port=$port"; + + my $self = $class->db_new($dsn, $user, $pass); + + # all class local variables stored in DBI derived class needs to have + # a prefix 'private_'. See DBI documentation. + $self->{private_bz_tables_locked} = 0; + + bless ($self, $class); + + return $self; +} + +# 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 $seq = $table . "_" . $column . "_seq"; + my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')"); + + return $last_insert_id; +} + +sub sql_regexp { + return "~"; +} + +sub sql_not_regexp { + return "!~" +} + +sub sql_limit { + my ($self, $limit,$offset) = @_; + + if (defined($offset)) { + return "LIMIT $limit OFFSET $offset"; + } else { + return "LIMIT $limit"; + } +} + +sub sql_to_days { + my ($self, $date) = @_; + + return "TO_CHAR($date, 'J')::int"; +} + +sub sql_date_format { + my ($self, $date, $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; + + return "TO_CHAR($date, " . $self->quote($format) . ")"; +} + +sub sql_interval { + my ($self, $interval) = @_; + + return "INTERVAL '$interval'"; +} + +sub bz_lock_tables { + my ($self, @tables) = @_; + + # Check first if there was no lock before + if ($self->{private_bz_tables_locked}) { + carp("Tables already locked"); + ThrowCodeError("already_locked"); + } else { + my %read_tables; + my %write_tables; + foreach my $table (@tables) { + $table =~ /^([\d\w]+)([\s]+AS[\s]+[\d\w]+)?[\s]+(WRITE|READ)$/i; + my $table_name = $1; + if ($3 =~ /READ/i) { + if (!exists $read_tables{$table_name}) { + $read_tables{$table_name} = undef; + } + } + else { + if (!exists $write_tables{$table_name}) { + $write_tables{$table_name} = undef; + } + } + } + + # Begin Transaction + $self->bz_start_transaction(); + + Bugzilla->dbh->do('LOCK TABLE ' . join(', ', keys %read_tables) . + ' IN ROW SHARE MODE') if keys %read_tables; + Bugzilla->dbh->do('LOCK TABLE ' . join(', ', keys %write_tables) . + ' IN ROW EXCLUSIVE MODE') if keys %write_tables; + } +} + +sub bz_unlock_tables { + my ($self, $abort) = @_; + + # Check first if there was previous matching lock + if (!$self->{private_bz_tables_locked}) { + # Abort is allowed even without previous lock for error handling + return if $abort; + + carp("No matching lock"); + ThrowCodeError("no_matching_lock"); + } else { + # End transaction, tables will be unlocked automatically + if ($abort) { + $self->bz_rollback_transaction(); + } else { + $self->bz_commit_transaction(); + } + } +} + +1; -- cgit v1.2.1