mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 02:17:25 +02:00
fix(smartplaylist): support isMissing/isPresent operators on ReplayGain fields (#5585)
* fix(smartplaylist): support isMissing/isPresent operators on ReplayGain fields ReplayGain values are stored in dedicated nullable columns (rg_album_gain, rg_album_peak, rg_track_gain, rg_track_peak) rather than in the media_file.tags JSON blob. The isMissing/isPresent operators previously only supported tag and role fields, causing two failure modes: 1. Using the documented alias names (replaygain_album_gain etc.) from PR #5256: these got registered as JSON tags from mappings.yaml, so isMissing queried json_tree(media_file.tags, '$.replaygain_album_gain') which is always empty -> the playlist matched ALL songs. 2. Using the canonical field names (rgalbumgain etc.): not a tag/role, so SQL generation returned an error. Because refreshSmartPlaylist deletes old tracks before regenerating, the abort left the playlist empty. Fix: add Nullable bool to FieldInfo and mark the four ReplayGain fields. Add static alias entries (replaygain_album_gain -> rgalbumgain etc.) with Numeric+Nullable set; because AddTagNames skips names already in the field map, these static entries take precedence over the mappings.yaml tag registration. missingExpr now emits IS NULL / IS NOT NULL for nullable column fields instead of the json_tree lookup. Fixes #5584 * chore(smartplaylist): address code review feedback - Simplify the isMissing/isPresent unsupported-field error message, removing the internal "nullable fields" jargon - Standardize comments on mappings.yaml (the actual filename) - Clarify the alias precedence comment in the LookupField test
This commit is contained in:
parent
37e5a1d248
commit
08fd222791
6 changed files with 146 additions and 24 deletions
|
|
@ -4,13 +4,14 @@ import "strings"
|
|||
|
||||
// FieldInfo contains semantic metadata about a criteria field.
|
||||
type FieldInfo struct {
|
||||
Alias string // If set, this field is a backward-compat alias for another canonical name
|
||||
IsTag bool
|
||||
IsRole bool
|
||||
Numeric bool
|
||||
Boolean bool
|
||||
Alias string // If set, this field is a backward-compat alias for another canonical name
|
||||
IsTag bool
|
||||
IsRole bool
|
||||
Numeric bool
|
||||
Boolean bool
|
||||
Nullable bool // If set, this column field can be NULL, so isMissing/isPresent are supported on it
|
||||
|
||||
tagAlias string // If set, a tag name from mappings.yml that resolves to this field
|
||||
tagAlias string // If set, a tag name from mappings.yaml that resolves to this field
|
||||
name string // Canonical name, populated by LookupField from the map key
|
||||
}
|
||||
|
||||
|
|
@ -80,15 +81,23 @@ var fieldMap = map[string]FieldInfo{
|
|||
"mbz_recording_id": {},
|
||||
"mbz_release_track_id": {},
|
||||
"mbz_release_group_id": {},
|
||||
"rgalbumgain": {Numeric: true},
|
||||
"rgalbumpeak": {Numeric: true},
|
||||
"rgtrackgain": {Numeric: true},
|
||||
"rgtrackpeak": {Numeric: true},
|
||||
"rgalbumgain": {Numeric: true, Nullable: true},
|
||||
"rgalbumpeak": {Numeric: true, Nullable: true},
|
||||
"rgtrackgain": {Numeric: true, Nullable: true},
|
||||
"rgtrackpeak": {Numeric: true, Nullable: true},
|
||||
"library_id": {Numeric: true},
|
||||
|
||||
// Backward compatibility: albumtype is an alias for the releasetype tag.
|
||||
"albumtype": {Alias: "releasetype", IsTag: true},
|
||||
|
||||
// Backward compatibility: the replaygain_* tag names (as written in metadata and in the
|
||||
// PR #5256 example) are aliases for the canonical rg* column fields. Without these, the tag
|
||||
// names would be registered as empty tags from mappings.yaml and isMissing would always match.
|
||||
"replaygain_album_gain": {Alias: "rgalbumgain", Numeric: true, Nullable: true},
|
||||
"replaygain_album_peak": {Alias: "rgalbumpeak", Numeric: true, Nullable: true},
|
||||
"replaygain_track_gain": {Alias: "rgtrackgain", Numeric: true, Nullable: true},
|
||||
"replaygain_track_peak": {Alias: "rgtrackpeak", Numeric: true, Nullable: true},
|
||||
|
||||
// Pseudo-field for random sorting
|
||||
"random": {},
|
||||
}
|
||||
|
|
@ -128,7 +137,7 @@ func AddRoles(roles []string) {
|
|||
}
|
||||
}
|
||||
|
||||
// AddTagNames adds tag names to the field map. This is used to add all tags mapped in the `mappings.yml`
|
||||
// AddTagNames adds tag names to the field map. This is used to add all tags mapped in the `mappings.yaml`
|
||||
// configuration file.
|
||||
func AddTagNames(tagNames []string) {
|
||||
for _, tagName := range tagNames {
|
||||
|
|
|
|||
|
|
@ -53,5 +53,27 @@ var _ = Describe("fields", func() {
|
|||
gomega.Expect(field.IsRole).To(gomega.BeTrue())
|
||||
})
|
||||
|
||||
It("marks ReplayGain column fields as nullable", func() {
|
||||
field, ok := LookupField("rgAlbumGain")
|
||||
|
||||
gomega.Expect(ok).To(gomega.BeTrue())
|
||||
gomega.Expect(field.Name()).To(gomega.Equal("rgalbumgain"))
|
||||
gomega.Expect(field.Nullable).To(gomega.BeTrue())
|
||||
gomega.Expect(field.IsTag).To(gomega.BeFalse())
|
||||
})
|
||||
|
||||
It("resolves replaygain_* tag names as aliases to nullable column fields", func() {
|
||||
// AddTagNames skips names already in the field map, so the startup tag registration
|
||||
// (from mappings.yaml) must not convert the pre-registered alias into a tag field.
|
||||
AddTagNames([]string{"replaygain_album_gain"})
|
||||
|
||||
field, ok := LookupField("replaygain_album_gain")
|
||||
|
||||
gomega.Expect(ok).To(gomega.BeTrue())
|
||||
gomega.Expect(field.Name()).To(gomega.Equal("rgalbumgain"))
|
||||
gomega.Expect(field.Nullable).To(gomega.BeTrue())
|
||||
gomega.Expect(field.IsTag).To(gomega.BeFalse())
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue