PageRenderTime 25ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/MusicBrainz/Server/Data/Relationship.pm

https://bitbucket.org/mineo/musicbrainz-server
Perl | 454 lines | 382 code | 65 blank | 7 comment | 46 complexity | e217ddfa7e03465d3b684f1a619f89ec MD5 | raw file
Possible License(s): GPL-2.0, CC-BY-3.0, CC0-1.0
  1. package MusicBrainz::Server::Data::Relationship;
  2. use Moose;
  3. use namespace::autoclean -also => [qw( _generate_table_list )];
  4. use Readonly;
  5. use Sql;
  6. use Carp qw( carp croak );
  7. use MusicBrainz::Server::Entity::Relationship;
  8. use MusicBrainz::Server::Data::Artist;
  9. use MusicBrainz::Server::Data::Label;
  10. use MusicBrainz::Server::Data::Link;
  11. use MusicBrainz::Server::Data::LinkType;
  12. use MusicBrainz::Server::Data::Recording;
  13. use MusicBrainz::Server::Data::ReleaseGroup;
  14. use MusicBrainz::Server::Data::URL;
  15. use MusicBrainz::Server::Data::Work;
  16. use MusicBrainz::Server::Data::Utils qw(
  17. placeholders
  18. ref_to_type
  19. type_to_model
  20. );
  21. use Scalar::Util 'weaken';
  22. extends 'MusicBrainz::Server::Data::Entity';
  23. Readonly my @TYPES => qw(
  24. artist
  25. label
  26. recording
  27. release
  28. release_group
  29. url
  30. work
  31. );
  32. my %TYPES = map { $_ => 1} @TYPES;
  33. sub all_link_types
  34. {
  35. return @TYPES;
  36. }
  37. sub _entity_class
  38. {
  39. return 'MusicBrainz::Server::Entity::Relationship';
  40. }
  41. sub _new_from_row
  42. {
  43. my ($self, $row, $obj, $matching_entity_type) = @_;
  44. my $entity0 = $row->{entity0};
  45. my $entity1 = $row->{entity1};
  46. my %info = (
  47. id => $row->{id},
  48. link_id => $row->{link},
  49. edits_pending => $row->{edits_pending},
  50. entity0_id => $entity0,
  51. entity1_id => $entity1,
  52. last_updated => $row->{last_updated}
  53. );
  54. my $weaken;
  55. if (defined $obj) {
  56. if ($matching_entity_type == 0 && $entity0 == $obj->id) {
  57. $weaken = 'entity0';
  58. $info{entity0} = $obj;
  59. $info{direction} = $MusicBrainz::Server::Entity::Relationship::DIRECTION_FORWARD;
  60. }
  61. elsif ($matching_entity_type == 1 && $entity1 == $obj->id) {
  62. $weaken = 'entity1';
  63. $info{entity1} = $obj;
  64. $info{direction} = $MusicBrainz::Server::Entity::Relationship::DIRECTION_BACKWARD;
  65. }
  66. else {
  67. carp "Neither relationship end-point matched the object.";
  68. }
  69. }
  70. my $rel = MusicBrainz::Server::Entity::Relationship->new(%info);
  71. # XXX MASSIVE MASSIVE HACK.
  72. weaken($rel->{$weaken}) if $obj;
  73. return $rel;
  74. }
  75. sub _check_types
  76. {
  77. my ($self, $type0, $type1) = @_;
  78. croak 'Invalid types'
  79. unless exists $TYPES{$type0} && exists $TYPES{$type1} && $type0 le $type1;
  80. }
  81. sub get_by_id
  82. {
  83. my ($self, $type0, $type1, $id) = @_;
  84. $self->_check_types($type0, $type1);
  85. my $query = "SELECT * FROM l_${type0}_${type1} WHERE id = ?";
  86. my $row = $self->sql->select_single_row_hash($query, $id)
  87. or return undef;
  88. return $self->_new_from_row($row);
  89. }
  90. sub _load
  91. {
  92. my ($self, $type, $target_types, @objs) = @_;
  93. my @target_types = @$target_types;
  94. my @types = map { [ sort($type, $_) ] } @target_types;
  95. my @rels;
  96. foreach my $t (@types) {
  97. my $target_type = $type eq $t->[0] ? $t->[1] : $t->[0];
  98. my %objs_by_id = map { $_->id => $_ }
  99. grep { @{ $_->relationships_by_type($target_type) } == 0 } @objs;
  100. my @ids = keys %objs_by_id;
  101. next unless @ids;
  102. my $type0 = $t->[0];
  103. my $type1 = $t->[1];
  104. my (@cond, @params, $target, $target_id, $query);
  105. if ($type eq $type0) {
  106. push @cond, "entity0 IN (" . placeholders(@ids) . ")";
  107. push @params, @ids;
  108. $target = $type1;
  109. $target_id = 'entity1';
  110. }
  111. if ($type eq $type1) {
  112. push @cond, "entity1 IN (" . placeholders(@ids) . ")";
  113. push @params, @ids;
  114. $target = $type0;
  115. $target_id = 'entity0';
  116. }
  117. my $select = "l_${type0}_${type1}.* FROM l_${type0}_${type1}
  118. JOIN link l ON link = l.id";
  119. my $order = 'l.begin_date_year, l.begin_date_month, l.begin_date_day,
  120. l.end_date_year, l.end_date_month, l.end_date_day,
  121. l.ended';
  122. if ($target eq 'url') {
  123. $query = "
  124. SELECT $select
  125. JOIN $target ON $target_id = ${target}.id
  126. WHERE " . join(" OR ", @cond) . "
  127. ORDER BY $order, url";
  128. } else {
  129. my $name_table =
  130. $target eq 'recording' ? 'track_name' :
  131. $target eq 'release_group' ? 'release_name' :
  132. "${target}_name";
  133. $query = "
  134. SELECT $select
  135. JOIN $target ON $target_id = ${target}.id
  136. JOIN $name_table name ON name.id = ${target}.name
  137. WHERE " . join(" OR ", @cond) . "
  138. ORDER BY $order, musicbrainz_collate(name.name)";
  139. }
  140. $self->sql->select($query, @params);
  141. while (1) {
  142. my $row = $self->sql->next_row_hash_ref or last;
  143. my $entity0 = $row->{entity0};
  144. my $entity1 = $row->{entity1};
  145. if ($type eq $type0 && exists $objs_by_id{$entity0}) {
  146. my $obj = $objs_by_id{$entity0};
  147. my $rel = $self->_new_from_row($row, $obj, 0);
  148. $obj->add_relationship($rel);
  149. push @rels, $rel;
  150. }
  151. if ($type eq $type1 && exists $objs_by_id{$entity1}) {
  152. my $obj = $objs_by_id{$entity1};
  153. my $rel = $self->_new_from_row($row, $obj, 1);
  154. $obj->add_relationship($rel);
  155. push @rels, $rel;
  156. }
  157. }
  158. $self->sql->finish;
  159. }
  160. return @rels;
  161. }
  162. sub load_entities
  163. {
  164. my ($self, @rels) = @_;
  165. my %ids_by_type;
  166. foreach my $rel (@rels) {
  167. my $linktype = $rel->link->type->name;
  168. if ($rel->entity0_id && !defined($rel->entity0)) {
  169. my $type = $rel->link->type->entity0_type;
  170. $ids_by_type{$type} = [] if !exists($ids_by_type{$type});
  171. push @{$ids_by_type{$type}}, $rel->entity0_id;
  172. }
  173. if ($rel->entity1_id && !defined($rel->entity1)) {
  174. my $type = $rel->link->type->entity1_type;
  175. $ids_by_type{$type} = [] if !exists($ids_by_type{$type});
  176. push @{$ids_by_type{$type}}, $rel->entity1_id;
  177. }
  178. }
  179. my %data_by_type;
  180. foreach my $type (keys %ids_by_type) {
  181. my @ids = @{$ids_by_type{$type}};
  182. $data_by_type{$type} =
  183. $self->c->model(type_to_model($type))->get_by_ids(@ids);
  184. }
  185. foreach my $rel (@rels) {
  186. if ($rel->entity0_id && !defined($rel->entity0)) {
  187. my $type = $rel->link->type->entity0_type;
  188. my $obj = $data_by_type{$type}->{$rel->entity0_id};
  189. $rel->entity0($obj) if defined($obj);
  190. }
  191. if ($rel->entity1_id && !defined($rel->entity1)) {
  192. my $type = $rel->link->type->entity1_type;
  193. my $obj = $data_by_type{$type}->{$rel->entity1_id};
  194. $rel->entity1($obj) if defined($obj);
  195. }
  196. }
  197. my @load_ac = grep { $_->meta->find_method_by_name('artist_credit') } map { values %$_ } values %data_by_type;
  198. $self->c->model('ArtistCredit')->load(@load_ac);
  199. }
  200. sub load_subset
  201. {
  202. my ($self, $types, @objs) = @_;
  203. my %objs_by_type;
  204. return unless @objs; # nothing to do
  205. foreach my $obj (@objs) {
  206. if (my $type = ref_to_type($obj)) {
  207. $objs_by_type{$type} = [] if !exists($objs_by_type{$type});
  208. push @{$objs_by_type{$type}}, $obj;
  209. }
  210. }
  211. my @rels;
  212. foreach my $type (keys %objs_by_type) {
  213. push @rels, $self->_load($type, $types, @{$objs_by_type{$type}});
  214. }
  215. $self->c->model('Link')->load(@rels);
  216. $self->c->model('LinkType')->load(map { $_->link } @rels);
  217. $self->load_entities(@rels);
  218. return @rels;
  219. }
  220. sub load
  221. {
  222. my ($self, @objs) = @_;
  223. return $self->load_subset(\@TYPES, @objs);
  224. }
  225. sub _generate_table_list
  226. {
  227. my ($type, @end_types) = @_;
  228. # Generate a list of all possible type combinations
  229. my @types;
  230. @end_types = @TYPES unless @end_types;
  231. foreach my $t (@end_types) {
  232. if ($type le $t) {
  233. push @types, ["l_${type}_${t}", 'entity0', 'entity1'];
  234. }
  235. if ($type ge $t) {
  236. push @types, ["l_${t}_${type}", 'entity1', 'entity0'];
  237. }
  238. }
  239. return @types;
  240. }
  241. sub all_pairs
  242. {
  243. my $self = shift;
  244. # Generate a list of all possible type combinations
  245. my @all;
  246. for my $l0 (@TYPES) {
  247. for my $l1 (@TYPES) {
  248. next if $l1 lt $l0;
  249. push @all, [ $l0, $l1 ];
  250. }
  251. }
  252. return @all;
  253. }
  254. sub merge_entities
  255. {
  256. my ($self, $type, $target_id, @source_ids) = @_;
  257. # Delete relationships where the start is the same as the end
  258. # (after merging)
  259. my @ids = ($target_id, @source_ids);
  260. $self->sql->do(
  261. "DELETE FROM l_${type}_${type} WHERE
  262. (entity0 IN (" . placeholders(@ids) . ')
  263. AND entity1 IN (' . placeholders(@ids) . '))',
  264. @ids, @ids);
  265. foreach my $t (_generate_table_list($type)) {
  266. my ($table, $entity0, $entity1) = @$t;
  267. # We want to keep a single row for each link type, and foreign entity.
  268. $self->sql->do(
  269. "DELETE FROM $table
  270. WHERE $entity0 IN (" . placeholders($target_id, @source_ids) . ")
  271. AND id NOT IN (
  272. SELECT DISTINCT ON ($entity1, link) id
  273. FROM $table
  274. WHERE $entity0 IN (" . placeholders($target_id, @source_ids) . ")
  275. )",
  276. $target_id, @source_ids, $target_id, @source_ids
  277. );
  278. # Move all remaining relationships
  279. $self->sql->do("
  280. UPDATE $table SET $entity0 = ?
  281. WHERE $entity0 IN (" . placeholders($target_id, @source_ids) . ")
  282. ", $target_id, $target_id, @source_ids);
  283. }
  284. }
  285. sub delete_entities
  286. {
  287. my ($self, $type, @ids) = @_;
  288. foreach my $t (_generate_table_list($type)) {
  289. my ($table, $entity0, $entity1) = @$t;
  290. $self->sql->do("
  291. DELETE FROM $table a
  292. WHERE $entity0 IN (" . placeholders(@ids) . ")
  293. ", @ids);
  294. }
  295. }
  296. sub exists
  297. {
  298. my ($self, $type0, $type1, $values) = @_;
  299. $self->_check_types($type0, $type1);
  300. return $self->sql->select_single_value(
  301. "SELECT 1 FROM l_${type0}_${type1}
  302. WHERE entity0 = ? AND entity1 = ? AND link = ?",
  303. $values->{entity0_id}, $values->{entity1_id},
  304. $self->c->model('Link')->find({
  305. link_type_id => $values->{link_type_id},
  306. begin_date => $values->{begin_date},
  307. end_date => $values->{end_date},
  308. ended => $values->{ended},
  309. attributes => $values->{attributes},
  310. })
  311. );
  312. }
  313. sub insert
  314. {
  315. my ($self, $type0, $type1, $values) = @_;
  316. $self->_check_types($type0, $type1);
  317. my $row = {
  318. link => $self->c->model('Link')->find_or_insert({
  319. link_type_id => $values->{link_type_id},
  320. begin_date => $values->{begin_date},
  321. end_date => $values->{end_date},
  322. ended => $values->{ended},
  323. attributes => $values->{attributes},
  324. }),
  325. entity0 => $values->{entity0_id},
  326. entity1 => $values->{entity1_id},
  327. };
  328. my $id = $self->sql->insert_row("l_${type0}_${type1}", $row, 'id');
  329. return $self->_entity_class->new( id => $id );
  330. }
  331. sub update
  332. {
  333. my ($self, $type0, $type1, $id, $values) = @_;
  334. $self->_check_types($type0, $type1);
  335. my %link = map {
  336. $_ => $values->{$_};
  337. } qw( link_type_id begin_date end_date attributes ended );
  338. my $row = {};
  339. $row->{link} = $self->c->model('Link')->find_or_insert(\%link);
  340. $row->{entity0} = $values->{entity0_id} if $values->{entity0_id};
  341. $row->{entity1} = $values->{entity1_id} if $values->{entity1_id};
  342. $self->sql->update_row("l_${type0}_${type1}", $row, { id => $id });
  343. }
  344. sub delete
  345. {
  346. my ($self, $type0, $type1, @ids) = @_;
  347. $self->_check_types($type0, $type1);
  348. $self->sql->do("DELETE FROM l_${type0}_${type1}
  349. WHERE id IN (" . placeholders(@ids) . ")", @ids);
  350. }
  351. sub adjust_edit_pending
  352. {
  353. my ($self, $type0, $type1, $adjust, @ids) = @_;
  354. $self->_check_types($type0, $type1);
  355. my $query = "UPDATE l_${type0}_${type1}
  356. SET edits_pending = numeric_larger(0, edits_pending + ?)
  357. WHERE id IN (" . placeholders(@ids) . ")";
  358. $self->sql->do($query, $adjust, @ids);
  359. }
  360. =method lock_and_do
  361. Lock the corresponding relationship table for $type0-$type in ROW EXCLUSIVE
  362. mode, and run a block of code.
  363. =cut
  364. sub lock_and_do {
  365. my ($self, $type0, $type1, $code) = @_;
  366. my ($t0, $t1) = sort ($type0, $type1);
  367. Sql::run_in_transaction(sub {
  368. $code->();
  369. }, $self->c->sql);
  370. }
  371. __PACKAGE__->meta->make_immutable;
  372. no Moose;
  373. 1;
  374. =head1 NAME
  375. MusicBrainz::Server::Data::Relationship
  376. =head1 COPYRIGHT
  377. Copyright (C) 2009 Lukas Lalinsky
  378. This program is free software; you can redistribute it and/or modify
  379. it under the terms of the GNU General Public License as published by
  380. the Free Software Foundation; either version 2 of the License, or
  381. (at your option) any later version.
  382. This program is distributed in the hope that it will be useful,
  383. but WITHOUT ANY WARRANTY; without even the implied warranty of
  384. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  385. GNU General Public License for more details.
  386. You should have received a copy of the GNU General Public License
  387. along with this program; if not, write to the Free Software
  388. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  389. =cut