Skip to content

[OPIK-5431] [BE] Fix IS_EMPTY/IS_NOT_EMPTY filters for DICTIONARY field type#6042

Merged
thiagohora merged 4 commits intomainfrom
thiaghora/OPIK-5431-fix-is-empty-filter-for-dictionary-fields
Apr 2, 2026
Merged

[OPIK-5431] [BE] Fix IS_EMPTY/IS_NOT_EMPTY filters for DICTIONARY field type#6042
thiagohora merged 4 commits intomainfrom
thiaghora/OPIK-5431-fix-is-empty-filter-for-dictionary-fields

Conversation

@thiagohora
Copy link
Copy Markdown
Contributor

Details

Filtering experiments by a metadata/dictionary field using the IS_EMPTY or IS_NOT_EMPTY operators was returning HTTP 400. This prevented the "Undefined" group from showing results when grouping experiments by configuration in the UI.

Root causes:

  1. FilterQueryBuilder had no SQL templates for the IS_EMPTY/IS_NOT_EMPTY operator + DICTIONARY/DICTIONARY_STATE_DB type combinations — causing a null operator lookup and a 400 response.
  2. FiltersFactory validation rejected these filters because value was null/empty for DICTIONARY/DICTIONARY_STATE_DB types (those operators don't require a value, only a key).
  3. FiltersFactory.toValidAndDecoded() tried to URLDecoder.decode(null, ...), causing a NullPointerException for no-value operators.

Fix:

  • Added JSON_EXISTS-based SQL templates in FilterQueryBuilder for both operators:
    • IS_EMPTYJSON_EXISTS(col, :filterKey) = false (key not present)
    • IS_NOT_EMPTYJSON_EXISTS(col, :filterKey) = true (key present)
    • Using JSON_EXISTS correctly distinguishes "key not present" from "key present with empty string value".
  • Updated FIELD_TYPE_VALIDATION_MAP in FiltersFactory to accept IS_EMPTY/IS_NOT_EMPTY with only a key (no value required) for DICTIONARY and DICTIONARY_STATE_DB types.
  • Guarded the URL-decode step in FiltersFactory.toValidAndDecoded() to skip NO_VALUE_OPERATORS, preventing NPE when filter.value() is null.

Change checklist

  • User facing
  • Documentation update

Issues

  • OPIK-5431

Testing

Added integration tests in ExperimentsResourceFindProjectExperimentsTest#findByFilterMetadataEmpty:

  • IS_NOT_EMPTY on key $.config.name → returns the experiment that has the key in metadata.
  • IS_EMPTY on key $.config.name → returns the experiment whose metadata is null (key not present at all).

To reproduce manually:

  1. Create two experiments in the same project — one with metadata = {"config": {"name": "simulated"}}, one with no metadata.
  2. In the UI, group by metadata.config.name — the "Undefined" group should appear and contain the experiment without the key.
  3. Alternatively, call the experiments endpoint with filter [{"field":"metadata","type":"dictionary","operator":"is_empty","key":"$.config.name"}] — previously returned 400, now returns the correct experiment.

Documentation

N/A — internal filter system fix, no configuration or API contract changes.

…ld type

Filtering experiments (or any entity) by a metadata/dictionary field using
the IS_EMPTY or IS_NOT_EMPTY operators was returning HTTP 400 because:
1. FilterQueryBuilder had no SQL templates for those operator+type combos.
2. FiltersFactory validation rejected the filter when value was null/empty
   for DICTIONARY/DICTIONARY_STATE_DB types.
3. FiltersFactory tried to URL-decode a null value, causing a NullPointerException.

Fix:
- Add JSON_EXISTS-based SQL templates to FilterQueryBuilder for both operators
  on DICTIONARY and DICTIONARY_STATE_DB:
    IS_EMPTY     → JSON_EXISTS(col, :filterKey) = false
    IS_NOT_EMPTY → JSON_EXISTS(col, :filterKey) = true
  This correctly distinguishes "key not present" from "key present but empty".
- Update FIELD_TYPE_VALIDATION_MAP in FiltersFactory to accept IS_EMPTY /
  IS_NOT_EMPTY with only a key (no value required).
- Guard the URL-decode step in toValidAndDecoded() to skip NO_VALUE_OPERATORS,
  preventing NPE when filter.value() is null.

Add integration tests in ExperimentsResourceFindProjectExperimentsTest:
- IS_NOT_EMPTY on $.config.name returns the experiment that has the key.
- IS_EMPTY on $.config.name returns the experiment whose metadata is null
  (key not present at all).

Implements OPIK-5431: [BE] Grouping experiments by configuration doesn't show
results for Undefined group
@thiagohora thiagohora requested a review from a team as a code owner April 1, 2026 17:35
@github-actions github-actions bot added java Pull requests that update Java code Backend tests Including test files, or tests related like configuration. labels Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Backend Tests - Integration Group 5

 26 files   -  4   26 suites   - 4   2m 54s ⏱️ +20s
162 tests +37  162 ✅ +38  0 💤 ±0  0 ❌ ±0 
115 runs   -  9  115 ✅  -  9  0 💤 ±0  0 ❌ ±0 

Results for commit 340aba2. ± Comparison against base commit 94c82d8.

This pull request removes 17 and adds 54 tests. Note that renamed tests count towards both.
com.comet.opik.domain.retention.RetentionPolicyServiceTest ‑ Unknown test
com.comet.opik.domain.retention.RetentionPolicyServiceTest$CatchUpJob ‑ noApplyToPast_catchUpDoneImmediately
com.comet.opik.domain.retention.RetentionPolicyServiceTest$CatchUpJob ‑ smallWorkspace_oneShotCatchUp
com.comet.opik.domain.retention.RetentionPolicyServiceTest$DeletionVerification ‑ deletesOnlyOldRowsAcrossAllTables
com.comet.opik.domain.retention.RetentionPolicyServiceTest$DeletionVerification ‑ deletionIsScopedToTargetWorkspaces
com.comet.opik.domain.retention.RetentionPolicyServiceTest$RetentionCycleExecution ‑ deletesExpiredDataAndKeepsRecentData
com.comet.opik.domain.retention.RetentionPolicyServiceTest$RetentionCycleExecution ‑ noRulesInRange_noDeletes
com.comet.opik.domain.retention.RetentionPolicyServiceTest$RetentionCycleExecution ‑ unlimitedRetentionRulesAreIgnored
com.comet.opik.domain.retention.RetentionPolicyServiceTest$RulePriorityResolution ‑ disabledRulesNotExecuted
com.comet.opik.domain.retention.RetentionPolicyServiceTest$RulePriorityResolution ‑ multipleWorkspacesDifferentRetention
…
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByCreatedDate
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[10]
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[11]
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[12]
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[13]
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[14]
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[15]
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[16]
com.comet.opik.api.resources.v1.priv.DatasetsResourceFindProjectDatasetsTest ‑ getProjectDatasets__whenFetchingAllDatasets__thenReturnDatasetsSortedByValidFields(Comparator, SortingField)[17]
…

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Backend Tests - Integration Group 11

221 tests   - 4   221 ✅  - 4   4m 43s ⏱️ +9s
 44 suites ±0     0 💤 ±0 
 44 files   ±0     0 ❌ ±0 

Results for commit 024cf49. ± Comparison against base commit 94c82d8.

This pull request removes 6 and adds 2 tests. Note that renamed tests count towards both.
com.comet.opik.api.resources.v1.priv.LlmProviderApiKeyResourceBuiltinProviderTest ‑ testBatchDelete_ignoresBuiltinProvider
com.comet.opik.api.resources.v1.priv.LlmProviderApiKeyResourceBuiltinProviderTest ‑ testFindProviders_builtinProviderHasCorrectConfiguration
com.comet.opik.api.resources.v1.priv.LlmProviderApiKeyResourceBuiltinProviderTest ‑ testFindProviders_builtinProviderHasReadOnlyTrue
com.comet.opik.api.resources.v1.priv.LlmProviderApiKeyResourceBuiltinProviderTest ‑ testFindProviders_builtinProviderIsAddedAtEnd
com.comet.opik.api.resources.v1.priv.LlmProviderApiKeyResourceBuiltinProviderTest ‑ testFindProviders_includesVirtualBuiltinProvider_whenEnabled
com.comet.opik.api.resources.v1.priv.LlmProviderApiKeyResourceBuiltinProviderTest ‑ testFindProviders_userProvidersHaveReadOnlyFalse
com.comet.opik.api.resources.v1.events.WebhookSubscriberLoggingTest ‑ processEvent_whenSuccessfulWebhook_shouldSendRequestAndCreateLogs
com.comet.opik.api.resources.v1.events.WebhookSubscriberLoggingTest ‑ processEvent_whenWebhookFails_shouldRetryAndCreateErrorLogs

♻️ This comment has been updated with latest results.

@thiagohora thiagohora added the test-environment Deploy Opik adhoc environment label Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🔄 Test environment deployment process has started

Phase 1: Deploying base version 1.10.59-4786 (from main branch) if environment doesn't exist
Phase 2: Building new images from PR branch thiaghora/OPIK-5431-fix-is-empty-filter-for-dictionary-fields
Phase 3: Will deploy newly built version after build completes

You can monitor the progress here.

@CometActions
Copy link
Copy Markdown
Collaborator

Test environment is now available!

To configure additional Environment variables for your environment, run [Deploy Opik AdHoc Environment workflow] (https://github.com/comet-ml/comet-deployment/actions/workflows/deploy_opik_adhoc_env.yaml)

Access Information

The deployment has completed successfully and the version has been verified.

@CometActions
Copy link
Copy Markdown
Collaborator

🌙 Nightly cleanup: The test environment for this PR (pr-6042) has been cleaned up to free cluster resources. PVCs are preserved — re-deploy to restore the environment.

@CometActions CometActions removed the test-environment Deploy Opik adhoc environment label Apr 2, 2026
BorisTkachenko
BorisTkachenko previously approved these changes Apr 2, 2026
Copy link
Copy Markdown
Contributor

@BorisTkachenko BorisTkachenko left a comment

Choose a reason for hiding this comment

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

Looks good!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Python SDK Compatibility V1 E2E Tests Results (Python 3.13)

118 tests  ±0   118 ✅ +2   3m 52s ⏱️ - 3m 35s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌  - 2 

Results for commit 340aba2. ± Comparison against base commit 94c82d8.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Python SDK Compatibility V1 E2E Tests Results (Python 3.10)

118 tests  ±0   118 ✅ +2   4m 1s ⏱️ - 3m 14s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌  - 2 

Results for commit 340aba2. ± Comparison against base commit 94c82d8.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Python SDK Compatibility V1 E2E Tests Results (Python 3.11)

118 tests  ±0   118 ✅ +4   3m 54s ⏱️ - 3m 12s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌  - 4 

Results for commit 340aba2. ± Comparison against base commit 94c82d8.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Python SDK Compatibility V1 E2E Tests Results (Python 3.14)

118 tests  ±0   118 ✅ +3   3m 52s ⏱️ - 2m 21s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌  - 3 

Results for commit 340aba2. ± Comparison against base commit 94c82d8.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Python SDK Compatibility V1 E2E Tests Results (Python 3.12)

118 tests  ±0   118 ✅ +4   3m 57s ⏱️ - 3m 1s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌  - 4 

Results for commit 340aba2. ± Comparison against base commit 94c82d8.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

TS SDK E2E Tests - Node 20

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
1 files   ±0   0 ❌ ±0 
1 errors

For more details on these parsing errors, see this check.

Results for commit 024cf49. ± Comparison against base commit 94c82d8.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

TS SDK E2E Tests - Node 22

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
1 files   ±0   0 ❌ ±0 
1 errors

For more details on these parsing errors, see this check.

Results for commit 024cf49. ± Comparison against base commit 94c82d8.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

TS SDK E2E Tests - Node 18

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
1 files   ±0   0 ❌ ±0 
1 errors

For more details on these parsing errors, see this check.

Results for commit 024cf49. ± Comparison against base commit 94c82d8.

…andle null and empty values

- IS_EMPTY now matches: key absent, empty string value, or JSON null value
- IS_NOT_EMPTY now requires: key present with a non-empty, non-null value
- JSON_VALUE returns the string 'null' for JSON null in ClickHouse (not SQL NULL),
  so the condition checks for both '' and 'null' explicitly
- Updated findByFilterMetadataEmpty test to use assertExperiments
- Added findByFilterMetadataEmptyWithNullAndBlankValues test covering the
  key-with-null-value and key-with-empty-string-value cases
@thiagohora thiagohora merged commit 8fa4025 into main Apr 2, 2026
76 checks passed
@thiagohora thiagohora deleted the thiaghora/OPIK-5431-fix-is-empty-filter-for-dictionary-fields branch April 2, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend java Pull requests that update Java code tests Including test files, or tests related like configuration.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants