Skip to content

feat(deletes): support item attribute conditions in API and consumer#7570

Merged
onewland merged 39 commits into
masterfrom
feat/delete-by-attribute-structured
Dec 11, 2025
Merged

feat(deletes): support item attribute conditions in API and consumer#7570
onewland merged 39 commits into
masterfrom
feat/delete-by-attribute-structured

Conversation

@onewland

@onewland onewland commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

This is a bit of a redo of #7537, to avoid making logic decisions based on the contents of a string which could contain either a column or an subscriptable reference.

Changes:

  • refactored the common logic out of snuba/web/rpc/v1/resolvers/R_eap_items/ for these code paths to use attribute_key_to_expression (without which, there would be a circular import issue)
  • add support for attribute_conditions in the lightweight deletions consumer
    • this means the consumer does resolution of attribute_conditions to "columns" internally
    • the wire format of DeleteQueryMessage has two new fields: attribute_conditions and attribute_conditions_item_type
  • fixed the unimplemented API validation of item types, and the respective allowed attributes
  • actually passing attribute_conditions queries through is gated behind a new run-time config permit_delete_by_attribute, defaulting to off

End-to-end verification pending, walk-through: https://www.notion.so/sentry/e2e-attribute-testing-2ab8b10e4b5d80cea5cfdc255cff5305

@codecov

codecov Bot commented Dec 4, 2025

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
1 1 0 0
View the top 1 failed test(s) by shortest run time
::tests.web.rpc.test_common
Stack Traces | 0s run time
import file mismatch:
imported module 'test_common' has this __file__ attribute:
  .../tests/protos/test_common.py
which is not the same as the test file we want to collect:
  .../web/rpc/test_common.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Comment thread snuba/web/rpc/__init__.py Outdated
Comment on lines +25 to +34
# Re-export for backward compatibility
__all__ = [
"ATTRIBUTES_TO_COALESCE",
"COLUMN_PREFIX",
"NORMALIZED_COLUMNS_EAP_ITEMS",
"PROTO_TYPE_TO_ATTRIBUTE_COLUMN",
"PROTO_TYPE_TO_CLICKHOUSE_TYPE",
"attribute_key_to_expression",
"apply_virtual_columns",
]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wanted to minimize number of changes in this PR, but I think on a follow-up I'll just totally delete all these re-exports and point anything that depends on them to snuba.protos.common

@onewland onewland changed the title [wip] delete by attribute with more structure than #7537 feat(deletes): support item attribute conditions in API and consumer Dec 8, 2025
@onewland
onewland marked this pull request as ready for review December 8, 2025 20:26
@onewland
onewland requested review from a team as code owners December 8, 2025 20:26
Comment thread snuba/web/delete_query.py Outdated
Comment thread snuba/web/delete_query.py
Comment thread snuba/web/delete_query.py Outdated
Comment thread snuba/web/delete_query.py Outdated
Comment thread snuba/web/delete_query.py
Try to get rid of specialized eap_items bucketing logic in
snuba/web/delete_query in #7570

I think this is a good compromise between running a full query pipeline
and duplicating bucketing logic in delete_query. It will continue to
execute column translations as configuration is changed in the
entity/storage YAMLs, increasing decoupling but doesn't bring in a ton
of extra scope.

Why not run the entire query pipeline? Unfortunately, this would require
coupling all deletes to *entity* definitions (these mappers exist on the
entity), but we configure and pass around delete queries based on the
storage.

Tested end-to-end with this flow:
https://www.notion.so/sentry/e2e-attribute-testing-2ab8b10e4b5d80cea5cfdc255cff5305
Comment thread snuba/web/delete_query.py
Comment on lines +392 to +402
exp = equals(virtual_column, literal(attr_values[0]))
else:
literal_values = [literal(v) for v in attr_values]
exp = in_cond(
virtual_column,
literals_tuple(alias=None, literals=literal_values),
)
and_conditions.append(exp)

where_clause = combine_and_conditions(and_conditions)
return _preprocess_for_items(storage, where_clause)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The _construct_condition function calls attribute_key_to_expression without handling the MalformedAttributeException it can raise, leading to an unhandled exception from user-provided data.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

The _construct_condition function in snuba/web/delete_query.py calls attribute_key_to_expression with an AttributeKey proto object that comes directly from the client request. The validation in _validate_attribute_conditions is insufficient as it does not check the structure of the AttributeKey proto itself. If a client sends a request with a malformed AttributeKey (e.g., with type set to TYPE_UNSPECIFIED), attribute_key_to_expression will raise a MalformedAttributeException. Since this exception is not caught anywhere in the deletion code path, it will result in an unhandled exception and crash the server process.

💡 Suggested Fix

Wrap the call to attribute_key_to_expression in a try-except block to catch MalformedAttributeException. Convert this exception into a graceful error response to the client, similar to the pattern used in snuba/web/rpc/v1/resolvers/R_eap_items/common/common.py. This prevents the server from crashing on invalid client input.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: snuba/web/delete_query.py#L384-L402

Potential issue: The `_construct_condition` function in `snuba/web/delete_query.py`
calls `attribute_key_to_expression` with an `AttributeKey` proto object that comes
directly from the client request. The validation in `_validate_attribute_conditions` is
insufficient as it does not check the structure of the `AttributeKey` proto itself. If a
client sends a request with a malformed `AttributeKey` (e.g., with `type` set to
`TYPE_UNSPECIFIED`), `attribute_key_to_expression` will raise a
`MalformedAttributeException`. Since this exception is not caught anywhere in the
deletion code path, it will result in an unhandled exception and crash the server
process.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 6901848

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is in the validation step right? So if it fails it should be fine?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's right. EndpointDeleteTraceItems, which is the only entrypoint for EAP deletes, calls into this method in order to return the count of rows that will be deleted. We should maybe wrap it in that endpoint to make it a 400 instead of a 500 but I don't think it particularly matters what the API returns since it'll get logged to Sentry either way.

@onewland
onewland merged commit 02f8035 into master Dec 11, 2025
34 checks passed
@onewland
onewland deleted the feat/delete-by-attribute-structured branch December 11, 2025 19:00
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.

2 participants