Passed
Push — feature/909_Ampache_API_improv... ( fcd91a...cb99df )
by Pauli
02:38
created

ArtistMapper::advFormatSqlCondition()   D

Complexity

Conditions 23
Paths 23

Size

Total Lines 26
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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

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\IDBConnection;
20
21
/**
22
 * @phpstan-extends BaseMapper<Artist>
23
 */
24
class ArtistMapper extends BaseMapper {
25
	public function __construct(IDBConnection $db) {
26
		parent::__construct($db, 'music_artists', Artist::class, 'name');
27
	}
28
29
	/**
30
	 * @param string $userId
31
	 * @param integer $sortBy sort order of the result set
32
	 * @return Artist[]
33
	 */
34
	public function findAllHavingAlbums(string $userId, int $sortBy=SortBy::None, ?int $limit=null, ?int $offset=null) : array {
35
		$sql = $this->selectUserEntities('EXISTS '.
36
				'(SELECT 1 FROM `*PREFIX*music_albums` `album` '.
37
				' WHERE `*PREFIX*music_artists`.`id` = `album`.`album_artist_id`)',
38
				($sortBy == SortBy::Name) ? 'ORDER BY LOWER(`name`)' : null);
39
40
		$params = [$userId];
41
		return $this->findEntities($sql, $params, $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

41
		return /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params, $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
42
	}
43
44
	/**
45
	 * @param int $genreId
46
	 * @param string $userId
47
	 * @param int|null $limit
48
	 * @param int|null $offset
49
	 * @return Artist[]
50
	 */
51
	public function findAllByGenre(int $genreId, string $userId, ?int $limit=null, ?int $offset=null) : array {
52
		$sql = $this->selectUserEntities('EXISTS '.
53
				'(SELECT 1 FROM `*PREFIX*music_tracks` `track`
54
				  WHERE `*PREFIX*music_artists`.`id` = `track`.`artist_id`
55
				  AND `track`.`genre_id` = ?)');
56
57
		$params = [$userId, $genreId];
58
		return $this->findEntities($sql, $params, $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

58
		return /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params, $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
59
	}
60
61
	/**
62
	 * returns summed track play counts of each aritst of the user, omittig artists which have never been played
63
	 *
64
	 * @return array [int => int], keys are artist IDs and values are play count sums; ordered largest counts first
65
	 */
66
	public function getArtistTracksPlayCount(string $userId, ?int $limit=null, ?int $offset=null) : array {
67
		$sql = 'SELECT `artist_id`, SUM(`play_count`) AS `sum_count`
68
				FROM `*PREFIX*music_tracks`
69
				WHERE `user_id` = ? AND `play_count` > 0
70
				GROUP BY `artist_id`
71
				ORDER BY `sum_count` DESC, `artist_id`'; // the second criterion is just to make the order predictable on even counts
72
73
		$result = $this->execute($sql, [$userId], $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

73
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, [$userId], $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
74
		$playCountByArtist = [];
75
		while ($row = $result->fetch()) {
76
			$playCountByArtist[$row['artist_id']] = (int)$row['sum_count'];
77
		}
78
		return $playCountByArtist;
79
	}
80
81
	/**
82
	 * returns the latest play time of each artist of the user, omittig artists which have never been played
83
	 *
84
	 * @return array [int => string], keys are artist IDs and values are date-times; ordered latest times first
85
	 */
86
	public function getLatestArtistPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
87
		$sql = 'SELECT `artist_id`, MAX(`last_played`) AS `latest_time`
88
				FROM `*PREFIX*music_tracks`
89
				WHERE `user_id` = ? AND `last_played` IS NOT NULL
90
				GROUP BY `artist_id`
91
				ORDER BY `latest_time` DESC';
92
93
		$result = $this->execute($sql, [$userId], $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

93
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, [$userId], $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
94
		$latestTimeByArtist = [];
95
		while ($row = $result->fetch()) {
96
			$latestTimeByArtist[$row['artist_id']] = $row['latest_time'];
97
		}
98
		return $latestTimeByArtist;
99
	}
100
101
	/**
102
	 * returns the latest play time of each artist of the user, including artists which have never been played
103
	 *
104
	 * @return array [int => ?string], keys are artist IDs and values are date-times (or null for never played);
105
	 *									ordered furthest times first
106
	 */
107
	public function getFurthestArtistPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
108
		$sql = 'SELECT `artist_id`, MAX(`last_played`) AS `latest_time`
109
				FROM `*PREFIX*music_tracks`
110
				WHERE `user_id` = ?
111
				GROUP BY `artist_id`
112
				ORDER BY `latest_time` ASC';
113
114
		$result = $this->execute($sql, [$userId], $limit, $offset);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

114
		$result = /** @scrutinizer ignore-deprecated */ $this->execute($sql, [$userId], $limit, $offset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
115
		$latestTimeByArtist = [];
116
		while ($row = $result->fetch()) {
117
			$latestTimeByArtist[$row['artist_id']] = $row['latest_time'];
118
		}
119
		return $latestTimeByArtist;
120
	}
121
122
	/**
123
	 * @param integer[] $coverFileIds
124
	 * @param string[]|null $userIds the users whose music library is targeted; all users are targeted if omitted
125
	 * @return Artist[] artists which got modified (with incomplete data, only id and user are valid),
126
	 *         empty array if none
127
	 */
128
	public function removeCovers(array $coverFileIds, ?array $userIds=null) : array {
129
		// find albums using the given file as cover
130
		$sql = 'SELECT `id`, `user_id` FROM `*PREFIX*music_artists` WHERE `cover_file_id` IN ' .
131
		$this->questionMarks(\count($coverFileIds));
132
		$params = $coverFileIds;
133
		if ($userIds !== null) {
134
			$sql .= ' AND `user_id` IN ' . $this->questionMarks(\count($userIds));
135
			$params = \array_merge($params, $userIds);
136
		}
137
		$artists = $this->findEntities($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...dMapper::findEntities() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

137
		$artists = /** @scrutinizer ignore-deprecated */ $this->findEntities($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
138
139
		// if any artists found, remove the cover from those
140
		$count = \count($artists);
141
		if ($count) {
142
			$sql = 'UPDATE `*PREFIX*music_artists`
143
					SET `cover_file_id` = NULL
144
					WHERE `id` IN ' . $this->questionMarks($count);
145
			$params = Util::extractIds($artists);
146
			$this->execute($sql, $params);
0 ignored issues
show
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...tcloudMapper::execute() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

146
			/** @scrutinizer ignore-deprecated */ $this->execute($sql, $params);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
147
		}
148
149
		return $artists;
150
	}
151
152
	/**
153
	 * Overridden from the base implementation to provide support for table-specific rules
154
	 *
155
	 * {@inheritdoc}
156
	 * @see BaseMapper::advFormatSqlCondition()
157
	 */
158
	protected function advFormatSqlCondition(string $rule, string $sqlOp) : string {
159
		// The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL.
160
		// 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.
161
		switch ($rule) {
162
			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(?))";
163
			case 'artist':			return parent::advFormatSqlCondition('name', $sqlOp); // alias
164
			case 'song':			return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE LOWER(`t`.`title`) $sqlOp LOWER(?))";
165
			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)";
166
			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)";
167
			case 'played':			// fall through, we give no access to other people's data; not part of the API spec but Ample uses this
168
			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"
169
			case 'album_count':		return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_albums` `a` ON `t`.`album_id` = `a`.`id` GROUP BY `artist_id` HAVING COUNT(DISTINCT `a`.`id`) $sqlOp ?) mysqlhack)";
170
			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)";
171
			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)";
172
			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
173
			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(?))";
174
			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') ? '=' : '!=') . ' "")';
175
			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`, '|%')))";
176
			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`, '|%')))";
177
			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(?))";
178
			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)";
179
			case 'mbid_artist':		return parent::advFormatSqlCondition('mbid', $sqlOp); // alias
180
			case 'mbid_song':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`mbid` $sqlOp ?)";
181
			case 'mbid_album':		return "`*PREFIX*music_artists`.`id` IN (SELECT `album_artist_id` from `*PREFIX*music_albums` `al` WHERE `al`.`mbid` $sqlOp ?)";
182
			case 'has_image':		return "`cover_file_id` $sqlOp"; // operator "IS NULL" or "IS NOT NULL"
183
			default:				return parent::advFormatSqlCondition($rule, $sqlOp);
184
		}
185
	}
186
187
	/**
188
	 * {@inheritdoc}
189
	 * @see \OCA\Music\Db\BaseMapper::findUniqueEntity()
190
	 * @param Artist $artist
191
	 * @return Artist
192
	 */
193
	protected function findUniqueEntity(Entity $artist) : Entity {
194
		$sql = $this->selectUserEntities('`hash` = ?');
195
		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...
Deprecated Code introduced by
The function OCA\Music\AppFramework\D...oudMapper::findEntity() has been deprecated: 14.0.0 Move over to QBMapper ( Ignorable by Annotation )

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

195
		return /** @scrutinizer ignore-deprecated */ $this->findEntity($sql, [$artist->getUserId(), $artist->getHash()]);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

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

195
		return $this->findEntity($sql, [$artist->getUserId(), $artist->/** @scrutinizer ignore-call */ getHash()]);
Loading history...
196
	}
197
}
198