Passed
Push — master ( 83669c...348a90 )
by Pauli
07:30 queued 04:27
created

ArtistMapper::advFormatSqlCondition()   D

Complexity

Conditions 25
Paths 25

Size

Total Lines 28
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 25
eloc 25
nc 25
nop 2
dl 0
loc 28
rs 4.1666
c 1
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Morris Jobke <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2013, 2014
12
 * @copyright Pauli Järvinen 2016 - 2023
13
 */
14
15
namespace OCA\Music\Db;
16
17
use OCA\Music\Utility\Util;
18
19
use OCP\IConfig;
20
use OCP\IDBConnection;
21
22
/**
23
 * @phpstan-extends BaseMapper<Artist>
24
 */
25
class ArtistMapper extends BaseMapper {
26
	public function __construct(IDBConnection $db, IConfig $config) {
27
		parent::__construct($db, $config, 'music_artists', Artist::class, 'name');
28
	}
29
30
	/**
31
	 * @param string $userId
32
	 * @param integer $sortBy sort order of the result set
33
	 * @return Artist[]
34
	 */
35
	public function findAllHavingAlbums(string $userId, int $sortBy=SortBy::None,
36
			?int $limit=null, ?int $offset=null, ?string $name=null, int $matchMode=MatchMode::Exact,
37
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
38
		$params = [$userId];
39
		$condition = $this->formatExcludeChildlessCondition();
40
41
		if ($name !== null) {
42
			[$nameCond, $nameParams] = $this->formatNameConditions($name, $matchMode);
43
			$condition .= " AND $nameCond";
44
			$params = \array_merge($params, $nameParams);
45
		}
46
47
		[$timestampConds, $timestampParams] = $this->formatTimestampConditions($createdMin, $createdMax, $updatedMin, $updatedMax);
48
		if (!empty($timestampConds)) {
49
			$condition .= " AND $timestampConds";
50
			$params = \array_merge($params, $timestampParams);
51
		}
52
53
		$sql = $this->selectUserEntities($condition, $this->formatSortingClause($sortBy));
54
55
		return $this->findEntities($sql, $params, $limit, $offset);
56
	}
57
58
	/**
59
	 * @param int $genreId
60
	 * @param string $userId
61
	 * @param int|null $limit
62
	 * @param int|null $offset
63
	 * @return Artist[]
64
	 */
65
	public function findAllByGenre(int $genreId, string $userId, ?int $limit=null, ?int $offset=null) : array {
66
		$sql = $this->selectUserEntities('EXISTS '.
67
				'(SELECT 1 FROM `*PREFIX*music_tracks` `track`
68
				  WHERE `*PREFIX*music_artists`.`id` = `track`.`artist_id`
69
				  AND `track`.`genre_id` = ?)');
70
71
		$params = [$userId, $genreId];
72
		return $this->findEntities($sql, $params, $limit, $offset);
73
	}
74
75
	/**
76
	 * returns summed track play counts of each aritst of the user, omittig artists which have never been played
77
	 *
78
	 * @return array [int => int], keys are artist IDs and values are play count sums; ordered largest counts first
79
	 */
80
	public function getArtistTracksPlayCount(string $userId, ?int $limit=null, ?int $offset=null) : array {
81
		$sql = 'SELECT `artist_id`, SUM(`play_count`) AS `sum_count`
82
				FROM `*PREFIX*music_tracks`
83
				WHERE `user_id` = ? AND `play_count` > 0
84
				GROUP BY `artist_id`
85
				ORDER BY `sum_count` DESC, `artist_id`'; // the second criterion is just to make the order predictable on even counts
86
87
		$result = $this->execute($sql, [$userId], $limit, $offset);
88
		$playCountByArtist = [];
89
		while ($row = $result->fetch()) {
90
			$playCountByArtist[$row['artist_id']] = (int)$row['sum_count'];
91
		}
92
		return $playCountByArtist;
93
	}
94
95
	/**
96
	 * returns the latest play time of each artist of the user, omittig artists which have never been played
97
	 *
98
	 * @return array [int => string], keys are artist IDs and values are date-times; ordered latest times first
99
	 */
100
	public function getLatestArtistPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
101
		$sql = 'SELECT `artist_id`, MAX(`last_played`) AS `latest_time`
102
				FROM `*PREFIX*music_tracks`
103
				WHERE `user_id` = ? AND `last_played` IS NOT NULL
104
				GROUP BY `artist_id`
105
				ORDER BY `latest_time` DESC';
106
107
		$result = $this->execute($sql, [$userId], $limit, $offset);
108
		$latestTimeByArtist = [];
109
		while ($row = $result->fetch()) {
110
			$latestTimeByArtist[$row['artist_id']] = $row['latest_time'];
111
		}
112
		return $latestTimeByArtist;
113
	}
