summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Config.pm2
-rw-r--r--Bugzilla/DB.pm408
-rw-r--r--Bugzilla/DB/Mysql.pm162
-rw-r--r--Bugzilla/DB/Pg.pm180
-rwxr-xr-xchecksetup.pl27
-rwxr-xr-xconfig.cgi2
-rwxr-xr-xquery.cgi2
7 files changed, 710 insertions, 73 deletions
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 <jake@bugzilla.org>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Christopher Aillon <christopher@aillon.com>
+# Tomas Kopal <Tomas.Kopal@altap.cz>
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<DBI>
=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<Bugzilla> module
-to access the current C<dbh> 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<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.
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<Bugzilla> module, and so these routines
should not be called from anywhere else.
+=head2 Functions
+
=over 4
=item C<connect_main>
-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<connect_shadow>
-Connects to the shadow database, returning a new dbh. This routine C<die>s if
-no shadow database is configured.
+ Description: Function to connect to the shadow database, returning a new
+ database handle.
+ This routine C<die>s 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 C<die>s 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<import>
+
+ 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<sql_>. All other methods have prefix C<bz_>.
=over 4
-=item C<GetFieldDefs>
+=item C<new>
+
+ 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<db_new()> method of its super class to create a new
+ class instance. See C<db_new> description in this module. As per
+ DBI documentation, all class variables must be prefixed with
+ "private_". See L<DBI>.
+
+=item C<sql_regexp>
+
+ 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<sql_not_regexp>
+
+ 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<sql_limit>
+
+ 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<sql_to_days>
+
+ 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<sql_date_format>
+
+ 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<sql_interval>
+
+ 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<bz_lock_tables>
+
+ 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<bz_unlock_tables>
+
+ 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<bz_last_key>
+
+ 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<bz_get_field_defs>
+
+ 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<bz_start_transaction>
+
+ Description: Starts a transaction if supported by the database being used
+ Params: none
+ Returns: none
+
+=item C<bz_commit_transaction>
+
+ Description: Ends a transaction, commiting all changes, if supported by
+ the database being used
+ Params: none
+ Returns: none
+
+=item C<bz_rollback_transaction>
+
+ Description: Ends a transaction, rolling back all changes, if supported by
+ the database being used
+ Params: none
+ Returns: none
+
+=item C<db_new>
+
+ 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 <davem00@aol.com>
+# Gayathri Swaminath <gayathrik00@aol.com>
+# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
+# Dave Lawrence <dkl@redhat.com>
+# Tomas Kopal <Tomas.Kopal@altap.cz>
+
+=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<Bugzilla::DB> and L<DBI>.
+
+=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 <davem00@aol.com>
+# Gayathri Swaminath <gayathrik00@aol.com>
+# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
+# Dave Lawrence <dkl@redhat.com>
+# Tomas Kopal <Tomas.Kopal@altap.cz>
+
+=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<Bugzilla::DB> and L<DBI>.
+
+=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;
diff --git a/checksetup.pl b/checksetup.pl
index 19b9e4fd5..953223b6c 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -101,6 +101,7 @@
# The format of that file is....
#
# $answer{'db_host'} = '$db_host = "localhost";
+# $db_driver = "mydbdriver";
# $db_port = 3306;
# $db_name = "mydbname";
# $db_user = "mydbuser";';
@@ -644,6 +645,14 @@ END
+LocalVar('db_driver', '
+#
+# What SQL database to use. Default is mysql. List of supported databases
+# can be obtained by listing Bugzilla/DB directory - every module corresponds
+# to one supported database and the name corresponds to a driver name.
+#
+$db_driver = "mysql";
+');
LocalVar('db_host', '
#
# How to access the SQL database:
@@ -793,6 +802,7 @@ if ($newstuff ne "") {
# Note that we won't need to do this in globals.pl because globals.pl couldn't
# care less whether they were defined ahead of time or not.
my $my_db_check = ${*{$main::{'db_check'}}{SCALAR}};
+my $my_db_driver = ${*{$main::{'db_driver'}}{SCALAR}};
my $my_db_host = ${*{$main::{'db_host'}}{SCALAR}};
my $my_db_port = ${*{$main::{'db_port'}}{SCALAR}};
my $my_db_name = ${*{$main::{'db_name'}}{SCALAR}};
@@ -1505,11 +1515,6 @@ $::ENV{'PATH'} = $origPath;
# Check if we have access to --MYSQL--
#
-# This settings are not yet changeable, because other code depends on
-# the fact that we use MySQL and not, say, PostgreSQL.
-
-my $db_base = 'mysql';
-
# No need to "use" this here. It should already be loaded from the
# version-checking routines above, and this file won't even compile if
# DBI isn't installed so the user gets nasty errors instead of our
@@ -1522,15 +1527,15 @@ if ($my_db_check) {
my $sql_want = "3.23.41"; # minimum version of MySQL
# original DSN line was:
-# my $dsn = "DBI:$db_base:$my_db_name;$my_db_host;$my_db_port";
+# my $dsn = "DBI:$my_db_driver:$my_db_name;$my_db_host;$my_db_port";
# removed the $db_name because we don't know it exists yet, and this will fail
# if we request it here and it doesn't. - justdave@syndicomm.com 2000/09/16
- my $dsn = "DBI:$db_base:;$my_db_host;$my_db_port";
+ my $dsn = "DBI:$my_db_driver:;$my_db_host;$my_db_port";
if ($my_db_sock ne "") {
$dsn .= ";mysql_socket=$my_db_sock";
}
my $dbh = DBI->connect($dsn, $my_db_user, $my_db_pass)
- or die "Can't connect to the $db_base database. Is the database " .
+ or die "Can't connect to the $my_db_driver database. Is the database " .
"installed and\nup and running? Do you have the correct username " .
"and password selected in\nlocalconfig?\n\n";
printf("Checking for %15s %-9s ", "MySQL Server", "(v$sql_want)") unless $silent;
@@ -1581,14 +1586,14 @@ EOF
}
# now get a handle to the database:
-my $connectstring = "dbi:$db_base:$my_db_name:host=$my_db_host:port=$my_db_port";
+my $connectstring = "dbi:$my_db_driver:$my_db_name:host=$my_db_host:port=$my_db_port";
if ($my_db_sock ne "") {
$connectstring .= ";mysql_socket=$my_db_sock";
}
my $dbh = DBI->connect($connectstring, $my_db_user, $my_db_pass)
or die "Can't connect to the table '$connectstring'.\n",
- "Have you read the Bugzilla Guide in the doc directory? Have you read the doc of '$db_base'?\n";
+ "Have you read the Bugzilla Guide in the doc directory? Have you read the doc of '$my_db_driver'?\n";
END { $dbh->disconnect if $dbh }
@@ -2191,7 +2196,7 @@ while (my ($tabname, $fielddef) = each %table) {
$fielddef =~ s/\$my_platforms/$my_platforms/;
$dbh->do("CREATE TABLE $tabname (\n$fielddef\n) TYPE = MYISAM")
- or die "Could not create table '$tabname'. Please check your '$db_base' access.\n";
+ or die "Could not create table '$tabname'. Please check your '$my_db_driver' access.\n";
}
###########################################################################
diff --git a/config.cgi b/config.cgi
index 2d0505db8..56751ec0e 100755
--- a/config.cgi
+++ b/config.cgi
@@ -83,7 +83,7 @@ $vars->{'open_status'} = \@open_status;
$vars->{'closed_status'} = \@closed_status;
# Generate a list of fields that can be queried.
-$vars->{'field'} = [Bugzilla::DB::GetFieldDefs()];
+$vars->{'field'} = [Bugzilla->dbh->bz_get_field_defs()];
# Determine how the user would like to receive the output;
# default is JavaScript.
diff --git a/query.cgi b/query.cgi
index cfe7702de..120dca05e 100755
--- a/query.cgi
+++ b/query.cgi
@@ -351,7 +351,7 @@ $vars->{'bug_severity'} = \@::legal_severity;
# Boolean charts
my @fields;
push(@fields, { name => "noop", description => "---" });
-push(@fields, Bugzilla::DB::GetFieldDefs());
+push(@fields, Bugzilla->dbh->bz_get_field_defs());
$vars->{'fields'} = \@fields;
# Creating new charts - if the cmd-add value is there, we define the field