use DBI;
use DBIx::Connector;
-has 'connector' => (is => 'lazy', handles => [qw( dbh )],);
-
use Bugzilla::Logging;
use Bugzilla::Constants;
use Bugzilla::Install::Requirements;
use English qw(-no_match_vars);
use Module::Runtime qw(require_module);
-has [qw(dsn user pass attrs)] => (is => 'ro', required => 1,);
+has 'connector' => (is => 'lazy', handles => [qw( dbh )]);
+has 'model' => (is => 'lazy');
+has [qw(dsn user pass attrs)] => (is => 'ro', required => 1);
+
+around 'attrs' => sub {
+ my ($method, $self) = @_;
+ my $attrs = dclone($self->$method);
+ my $class = ref $self;
+ # This is only used by the DBIx::Class code DBIx::Connector does something
+ # different because the old bugzilla code has its own ideas about
+ # transactions.
+ $attrs->{Callbacks}{connected} = sub {
+ my ($dbh, $dsn) = @_;
+ $class->on_dbi_connected(@_) if $class->can('on_dbi_connected');
+ return;
+ };
+
+ return $attrs;
+};
# Install proxy methods to the DBI object.
# We can't use handles() as DBIx::Connector->dbh has to be called each
my $sql_vers = $self->bz_server_version;
- my $sql_want = $db->{db_version};
+ my $sql_want = $db->{db_version};
my $version_ok = vers_cmp($sql_vers, $sql_want) > -1 ? 1 : 0;
my $sql_server = $db->{name};
# See if we can connect to the actual Bugzilla database.
my $conn_success = eval { $dbh = connect_main() };
- my $db_name = Bugzilla->localconfig->{db_name};
+ my $db_name = Bugzilla->localconfig->{db_name};
if (!$conn_success) {
$dbh = _get_no_db_connection();
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;
+ my $module_name = pop @module_parts;
$self->{private_bz_schema} = Bugzilla::DB::Schema->new($module_name);
return $self->{private_bz_schema};
}
# Subclass Helpers
#####################################################################
+sub _build_model {
+ my ($self) = @_;
+ require Bugzilla::Model;
+ Bugzilla::Model->connect($self->dsn, $self->user, $self->pass, $self->attrs);
+}
+
sub _build_connector {
my ($self) = @_;
my ($dsn, $user, $pass, $override_attrs)
}
}
my $class = ref $self;
+
+# This is different from the around 'attrs' above because we need a reference to $self.
+# If we ever kill $dbh->bz_start_transaction() this hack can go away.
weaken($self);
$attributes->{Callbacks} = {
connected => sub {
my $insert
= $self->prepare("INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
my $sortorder = 0;
- my $maxlen = max(map(length($_), @$valuelist)) + 2;
+ my $maxlen = max(map(length($_), @$valuelist)) + 2;
foreach my $value (@$valuelist) {
$sortorder += 100;
$insert->execute($value, $sortorder);
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model;
+use Mojo::Base 'DBIx::Class::Schema';
+
+__PACKAGE__->load_namespaces(default_resultset_class => 'ResultSet');
+__PACKAGE__->load_components('Helper::Schema::QuoteNames');
+
+1;
+
+=head1 NAME
+
+Bugzilla::Model - a DBIx::Class::Schema for Bugzilla
+
+=head1 SYNOPSIS
+
+ my $model = Bugzilla->dbh->model;
+ my $firefox_rs = $model->resultset('Bug')->search({'product.name' => 'Firefox'},
+ {join => ['product', {bug_keywords => 'keyword'}]});
+ my @report = $firefox_rs->group_by('bug_id')->columns({
+ bug_id => 'bug.bug_id',
+ summary => 'bug.short_desc',
+ product => 'product.name',
+ keywords => {group_concat => 'keyword.name'}
+ })->hri->all;
+ is(
+ \@report,
+ [
+ {
+ bug_id => 1,
+ keywords => 'regression,relnote',
+ product => 'Firefox',
+ summary => 'Some bug'
+ },
+ {
+ bug_id => 2,
+ keywords => undef,
+ product => 'Firefox',
+ summary => 'some other bug'
+ }
+ ]
+ );
+
+=head1 SEE ALSO
+
+See L<DBIx::Class> and L<DBIx::Class::Helper::ResultSet::Shortcut> for more examples of usage.
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::Bug;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::Bug->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::Bug->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::Bug->ID_FIELD);
+
+__PACKAGE__->has_one(
+ reporter => 'Bugzilla::Model::Result::User',
+ {'foreign.userid' => 'self.reporter'}
+);
+
+__PACKAGE__->has_one(
+ assigned_to => 'Bugzilla::Model::Result::User',
+ {'foreign.userid' => 'self.assigned_to'}
+);
+__PACKAGE__->might_have(
+ qa_contact => 'Bugzilla::Model::Result::User',
+ {'foreign.userid' => 'self.qa_contact'}
+);
+
+__PACKAGE__->has_many(
+ bug_keywords => 'Bugzilla::Model::Result::BugKeyword',
+ 'bug_id'
+);
+
+__PACKAGE__->many_to_many(keywords => 'bug_keywords', 'keyword');
+
+__PACKAGE__->has_many(flags => 'Bugzilla::Model::Result::Flag', 'bug_id');
+
+__PACKAGE__->has_many(
+ bug_groups => 'Bugzilla::Model::Result::BugGroup',
+ 'bug_id'
+);
+__PACKAGE__->many_to_many(groups => 'bug_groups', 'group');
+
+__PACKAGE__->has_one(
+ product => 'Bugzilla::Model::Result::Product',
+ {'foreign.id' => 'self.product_id'}
+);
+
+__PACKAGE__->has_one(
+ component => 'Bugzilla::Model::Result::Component',
+ {'foreign.id' => 'self.component_id'}
+);
+
+
+1;
+
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::BugGroup;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table('bug_group_map');
+__PACKAGE__->add_columns('bug_id', 'group_id');
+__PACKAGE__->set_primary_key('bug_id', 'group_id');
+
+__PACKAGE__->belongs_to(bug => 'Bugzilla::Model::Result::Bug', 'bug_id');
+__PACKAGE__->belongs_to(group => 'Bugzilla::Model::Result::Group', 'group_id');
+
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::BugKeyword;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table('keywords');
+__PACKAGE__->add_columns('bug_id', 'keywordid');
+__PACKAGE__->set_primary_key('bug_id', 'keywordid');
+
+__PACKAGE__->belongs_to(bug => 'Bugzilla::Model::Result::Bug', 'bug_id');
+__PACKAGE__->belongs_to(keyword => 'Bugzilla::Model::Result::Keyword', 'keywordid');
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::Component;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::Component->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::Component->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::Component->ID_FIELD);
+
+__PACKAGE__->belongs_to(product => 'Bugzilla::Model::Result::Product', 'product_id');
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::Flag;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::Flag->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::Flag->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::Flag->ID_FIELD);
+
+__PACKAGE__->belongs_to(bug => 'Bugzilla::Model::Result::Bug', 'bug_id');
+__PACKAGE__->belongs_to(type => 'Bugzilla::Model::Result::FlagType', 'type_id');
+__PACKAGE__->has_one(
+ setter => 'Bugzilla::Model::Result::User',
+ {'foreign.userid' => 'self.setter_id'}
+);
+
+__PACKAGE__->might_have(
+ requestee => 'Bugzilla::Model::Result::User',
+ {'foreign.userid' => 'self.requestee_id'}
+);
+
+
+1;
--- /dev/null
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::FlagType;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::FlagType->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::FlagType->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::FlagType->ID_FIELD);
+
+__PACKAGE__->has_many(flags => 'Bugzilla::Model::Result::Flag', 'type_id');
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::Group;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::Group->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::Group->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::Group->ID_FIELD);
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::Keyword;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::Keyword->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::Keyword->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::Keyword->ID_FIELD);
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::Product;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::Product->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::Product->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::Product->ID_FIELD);
+
+__PACKAGE__->has_many('components', 'Bugzilla::Model::Result::Component', 'product_id');
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::Result::User;
+use Mojo::Base 'DBIx::Class::Core';
+
+__PACKAGE__->table(Bugzilla::User->DB_TABLE);
+__PACKAGE__->add_columns(Bugzilla::User->DB_COLUMN_NAMES);
+__PACKAGE__->set_primary_key(Bugzilla::User->ID_FIELD);
+
+sub name {
+ my ($self) = @_;
+ $self->realname;
+}
+
+sub login {
+ my ($self) = @_;
+ $self->login_name;
+}
+
+1;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Model::ResultSet;
+use Mojo::Base 'DBIx::Class::ResultSet';
+
+__PACKAGE__->load_components('Helper::ResultSet');
+
+1;
# hooks to only run once per request instead of multiple times on each
# page.
+sub DB_COLUMN_NAMES {
+ my ($class) = @_;
+ my $table = $class->DB_TABLE;
+ my $_error = sub { die "$class: cannot determine attribute name from $_[0]\n" };
+
+ my $column_re = qr{
+ ^(?:\Q$table.\E)?(?<name>\w+)$
+ | ^(?:\Q$table.\E)?(?<name>\w+)\s+AS\s+\w+$
+ | (?<name>\w+)\b.+AS\s+\g{name}$
+ }six;
+
+ return map { trim($_) =~ $column_re ? $+{name} : $_error->($_) }
+ $class->_get_db_columns;
+}
+
+
sub _get_db_columns {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
--- /dev/null
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+use 5.10.1;
+use strict;
+use warnings;
+use lib qw( . lib local/lib/perl5 );
+
+BEGIN {
+ unlink('data/db/model_test') if -f 'data/db/model_test';
+ $ENV{test_db_name} = 'model_test';
+}
+
+use Bugzilla::Test::MockDB;
+use Bugzilla::Test::MockParams (password_complexity => 'no_constraints');
+use Bugzilla::Test::Util qw(create_bug create_user);
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Hook;
+BEGIN { Bugzilla->extensions }
+use Test2::V0;
+use Test2::Tools::Mock qw(mock mock_accessor);
+use Test2::Tools::Exception qw(dies lives);
+use PerlX::Maybe qw(provided);
+
+Bugzilla->dbh->model->resultset('Keyword')
+ ->create({name => 'regression', description => 'the regression keyword'});
+
+my $user = create_user('reportuser@invalid.tld', '*');
+$user->{groups} = [Bugzilla::Group->get_all];
+Bugzilla->set_user($user);
+
+create_bug(
+ short_desc => "test bug $_",
+ comment => "Hello, world: $_",
+ provided $_ % 3 == 0, keywords => ['regression'],
+ assigned_to => 'reportuser@invalid.tld'
+) for (1..10);
+
+my $model = Bugzilla->dbh->model;
+my $bug3 = $model->resultset('Bug')->find(3);
+isa_ok($bug3, 'Bugzilla::Model::Result::Bug');
+
+is([map { $_->name } $bug3->keywords->all], ['regression']);
+
+is($bug3->reporter->login_name, Bugzilla::Bug->new($bug3->id)->reporter->login);
+
+
+done_testing;
+