114
115
	/**
116
	 * returns the latest play time of each artist of the user, including artists which have never been played
117
	 *
118
	 * @return array [int => ?string], keys are artist IDs and values are date-times (or null for never played);
119
	 *									ordered furthest times first
120
	 */
121
	public function getFurthestArtistPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
122
		$sql = 'SELECT `artist_id`, MAX(`last_played`) AS `latest_time`
123
				FROM `*PREFIX*music_tracks`
124
				WHERE `user_id` = ?
125
				GROUP BY `artist_id`
126
				ORDER BY `latest_time` ASC';
127
128
		$result = $this->execute($sql, [$userId], $limit, $offset);
129
		$latestTimeByArtist = [];
130
		while ($row = $result->fetch()) {
131
			$latestTimeByArtist[$row['artist_id']] = $row['latest_time'];
132
		}
133
		return $latestTimeByArtist;
134
	}
135
136
	/**
137
	 * @param integer[] $coverFileIds
138
	 * @param string[]|null $userIds the users whose music library is targeted; all users are targeted if omitted
139
	 * @return Artist[] artists which got modified (with incomplete data, only id and user are valid),
140
	 *         empty array if none
141
	 */
142
	public function removeCovers(array $coverFileIds, ?array $userIds=null) : array {
143
		// find albums using the given file as cover
144
		$sql = 'SELECT `id`, `user_id` FROM `*PREFIX*music_artists` WHERE `cover_file_id` IN ' .
145
		$this->questionMarks(\count($coverFileIds));
146
		$params = $coverFileIds;
147
		if ($userIds !== null) {
148
			$sql .= ' AND `user_id` IN ' . $this->questionMarks(\count($userIds));
149
			$params = \array_merge($params, $userIds);
150
		}
151
		$artists = $this->findEntities($sql, $params);
152
153
		// if any artists found, remove the cover from those
154
		$count = \count($artists);
155
		if ($count) {
156
			$sql = 'UPDATE `*PREFIX*music_artists`
157
					SET `cover_file_id` = NULL
158
					WHERE `id` IN ' . $this->questionMarks($count);
159
			$params = Util::extractIds($artists);
160
			$this->execute($sql, $params);
161
		}
162
163
		return $artists;
164
	}
165
166
	/**
167
	 * Overridden from the base implementation
168
	 *
169
	 * @see BaseMapper::formatExcludeChildlessCondition()
170
	 */
171
	protected function formatExcludeChildlessCondition() : string {
172
		return 'EXISTS (SELECT 1 FROM `*PREFIX*music_albums` `album` WHERE `*PREFIX*music_artists`.`id` = `album`.`album_artist_id`)';
173
	}
174
175
	/**
176
	 * Overridden from the base implementation to provide support for table-specific rules
177
	 *
178
	 * {@inheritdoc}
179
	 * @see BaseMapper::advFormatSqlCondition()
180
	 */
