Skip to content

Build RSQL filters as one expression to fix computed field filters - #25569

Draft
marifex wants to merge 1 commit into
glpi-project:11.0/bugfixesfrom
marifex:fix/rsql-having-computed-fields
Draft

marifex wants to merge 1 commit into
glpi-project:11.0/bugfixesfrom
marifex:fix/rsql-having-computed-fields

Conversation

@marifex

@marifex marifex commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #25565.

The RSQL parser built the SQL as two strings, WHERE for regular fields and HAVING for computed ones, but appended the logical separators (AND/OR) and the group parentheses only to the WHERE string. So a filter that put two computed comparisons together left them next to each other in HAVING with no operator between them, and a computed comparison inside a group left an empty pair of parentheses in WHERE. Both are invalid SQL and return a 500. On custom assets the computed fields are the custom fields, so filtering on two of them, or wrapping one in parentheses, hit this. An OR between a regular field and a computed field also could not be represented once the expression was split between the two clauses.

The parser now builds the whole expression, with its separators and groups, as a single string. It stays in the WHERE clause, so index usage is unchanged for the common all-regular case. Only when the filter references at least one computed property is the whole expression moved to the HAVING clause, where the computed columns are available and the operator precedence and grouping stay intact. This is the trade discussed in the issue: the extra cost of evaluating regular comparisons in HAVING is only paid when a computed field is actually part of the filter.

Added tests covering two computed comparisons with AND and with OR, a computed comparison in a group, and a regular field combined with a computed field with both AND and OR. They fail without the change and pass with it.

The RSQL parser appended the logical separators and the groups only to
the WHERE string while sending computed property comparisons to a
separate HAVING string. Two computed comparisons then ended up next to
each other in HAVING with no operator between them, and a computed
comparison inside a group left an empty pair of parentheses in WHERE.
Both produce invalid SQL and a 500. An OR between a regular and a
computed comparison could also not be represented once the expression
was split.

Build the whole expression, with its separators and groups, as a single
string and keep it in the WHERE clause, which preserves index usage for
the common case. Only when the filter references a computed property is
the whole expression moved to the HAVING clause, so the comparison can
resolve against the computed column and the operator precedence stays
intact.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo


🔴 High

1. Mixed joined filters miss assets 🐞 Bug
Description
Parser::parse moves raw regular-field predicates into HAVING whenever $uses_computation is
true, even when the field belongs to a multi-valued join grouped only by asset ID. A custom-field
filter combined with group.name or group_tech.name therefore evaluates an arbitrary joined row
after grouping instead of filtering matching rows before grouping, so assets with multiple groups
can be incorrectly included or omitted.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Moving an entire mixed RSQL expression to `HAVING` changes the semantics of predicates on multi-valued joined properties because searches group by the root record ID. Such predicates must continue to test joined rows rather than an arbitrary value from each grouped result.

## Fix Focus Areas
- src/Glpi/Api/HL/RSQL/Parser.php[329-364]
- src/Glpi/Api/HL/Search.php[145-189]
- src/Glpi/Api/HL/Search.php[214-241]
- tests/functional/Glpi/Api/HL/Controller/CustomAssetControllerTest.php[98-127]

## Recommended Fix
Render computed-property comparisons from their underlying computation expression so the unified logical expression can remain in `WHERE`, rather than referencing the computed SELECT alias and moving all predicates to `HAVING`. Add mixed computed-field tests for assets assigned to multiple groups, covering both matching and nonmatching joined rows with AND and OR.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
🟠 **Medium**
2. Ignored filters can cause server errors 🐞 Bug
Description
Parser::parse now appends every separator once $sql_string contains a computed comparison, while
an invalid comparison contributes no expression and does not remove either adjacent separator. A
filter containing a valid computed comparison, an unknown property, and another valid comparison
consequently produces OR OR or AND AND, sending malformed SQL to the database instead of
ignoring the unknown property as the parser contract requires.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The single-string RSQL builder emits logical separators before knowing whether the following comparison is valid. When an invalid comparison is skipped between valid comparisons, duplicate separators remain and produce malformed SQL.

## Fix Focus Areas
- src/Glpi/Api/HL/RSQL/Parser.php[283-348]
- tests/functional/Glpi/Api/HL/RSQL/ParserTest.php[216-241]

## Recommended Fix
Track logical operators as pending tokens and append them only when a subsequent valid comparison is rendered. Apply the same validity-aware handling to groups, then add a parser test with a computed comparison followed by an invalid comparison and another valid comparison for both AND and OR.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


@marifex
marifex marked this pull request as draft September 18, 2026 09:02
@marifex

marifex commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

@cconard96 wanted your steer on this before reworking, since it is your area. The automated review above raises a fair point: moving the whole expression to HAVING when a computed field is present changes the meaning of predicates on multi-valued joined properties (like the asset groups), which need to be evaluated in WHERE before the group by. So it regresses a mixed filter such as a group condition AND a custom field, which works today.

Two directions I see. One, render computed comparisons from their computation expression instead of the SELECT alias, so the whole expression stays in WHERE and nothing moves to HAVING; this keeps joined semantics and index usage, but the operator callables pre-quote the field name, so each would need to emit the RAW criterion form for computed fields. Two, keep regular and joined predicates in WHERE and computed ones in HAVING as today, but track the operators and groups in both clauses and emit a neutral 1 into the clause a predicate does not belong to; this is smaller and safe for joined semantics, but a top-level OR between a regular and a computed field would over-return rather than error, since that cannot be expressed once split. Which would you prefer? I have marked this draft meanwhile. The separator case, an unknown property between two valid comparisons, reproduces on the current code with plain fields too, so I will handle that separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HL API: RSQL filters on custom asset custom fields return 500 when combined or grouped

1 participant