Skip to content

Fix numeric comparisons for float custom tags #4116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

deluan
Copy link
Member

@deluan deluan commented May 25, 2025

Summary

  • cast tag values to numeric when numeric args are used in smart playlist rules
  • test numeric comparison for custom tag

Testing

  • make test

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @deluan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

Hello team,

Gemini or gemini-code-assist here to provide a summary of this pull request. This PR aims to fix an issue where numeric comparisons on custom tags within smart playlist rules might not work correctly, especially for floating-point values. The core problem is that tag values are stored as text within a JSON structure, and direct comparison might treat them lexicographically rather than numerically.

The solution implemented involves modifying the SQL generation logic. When a comparison is performed on a custom tag and the argument provided for the comparison is detected as a number, the generated SQL is updated to explicitly cast the tag's value to a REAL number type using CAST(value AS REAL). This forces the database to perform a proper numeric comparison.

A new helper function, isNumber, is introduced to reliably check if a given Go value is one of the standard integer or float types. Finally, a dedicated test case is added to the operators test suite to specifically verify that numeric comparisons on custom tags now correctly produce SQL that includes the necessary CAST operation.

Highlights

  • Fix Numeric Tag Comparisons: Addresses an issue preventing correct numeric comparisons (including floats) on custom tags in smart playlist rules.
  • SQL Casting for Numeric Values: Modifies the SQL generation to include CAST(value AS REAL) for custom tag values when the comparison argument is numeric, ensuring proper numeric comparison.
  • Add isNumber Helper: Introduces a new utility function to check if a variable holds a numeric type (int, uint, float types).
  • Add Test Case: Includes a new test specifically validating that numeric comparisons on custom tags correctly generate SQL with the necessary casting.

Changelog

  • model/criteria/fields.go
    • Added logic in tagCond.ToSql to check if the comparison argument is numeric and, if so, modify the generated SQL to cast the tag value to REAL (lines 148-150).
    • Added the isNumber helper function to check for various integer and float types (lines 159-167).
  • model/criteria/operators_test.go
    • Added a new test case within the "Operators" suite to specifically verify that numeric comparisons (using Lt in the example) on custom tags correctly generate SQL with CAST(value AS REAL) (lines 80-86).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


Text becomes a number,
Casting makes the math work right,
Playlist rules now sing.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses an issue with numeric comparisons for custom tags stored as text by introducing a CAST to REAL in the SQL query when the comparison involves a numeric argument. The addition of the isNumber helper function and the corresponding test case are well-implemented. My main feedback point revolves around the behavior of the CAST operation on non-numeric string values within tags, which could lead to unexpected query results in certain edge cases.

Summary of Findings

  • Behavior of SQL CAST on Non-Numeric Tag Values: When a custom tag intended for numeric comparison contains a non-numeric string (e.g., 'N/A', 'Unknown'), SQLite's CAST(value AS REAL) operation will convert such strings to 0.0. This can lead to unexpected outcomes, such as non-numeric tags matching conditions like tag = 0. This was raised as a medium severity issue requiring consideration for potential user impact or documentation.
  • Test Coverage for Float Types in Comparisons: The new test case It("casts numeric comparisons") uses an integer value (6) for the comparison. While the isNumber function correctly identifies float types and CAST(... AS REAL) handles them, explicitly testing with a float literal (e.g., Lt{"rate": 6.5}) would offer slightly more direct confirmation for float value comparisons. This was deemed a low severity point and not formally commented on due to the configured review settings.

Merge Readiness

The core functionality of this PR is well-implemented and addresses the stated problem. There is one medium-severity consideration regarding the behavior of CAST on non-numeric strings that I believe should be discussed or acknowledged before merging to ensure there are no unintended consequences for users. Once that point is addressed (e.g., through documentation, further testing of edge cases, or confirmation of acceptable behavior), the PR should be in good shape. As a reviewer, I am not authorized to approve pull requests.

Comment on lines 148 to 150
if len(args) > 0 && isNumber(args[0]) {
cond = strings.ReplaceAll(cond, "value", "CAST(value AS REAL)")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This change correctly casts the value column to REAL when the comparison argument is numeric. This is a good solution for enabling proper numeric comparisons on tags that are stored as text in JSON.

One aspect to consider is the behavior of CAST(value AS REAL) in SQLite when the value from the JSON tag is not a valid representation of a number (e.g., if a tag expected to be numeric accidentally contains text like "N/A" or "Unknown"). SQLite typically converts such non-numeric strings to 0.0 during the cast.

For example:

  • If a tag rate has the string value "abc", CAST(value AS REAL) for this tag would result in 0.0.
  • A condition like rate > 5 would then evaluate as 0.0 > 5 (false).
  • A condition like rate = 0 would evaluate as 0.0 = 0 (true), which might be surprising if "abc" was not intended to be equivalent to zero.

Could this behavior lead to any unexpected results for users, particularly for equality checks against 0 or when tags might contain such non-parseable strings?

While this PR correctly implements the casting mechanism for intended numeric comparisons, it might be beneficial to consider if this side-effect needs documentation or if there are any scenarios where this could be problematic for users relying on smart playlists.

@deluan
Copy link
Member Author

deluan commented May 25, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a valuable feature for handling numeric comparisons and sorting for custom tags, especially floats. The approach of adding a numeric flag to mappedField and using CAST AS REAL in SQL queries is generally well-aligned with the goal, particularly for SQLite environments. The inclusion of tests for both sorting and filtering scenarios is a positive step.

I have identified one high-severity issue related to how the CAST operation is applied in tagCond.ToSql, which could lead to incorrect behavior for string-based operators on numeric tags. Additionally, there's a medium-severity consideration regarding database agnosticism for the CAST AS REAL syntax.

Addressing the high-severity issue is recommended before merging.

Summary of Findings

  • Broad Numeric Casting in Tag Conditions: In model/criteria/fields.go, the CAST(value AS REAL) operation is applied to any tag marked numeric within tagCond.ToSql(), regardless of the specific comparison operator. This can cause issues with string-based operators (like Contains, StartsWith) by incorrectly casting the value to numeric before the string operation, potentially leading to errors or unexpected results. The cast should be conditional, applying only to numeric comparison operators.
  • Database-Specific Casting Syntax: The use of CAST(... AS REAL) is SQLite-specific. While appropriate for the current context if SQLite is the sole database target, it may require changes if support for other SQL databases is introduced in the future. This is a consideration for long-term maintainability and portability.

Merge Readiness

The pull request makes good progress in supporting numeric comparisons for custom tags. However, due to the high-severity issue identified with the broad application of numeric casting in tagCond.ToSql(), I recommend addressing this before merging. The suggested change aims to make the casting more precise and avoid unintended side effects with string-based operators.

The medium-severity point about database-specific syntax is more of a long-term consideration and doesn't block the current functionality for SQLite.

As a reviewer, I am not authorized to approve pull requests. Please ensure further review and approval from authorized maintainers after addressing the feedback.

Signed-off-by: Deluan <deluan@navidrome.org>
@deluan deluan force-pushed the mxtnyi-codex/fix-custom-tag-float-comparison-in-playlists branch from 5a3e285 to 9e1c19e Compare May 25, 2025 21:49
@deluan deluan merged commit 5e2db2c into master May 25, 2025
20 checks passed
@deluan deluan deleted the mxtnyi-codex/fix-custom-tag-float-comparison-in-playlists branch May 25, 2025 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: Custom tags defined as floats appear to be treated as strings in smart playlists
1 participant