181
	protected function advFormatSqlCondition(string $rule, string $sqlOp) : string {
182
		// The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL.
183
		// In case of 'recent_played', the MySQL 5.5.62 errored with "1235 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" without the extra subquery.
184
		switch ($rule) {
185
			case 'album':			return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_albums` `a` ON `t`.`album_id` = `a`.`id` WHERE LOWER(`a`.`name`) $sqlOp LOWER(?))";
186
			case 'artist':			return parent::advFormatSqlCondition('name', $sqlOp); // alias
187
			case 'song':			return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE LOWER(`t`.`title`) $sqlOp LOWER(?))";
188
			case 'songrating':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`rating` $sqlOp ?)";
189
			case 'albumrating':		return "`*PREFIX*music_artists`.`id` IN (SELECT `album_artist_id` from `*PREFIX*music_albums` `al` WHERE `al`.`rating` $sqlOp ?)";
190
			case 'played_times':	return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING SUM(`play_count`) $sqlOp ?) mysqlhack)";
191
			case 'last_play':		return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING MAX(`last_played`) $sqlOp ?) mysqlhack)";
192
			case 'played':			// fall through, we give no access to other people's data; not part of the API spec but Ample uses this
193
			case 'myplayed':		return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING MAX(`last_played`) $sqlOp) mysqlhack)"; // operator "IS NULL" or "IS NOT NULL"
194
			case 'album_count':		return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `album_artist_id` FROM `*PREFIX*music_albums` GROUP BY `album_artist_id` HAVING COUNT(`id`) $sqlOp ?) mysqlhack)";
195
			case 'song_count':		return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING COUNT(`id`) $sqlOp ?) mysqlhack)";
196
			case 'time':			return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING SUM(`length`) $sqlOp ?) mysqlhack)";
197
			case 'genre':			return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` GROUP BY `artist_id` HAVING LOWER(GROUP_CONCAT(`g`.`name`)) $sqlOp LOWER(?)) mysqlhack)"; // GROUP_CONCAT not available on PostgreSQL
198
			case 'song_genre':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` WHERE LOWER(`g`.`name`) $sqlOp LOWER(?))";
199
			case 'no_genre':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` WHERE `g`.`name` " . (($sqlOp == 'IS NOT NULL') ? '=' : '!=') . ' "")';
200
			case 'playlist':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE $sqlOp EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE `p`.`id` = ? AND `p`.`track_ids` LIKE CONCAT('%|',`t`.`id`, '|%')))";
201
			case 'playlist_name':	return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE `p`.`name` $sqlOp ? AND `p`.`track_ids` LIKE CONCAT('%|',`t`.`id`, '|%')))";
202
			case 'file':			return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*filecache` `f` ON `t`.`file_id` = `f`.`fileid` WHERE LOWER(`f`.`name`) $sqlOp LOWER(?))";
203
			case 'recent_played':	return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM (SELECT `artist_id`, MAX(`last_played`) FROM `*PREFIX*music_tracks` WHERE `user_id` = ? GROUP BY `artist_id` ORDER BY MAX(`last_played`) DESC LIMIT $sqlOp) mysqlhack)";
204
			case 'mbid_artist':		return parent::advFormatSqlCondition('mbid', $sqlOp); // alias
205
			case 'mbid_song':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`mbid` $sqlOp ?)";
206
			case 'mbid_album':		return "`*PREFIX*music_artists`.`id` IN (SELECT `album_artist_id` from `*PREFIX*music_albums` `al` WHERE `al`.`mbid` $sqlOp ?)";
207
			case 'has_image':		return "`cover_file_id` $sqlOp"; // operator "IS NULL" or "IS NOT NULL"
208
			default:				return parent::advFormatSqlCondition($rule, $sqlOp);
209
		}
210
	}
211
212
	/**
213
	 * {@inheritdoc}
214
	 * @see \OCA\Music\Db\BaseMapper::findUniqueEntity()
215
	 * @param Artist $artist
216
	 * @return Artist
217
	 */
218
	protected function findUniqueEntity(Entity $artist) : Entity {
219
		$sql = $this->selectUserEntities('`hash` = ?');
220
		return $this->findEntity($sql, [$artist->getUserId(), $artist->getHash()]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity...), $artist->getHash())) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\Music\Db\Entity.
Loading history...
Bug introduced by
The method getHash() does not exist on OCA\Music\Db\Entity. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

220
		return $this->findEntity($sql, [$artist->getUserId(), $artist->/** @scrutinizer ignore-call */ getHash()]);
Loading history...
221
	}
222
}
223