From: Dylan William Hardison Date: Fri, 8 Mar 2019 00:30:05 +0000 (-0500) Subject: Bug 1532409 - Introduce Bugzilla::Model (a DBIx::Class::Schema) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2ef96856aa26d581eb6fb22937c2991a81c9e63c;p=thirdparty%2Fbugzilla.git Bug 1532409 - Introduce Bugzilla::Model (a DBIx::Class::Schema) --- diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm index dc66b8bfb..4cba3af98 100644 --- a/Bugzilla/DB.pm +++ b/Bugzilla/DB.pm @@ -13,8 +13,6 @@ use Moo; use DBI; use DBIx::Connector; -has 'connector' => (is => 'lazy', handles => [qw( dbh )],); - use Bugzilla::Logging; use Bugzilla::Constants; use Bugzilla::Install::Requirements; @@ -33,8 +31,26 @@ use Storable qw(dclone); 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 @@ -239,7 +255,7 @@ sub bz_check_server_version { 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}; @@ -275,7 +291,7 @@ sub bz_create_database { # 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(); @@ -1152,7 +1168,7 @@ 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; + my $module_name = pop @module_parts; $self->{private_bz_schema} = Bugzilla::DB::Schema->new($module_name); return $self->{private_bz_schema}; } @@ -1316,6 +1332,12 @@ sub bz_rollback_transaction { # 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) @@ -1344,6 +1366,9 @@ sub _build_connector { } } 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 { @@ -1518,7 +1543,7 @@ sub _bz_populate_enum_table { 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); diff --git a/Bugzilla/Model.pm b/Bugzilla/Model.pm new file mode 100644 index 000000000..901ea5488 --- /dev/null +++ b/Bugzilla/Model.pm @@ -0,0 +1,51 @@ +# 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 and L for more examples of usage. diff --git a/Bugzilla/Model/Result/Bug.pm b/Bugzilla/Model/Result/Bug.pm new file mode 100644 index 000000000..e4e052e4b --- /dev/null +++ b/Bugzilla/Model/Result/Bug.pm @@ -0,0 +1,56 @@ +# 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; + diff --git a/Bugzilla/Model/Result/BugGroup.pm b/Bugzilla/Model/Result/BugGroup.pm new file mode 100644 index 000000000..7b7b044ae --- /dev/null +++ b/Bugzilla/Model/Result/BugGroup.pm @@ -0,0 +1,19 @@ +# 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; diff --git a/Bugzilla/Model/Result/BugKeyword.pm b/Bugzilla/Model/Result/BugKeyword.pm new file mode 100644 index 000000000..2243a9e66 --- /dev/null +++ b/Bugzilla/Model/Result/BugKeyword.pm @@ -0,0 +1,18 @@ +# 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; diff --git a/Bugzilla/Model/Result/Component.pm b/Bugzilla/Model/Result/Component.pm new file mode 100644 index 000000000..5c098773a --- /dev/null +++ b/Bugzilla/Model/Result/Component.pm @@ -0,0 +1,17 @@ +# 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; diff --git a/Bugzilla/Model/Result/Flag.pm b/Bugzilla/Model/Result/Flag.pm new file mode 100644 index 000000000..5bb36f08e --- /dev/null +++ b/Bugzilla/Model/Result/Flag.pm @@ -0,0 +1,28 @@ +# 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; diff --git a/Bugzilla/Model/Result/FlagType.pm b/Bugzilla/Model/Result/FlagType.pm new file mode 100644 index 000000000..8e6cdd455 --- /dev/null +++ b/Bugzilla/Model/Result/FlagType.pm @@ -0,0 +1,18 @@ + +# 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; diff --git a/Bugzilla/Model/Result/Group.pm b/Bugzilla/Model/Result/Group.pm new file mode 100644 index 000000000..39268cc19 --- /dev/null +++ b/Bugzilla/Model/Result/Group.pm @@ -0,0 +1,15 @@ +# 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; diff --git a/Bugzilla/Model/Result/Keyword.pm b/Bugzilla/Model/Result/Keyword.pm new file mode 100644 index 000000000..e38a97ed7 --- /dev/null +++ b/Bugzilla/Model/Result/Keyword.pm @@ -0,0 +1,15 @@ +# 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; diff --git a/Bugzilla/Model/Result/Product.pm b/Bugzilla/Model/Result/Product.pm new file mode 100644 index 000000000..2539c680d --- /dev/null +++ b/Bugzilla/Model/Result/Product.pm @@ -0,0 +1,17 @@ +# 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; diff --git a/Bugzilla/Model/Result/User.pm b/Bugzilla/Model/Result/User.pm new file mode 100644 index 000000000..d127c6bea --- /dev/null +++ b/Bugzilla/Model/Result/User.pm @@ -0,0 +1,25 @@ +# 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; diff --git a/Bugzilla/Model/ResultSet.pm b/Bugzilla/Model/ResultSet.pm new file mode 100644 index 000000000..2c842748c --- /dev/null +++ b/Bugzilla/Model/ResultSet.pm @@ -0,0 +1,13 @@ +# 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; diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm index bd108495a..f3059ad48 100644 --- a/Bugzilla/Object.pm +++ b/Bugzilla/Object.pm @@ -883,6 +883,22 @@ sub _insert_dep_field { # 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)?(?\w+)$ + | ^(?:\Q$table.\E)?(?\w+)\s+AS\s+\w+$ + | (?\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; diff --git a/t/model.t b/t/model.t new file mode 100644 index 000000000..551fb4c2a --- /dev/null +++ b/t/model.t @@ -0,0 +1,54 @@ +# 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; +