-
Notifications
You must be signed in to change notification settings - Fork 123
Add backwards-compatible serialization for filtration #840
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
base: main
Are you sure you want to change the base?
Add backwards-compatible serialization for filtration #840
Conversation
### New Additions * **`FilterableAttribute`** is a flattened POJO implementation of the new [granular filtration feature](meilisearch/meilisearch#5163). It contains backwards compatibility with legacy data. * **`GsonFilterableAttributeSerializer`** allows for the serialization/deserialization of these POJOs from legacy primitive string filter attributes, as well as the persistence of new single-string attributes with default settings in legacy format. ### Changes * **`GsonJsonHandler`** registers our new serializer. * **`Index`**: `getFilterableAttributesSettings` is updated to accommodate the new POJO. Backwards compatible `legacyGetFilterableAttributesSettings` which returns the traditional `String[]` attribute configuration has been added as well. `updateFilterableAttributes` has been updated to accept `Object[]`, as polymorphic backwards compatibility which can accept `null` values, requires no ambiguity; as we cannot do a polymorphic method, this seemed the best fit. * **`Settings`**: `filterableAttributes` was updated to now be of type `FilterableAttribute[]`. * **`SettingsHandler`**: `updateFilterableAttributesSettings` now accepts a `FilterableAttribute[]` object. `getFilterableAttributesSettings` now returns one likewise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new FilterableAttribute POJO along with a backwards-compatible Gson serializer/deserializer and updates to Settings and Index to support the new filtration format while preserving legacy functionality. Key changes include:
- Adding new serialization/deserialization logic for FilterableAttribute in GsonFilterableAttributeSerializer and its tests.
- Updating Settings and Index classes to handle FilterableAttribute arrays and provide legacy support.
- Modifying integration tests and code samples to work with the new filtration attributes.
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
File | Description |
---|---|
src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java | Unit tests for verifying legacy and new serialization behaviors. |
src/test/java/com/meilisearch/integration/TasksTest.java | Integration tests now print debug information for task queries. |
src/test/java/com/meilisearch/integration/SettingsTest.java | Tests updated to use legacy methods and new methods for filterable attributes settings. |
src/test/java/com/meilisearch/integration/FilterableAttributeTest.java | Unit tests for the FilterableAttribute POJO constructors and validations. |
src/main/java/com/meilisearch/sdk/model/Settings.java | Updated settings to use FilterableAttribute[] and added a helper to convert legacy strings. |
src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java | New POJO implementation for filtration settings with validation for geo patterns. |
src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java | Registers the new serializer for FilterableAttribute. |
src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java | Implements Gson serialization and deserialization for FilterableAttribute. |
src/main/java/com/meilisearch/sdk/SettingsHandler.java | Modified to support FilterableAttribute in get/update/reset settings. |
src/main/java/com/meilisearch/sdk/Index.java | Updated to provide both legacy and new methods for filterable attributes settings. |
.code-samples.meilisearch.yaml | Updated samples to demonstrate the new filterable attributes setup. |
Comments suppressed due to low confidence (3)
.code-samples.meilisearch.yaml:233
- There is an inconsistency with class and variable names – use 'FilterableAttribute' instead of 'FilterableAttributes' and ensure that the correct variable (e.g., 'filtersTypes') is passed in the constructor.
settings.setFilterableAttributes(new FilterableAttributes[] {new FilterableAttributes("genres"), new FilterableAttributes(new String[]{"director"}, true, filters)});
src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java:98
- The testDoubleEncoding: 'array' already holds the encoded string value, so re-encoding it may lead to incorrect output. Compare 'array' directly with expectedOutput to ensure correct behavior.
assertEquals(expectedOutput, handler.encode(array));
src/test/java/com/meilisearch/integration/TasksTest.java:145
- [nitpick] Remove the debugging print statements; they are likely leftover from development and are not needed in committed integration tests.
System.out.println("Expected from: " + from);
Co-authored-by: Laurent Cazanove <[email protected]>
…butes-Methods
Hello! Thank you for your PR @Noah-Mack-01! ❤️ If I understand correctly, this PR introduces breaking changes, because people need to use the Also, is there a way to avoid breaking change? Make the new methods to accept both of format (array of string and array of object)? I would love having the review of @brunoocasali before merging that |
Hi @curquiza !! Thinking through this now. Its unfortunate we're working off such an old JDK, since Java 15's All update methods are generic, such as the following:
However I can't think of a way to guarantee that the I suppose the answer would be to maintain the legacy implementation's original nomenclature |
Priority was placed on full backwards compatibility. Since two methods in the same java class cannot have the same name and input parameters (the compiler does not consider output class) the legacy feature will retain its same `getFilterableAttributesSettings` name and the new, full config getter will instead be called `getFullFilterableAttributesSettings[]`.
…erable-Attributes-Methods' into enhancement/meilisearch#838-Filterable-Attributes-Methods
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughThe changes introduce support for the new, structured Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Index
participant SettingsHandler
participant GsonJsonHandler
User->>Index: updateFilterableAttributesSettings(attributes)
Index->>Index: Determine attribute type (String[] or FilterableAttribute[])
Index->>SettingsHandler: updateFilterableAttributesSettings(uid, FilterableAttribute[])
SettingsHandler->>GsonJsonHandler: Serialize FilterableAttribute[]
GsonJsonHandler-->>SettingsHandler: JSON payload
SettingsHandler->>Meilisearch API: PUT /settings/filterable-attributes
Meilisearch API-->>SettingsHandler: TaskInfo
SettingsHandler-->>Index: TaskInfo
Index-->>User: TaskInfo
User->>Index: getFullFilterableAttributesSettings()
Index->>SettingsHandler: getFilterableAttributesSettings(uid)
SettingsHandler->>Meilisearch API: GET /settings/filterable-attributes
Meilisearch API-->>SettingsHandler: JSON response
SettingsHandler->>GsonJsonHandler: Deserialize JSON to FilterableAttribute[]
GsonJsonHandler-->>SettingsHandler: FilterableAttribute[]
SettingsHandler-->>Index: FilterableAttribute[]
Index-->>User: FilterableAttribute[]
Assessment against linked issues
Poem
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🔭 Outside diff range comments (1)
src/test/java/com/meilisearch/integration/SettingsTest.java (1)
774-776
:⚠️ Potential issueWrong reset method called – test is exercising the wrong API
In the sortable attributes test you reset filterable attributes
instead of sortable ones, so the assertions are not validating what
the test name describes.- index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); - String[] filterableAttributesAfterReset = index.getFilterableAttributesSettings(); + index.waitForTask(index.resetSortableAttributesSettings().getTaskUid()); + String[] sortableAttributesAfterReset = index.getSortableAttributesSettings();Update the variable names accordingly.
🧹 Nitpick comments (7)
src/test/java/com/meilisearch/integration/TasksTest.java (1)
145-147
: Consider removing debugging print statements.These print statements appear to be added for debugging purposes during development and don't provide value in automated test execution. It's generally best practice to remove debug outputs before submitting a PR, as they can create noise in test logs.
- System.out.println("Expected from: " + from); - System.out.println("Actual from: " + result.getFrom()); - System.out.println("Tasks returned: " + Arrays.toString(result.getResults()));.code-samples.meilisearch.yaml (1)
230-233
: Updates example to demonstrate structured filterable attributesThe example now shows how to use the new FilterableAttribute class with filter types.
However, there's a small issue in the code:
- settings.setFilterableAttributes(new FilterableAttribute[] {new FilterableAttribute("genres"), new FilterableAttribute(new String[]{"director"}, true, filters)}); + settings.setFilterableAttributes(new FilterableAttribute[] {new FilterableAttribute("genres"), new FilterableAttribute(new String[]{"director"}, true, filtersTypes)});The variable is defined as
filtersTypes
on line 231 but used asfilters
on line 233.src/main/java/com/meilisearch/sdk/Index.java (1)
750-765
: Inefficient & duplicate handler call – simplify stream processing
getFilterableAttributesSettings()
invokessettingsHandler.getFilterableAttributesSettings
twice and uses an over-complicatedreduce
to flatten patterns. The extra call wastes an HTTP round-trip, and thereduce
allocates a new list at every accumulation step.A leaner, single-pass approach is both clearer and faster:
-FilterableAttribute[] attributes = - this.settingsHandler.getFilterableAttributesSettings(this.uid); -return Arrays.stream(this.settingsHandler.getFilterableAttributesSettings(this.uid)) - .reduce( - new ArrayList<String>(), - (list, next) -> { - list.addAll(Arrays.asList(next.getPatterns())); - return list; - }, - (a, b) -> { - a.addAll(b); - return a; - }) - .toArray(new String[0]); +FilterableAttribute[] attributes = + this.settingsHandler.getFilterableAttributesSettings(this.uid); +return Arrays.stream(attributes) + .flatMap(attr -> Arrays.stream(attr.getPatterns())) + .toArray(String[]::new);src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java (1)
59-70
: Error aggregation prints “null” entries – improve message building
Arrays.toString(errors)
will include the string"null"
for slots
without an error, resulting in confusing messages such as
[null, Equality filter cannot be null..., null]
.A cleaner JDK-8-compatible build:
-String[] errors = new String[3]; -... -for (String error : errors) - if (error != null) - throw new RuntimeException( - "Invalid filter for geo pattern: " + Arrays.toString(errors)); +List<String> errors = new ArrayList<>(); +... +if (!errors.isEmpty()) { + throw new RuntimeException( + "Invalid filter for geo pattern: " + String.join("; ", errors)); +}src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java (2)
15-17
: Remove unused field to avoid confusionThe
serializer
instance is declared but never assigned/used.
Keeping dead fields dilutes readability and can mislead maintainers about the test’s intent.- GsonFilterableAttributeSerializer serializer; - GsonJsonHandler handler; + GsonJsonHandler handler;
152-164
: Potential NPE and asymmetric filter comparison
assertDeserializedOutputsEquals
iterates only overa.getFilter().keySet()
.
If either side contains additional keys, orgetFilter()
returnsnull
, the method can throw or silently miss mismatches.- for (String key : a.getFilter().keySet()) { - assertEquals(a.getFilter().get(key), b.getFilter().get(key)); - } + Map<String, Boolean> filtersA = Optional.ofNullable(a.getFilter()).orElse(Map.of()); + Map<String, Boolean> filtersB = Optional.ofNullable(b.getFilter()).orElse(Map.of()); + + assertEquals(filtersA.size(), filtersB.size()); + for (Map.Entry<String, Boolean> entry : filtersA.entrySet()) { + assertEquals(entry.getValue(), filtersB.get(entry.getKey())); + }src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java (1)
68-70
: Exception message building hides root causesUsing
String.join("\n", Arrays.toString(exceptions.toArray()))
collapses the list into a single string (the delimiter is ignored) and prints theException.toString()
rather than the message.- throw new JsonParseException(String.join("\n", Arrays.toString(exceptions.toArray()))); + String message = exceptions.stream() + .map(Throwable::getMessage) + .collect(Collectors.joining("\n")); + throw new JsonParseException(message);Remember to
import java.util.stream.Collectors;
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
.code-samples.meilisearch.yaml
(1 hunks)src/main/java/com/meilisearch/sdk/Index.java
(3 hunks)src/main/java/com/meilisearch/sdk/SettingsHandler.java
(3 hunks)src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java
(1 hunks)src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java
(2 hunks)src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java
(1 hunks)src/main/java/com/meilisearch/sdk/model/Settings.java
(2 hunks)src/test/java/com/meilisearch/integration/FilterableAttributeTest.java
(1 hunks)src/test/java/com/meilisearch/integration/SettingsTest.java
(2 hunks)src/test/java/com/meilisearch/integration/TasksTest.java
(1 hunks)src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java (1)
src/main/java/com/meilisearch/sdk/model/Settings.java (1)
Getter
(13-44)
🔇 Additional comments (9)
src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java (2)
10-10
: Adds required import for new classThis import is necessary for the registration of the FilterableAttribute class with Gson.
20-21
: Adds custom type adapter for the new FilterableAttribute classThe registration of GsonFilterableAttributeSerializer enables the JSON handler to support both legacy string format and the new object format for filterable attributes.
src/main/java/com/meilisearch/sdk/model/Settings.java (2)
20-20
: Updates field type to support new structured filterable attributesChanging from
String[]
toFilterableAttribute[]
enables storing richer metadata associated with filterable attributes.
38-43
: Adds backward-compatible setter methodThis method provides backward compatibility by converting simple strings to FilterableAttribute objects, allowing existing code to continue working without changes.
src/main/java/com/meilisearch/sdk/SettingsHandler.java (2)
5-5
: Optimizes imports using wildcardReplaced individual model class imports with a wildcard import, which is cleaner and easier to maintain as new model classes are added.
316-320
:✅ Verification successful
Updates method return type for filterable attributes
Changed the return type from
String[]
toFilterableAttribute[]
to support the new structured format. This is a breaking change at this level, but according to the PR description, backward compatibility is maintained at the Index class level with legacy methods.Let's verify the backward compatibility handling in the Index class:
🏁 Script executed:
#!/bin/bash # Find the backward-compatible methods in Index class rg "legacyGetFilterableAttributesSettings" --type javaLength of output: 54
To confirm how backward compatibility is exposed, let’s locate the
Index
class and search for any methods handling “filterable” attributes:
🏁 Script executed:
#!/bin/bash # 1. Find the Index class file fd Index.java # 2. Search for any filterable-related methods across Java sources rg -n "filterable" --type javaLength of output: 7009
Let’s inspect how
getFilterableAttributesSettings
is implemented inIndex.java
to confirm backward compatibility:
🏁 Script executed:
#!/bin/bash # Show the getFilterableAttributesSettings methods with context rg -n "getFilterableAttributesSettings" -C3 src/main/java/com/meilisearch/sdk/Index.javaLength of output: 1602
Backward compatibility for filterable attributes methods confirmed
- SettingsHandler.getFilterableAttributesSettings now returns FilterableAttribute[]
- Index#getFullFilterableAttributesSettings() exposes the new FilterableAttribute[] return type
- Index#getFilterableAttributesSettings() remains as String[] by converting the structured response
No further changes needed.
src/test/java/com/meilisearch/integration/SettingsTest.java (1)
710-715
: Logical mismatch: assertions after reset still use new patternsAfter a successful reset we expect the attributes to match the initial
state, not the interim new list. Re-checking against
newFilterableAttributes
may lead to false positives (loops simply
don’t execute when the array is empty).Please verify that the post-reset values equal
initialFilterableAttributes
instead.src/test/java/com/meilisearch/integration/FilterableAttributeTest.java (1)
12-19
: Good coverage of default constructor behaviourThe test accurately checks patterns, facetSearch and default filters for
a single-pattern attribute. Nice work!src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java (1)
137-140
:JsonArray#asList()
is available only from Gson 2.10Projects pinned to older Gson versions (quite common in Android stacks) will not compile.
Consider a version-agnostic fallback:JsonArray array = object.getAsJsonArray("attributePatterns"); patterns = StreamSupport.stream(array.spliterator(), false) .map(JsonElement::getAsString) .toArray(String[]::new);
TaskInfo updateFilterableAttributesSettings( | ||
String uid, FilterableAttribute[] filterableAttributes) throws MeilisearchException { | ||
return httpClient.put( | ||
settingsPath(uid).addSubroute("filterable-attributes").getURL(), | ||
filterableAttributes == null | ||
? httpClient.jsonHandler.encode(filterableAttributes) | ||
: filterableAttributes, | ||
httpClient.jsonHandler.encode(filterableAttributes), | ||
TaskInfo.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Updates update method parameter type and simplifies encoding
Changed the parameter type from String[]
to FilterableAttribute[]
and simplified the JSON encoding logic. The removal of the null check is safe because encode()
already handles null values.
Let's verify how the Update method is called:
🏁 Script executed:
#!/bin/bash
# Find calls to updateFilterableAttributesSettings
rg "updateFilterableAttributesSettings" --type java
Length of output: 2055
Breaking Change: UpdateFilterableAttributesSettings Signature Now Requires FilterableAttribute[]
The parameter type was changed from String[]
to FilterableAttribute[]
and the null‐check was removed (since encode()
already handles null). This is a breaking change: all existing call sites passing String[]
or relying on the generic overload will fail to compile.
Please update the following locations:
-
src/main/java/com/meilisearch/sdk/Index.java
The generic methodpublic <O> TaskInfo updateFilterableAttributesSettings(O[] filterableAttributes)
now passes
O[]
directly toSettingsHandler#updateFilterableAttributesSettings(String, FilterableAttribute[])
. You must either:- Add an overload taking
String... filterableAttributes
that converts eachString
to aFilterableAttribute
; or - Change all call sites to use
FilterableAttribute
enums directly.
- Add an overload taking
-
src/test/java/com/meilisearch/integration/SettingsTest.java
-
src/test/java/com/meilisearch/integration/DocumentsTest.java
Update calls likeindex.updateFilterableAttributesSettings(new String[] {"genres"}); index.updateFilterableAttributesSettings(null);
to use
FilterableAttribute[]
(or your newString...
overload).
No change needed in SettingsHandler.java
beyond the already applied signature update.
private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) { | ||
return this.settingsHandler.updateFilterableAttributesSettings( | ||
this.uid, filterableAttributes); | ||
this.uid, | ||
Arrays.stream(filterableAttributes) | ||
.map(FilterableAttribute::new) | ||
.toArray(FilterableAttribute[]::new)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked exception is swallowed – compilation will fail
updateFilterableAttributeSettingsLegacy
calls
settingsHandler.updateFilterableAttributesSettings
, which declares
throws MeilisearchException
.
Because this private method does not declare or catch the checked
exception, the file will not compile.
-private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) {
+private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes)
+ throws MeilisearchException {
Also adjust the single call-site (line 799/800) – the surrounding method
already declares throws MeilisearchException
, so no further change is
needed.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) { | |
return this.settingsHandler.updateFilterableAttributesSettings( | |
this.uid, filterableAttributes); | |
this.uid, | |
Arrays.stream(filterableAttributes) | |
.map(FilterableAttribute::new) | |
.toArray(FilterableAttribute[]::new)); | |
} | |
private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) | |
throws MeilisearchException { | |
return this.settingsHandler.updateFilterableAttributesSettings( | |
this.uid, | |
Arrays.stream(filterableAttributes) | |
.map(FilterableAttribute::new) | |
.toArray(FilterableAttribute[]::new)); | |
} |
String[] patterns, boolean facetSearch, Map<String, Boolean> filters) { | ||
if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null"); | ||
boolean patternHasGeo = false; | ||
for (String s : patterns) | ||
if (_GEO.equals(s)) { | ||
patternHasGeo = true; | ||
break; | ||
} | ||
if (patternHasGeo) checkGeoValidation(facetSearch, filters); | ||
this.patterns = patterns; | ||
this.facetSearch = facetSearch; | ||
this.filter = filters; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Potential NPE & defensive-copy omissions in full constructor
filters
may benull
; subsequentcontainsKey
calls would throw a
NullPointerException
, breaking the geo-validation path.patterns
is stored directly; callers can mutate the passed array
after construction, violating encapsulation.
if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null");
+if (filters == null) throw new IllegalArgumentException("Filters cannot be null");
...
-this.patterns = patterns;
+this.patterns = Arrays.copyOf(patterns, patterns.length); // defensive copy
-this.filter = filters;
+this.filter = new HashMap<>(filters); // defensive copy
Consider wrapping filter
with Collections.unmodifiableMap
to keep
the instance immutable.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
String[] patterns, boolean facetSearch, Map<String, Boolean> filters) { | |
if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null"); | |
boolean patternHasGeo = false; | |
for (String s : patterns) | |
if (_GEO.equals(s)) { | |
patternHasGeo = true; | |
break; | |
} | |
if (patternHasGeo) checkGeoValidation(facetSearch, filters); | |
this.patterns = patterns; | |
this.facetSearch = facetSearch; | |
this.filter = filters; | |
} | |
public FilterableAttribute(String[] patterns, boolean facetSearch, Map<String, Boolean> filters) { | |
if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null"); | |
if (filters == null) throw new IllegalArgumentException("Filters cannot be null"); | |
boolean patternHasGeo = false; | |
for (String s : patterns) | |
if (_GEO.equals(s)) { | |
patternHasGeo = true; | |
break; | |
} | |
if (patternHasGeo) checkGeoValidation(facetSearch, filters); | |
this.patterns = Arrays.copyOf(patterns, patterns.length); // defensive copy | |
this.facetSearch = facetSearch; | |
this.filter = new HashMap<>(filters); // defensive copy | |
} |
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]"; | ||
String array = handler.encode(new FilterableAttribute[] {input, input2}); | ||
assertEquals(expectedOutput, handler.encode(array)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Deterministic JSON comparison
Even after the above fix, the test performs a string comparison.
HashMap
does not guarantee key order, so the produced JSON property order can vary between JDKs, causing nondeterministic failures.
Prefer structural comparison:
JsonElement expected = JsonParser.parseString(expectedOutput);
JsonElement actual = JsonParser.parseString(result);
assertEquals(expected, actual);
public void testNewDeserializationOutputArray() { | ||
String input = "{attributePatterns:[\"attribute1\"]}"; | ||
FilterableAttribute expectedOutput = new FilterableAttribute("attribute1"); | ||
assertDeserializedOutputsEquals( | ||
handler.decode(input, FilterableAttribute.class), expectedOutput); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Test input is not valid JSON – rely on proper JSON to reduce flakiness
{attributePatterns:["attribute1"]}
omits the mandatory double quotes around property names required by strict JSON.
Unless GsonJsonHandler
enables lenient parsing, this will break; even with lenient mode the example obscures the real-world payload format and makes the test harder to follow.
- String input = "{attributePatterns:[\"attribute1\"]}";
+ String input = "{\"attributePatterns\":[\"attribute1\"]}";
Please update all raw JSON literals in the file to valid JSON for clarity and forward-compatibility.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public void testNewDeserializationOutputArray() { | |
String input = "{attributePatterns:[\"attribute1\"]}"; | |
FilterableAttribute expectedOutput = new FilterableAttribute("attribute1"); | |
assertDeserializedOutputsEquals( | |
handler.decode(input, FilterableAttribute.class), expectedOutput); | |
} | |
public void testNewDeserializationOutputArray() { | |
String input = "{\"attributePatterns\":[\"attribute1\"]}"; | |
FilterableAttribute expectedOutput = new FilterableAttribute("attribute1"); | |
assertDeserializedOutputsEquals( | |
handler.decode(input, FilterableAttribute.class), expectedOutput); | |
} |
public void testMixedSerializationWithFeatures() { | ||
HashMap<String, Boolean> filters = new HashMap<>(); | ||
filters.put("equality", false); | ||
filters.put("comparison", true); | ||
FilterableAttribute input = | ||
new FilterableAttribute(new String[] {"attribute1", "attribute2"}, true, filters); | ||
FilterableAttribute input2 = new FilterableAttribute("attribute3"); | ||
String expectedOutput = | ||
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]"; | ||
String array = handler.encode(new FilterableAttribute[] {input, input2}); | ||
assertEquals(expectedOutput, handler.encode(array)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assertion encodes the JSON twice – the test will always fail
handler.encode(array)
is called on a String
, causing the string to be wrapped in quotes and escaped, so the comparison with the raw expectedOutput
never matches.
- String array = handler.encode(new FilterableAttribute[] {input, input2});
- assertEquals(expectedOutput, handler.encode(array));
+ String result = handler.encode(new FilterableAttribute[] {input, input2});
+ assertEquals(expectedOutput, result);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public void testMixedSerializationWithFeatures() { | |
HashMap<String, Boolean> filters = new HashMap<>(); | |
filters.put("equality", false); | |
filters.put("comparison", true); | |
FilterableAttribute input = | |
new FilterableAttribute(new String[] {"attribute1", "attribute2"}, true, filters); | |
FilterableAttribute input2 = new FilterableAttribute("attribute3"); | |
String expectedOutput = | |
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]"; | |
String array = handler.encode(new FilterableAttribute[] {input, input2}); | |
assertEquals(expectedOutput, handler.encode(array)); | |
} | |
public void testMixedSerializationWithFeatures() { | |
HashMap<String, Boolean> filters = new HashMap<>(); | |
filters.put("equality", false); | |
filters.put("comparison", true); | |
FilterableAttribute input = | |
new FilterableAttribute(new String[] {"attribute1", "attribute2"}, true, filters); | |
FilterableAttribute input2 = new FilterableAttribute("attribute3"); | |
String expectedOutput = | |
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]"; | |
- String array = handler.encode(new FilterableAttribute[] {input, input2}); | |
- assertEquals(expectedOutput, handler.encode(array)); | |
+ String result = handler.encode(new FilterableAttribute[] {input, input2}); | |
+ assertEquals(expectedOutput, result); | |
} |
private boolean canBeString(FilterableAttribute attribute) { | ||
if (attribute == null) return false; | ||
Map<String, Boolean> filters = attribute.getFilter(); | ||
if (filters == null) filters = new HashMap<>(); | ||
return (attribute.getPatterns() != null | ||
&& attribute.getPatterns().length == 1 | ||
&& (attribute.getFacetSearch() == null || !attribute.getFacetSearch()) | ||
&& (filters.containsKey("equality") && filters.get("equality")) | ||
&& (!filters.containsKey("comparison") || !filters.get("comparison"))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
canBeString
incorrectly rejects default filter settings
If attribute.getFilter()
is null
, the current logic treats equality as disabled (because the empty map lacks the key) and thus serialises the attribute as an object instead of the compact legacy string.
Refine the checks so that a missing key inherits the default (equality == true
, comparison == false
):
- return (attribute.getPatterns() != null
- && attribute.getPatterns().length == 1
- && (attribute.getFacetSearch() == null || !attribute.getFacetSearch())
- && (filters.containsKey("equality") && filters.get("equality"))
- && (!filters.containsKey("comparison") || !filters.get("comparison")));
+ boolean equalityAllowed = !filters.containsKey("equality") || filters.get("equality");
+ boolean comparisonAllowed = filters.getOrDefault("comparison", false);
+
+ return attribute.getPatterns() != null
+ && attribute.getPatterns().length == 1
+ && (attribute.getFacetSearch() == null || !attribute.getFacetSearch())
+ && equalityAllowed
+ && !comparisonAllowed;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private boolean canBeString(FilterableAttribute attribute) { | |
if (attribute == null) return false; | |
Map<String, Boolean> filters = attribute.getFilter(); | |
if (filters == null) filters = new HashMap<>(); | |
return (attribute.getPatterns() != null | |
&& attribute.getPatterns().length == 1 | |
&& (attribute.getFacetSearch() == null || !attribute.getFacetSearch()) | |
&& (filters.containsKey("equality") && filters.get("equality")) | |
&& (!filters.containsKey("comparison") || !filters.get("comparison"))); | |
} | |
private boolean canBeString(FilterableAttribute attribute) { | |
if (attribute == null) return false; | |
Map<String, Boolean> filters = attribute.getFilter(); | |
if (filters == null) filters = new HashMap<>(); | |
boolean equalityAllowed = !filters.containsKey("equality") || filters.get("equality"); | |
boolean comparisonAllowed = filters.getOrDefault("comparison", false); | |
return attribute.getPatterns() != null | |
&& attribute.getPatterns().length == 1 | |
&& (attribute.getFacetSearch() == null || !attribute.getFacetSearch()) | |
&& equalityAllowed | |
&& !comparisonAllowed; | |
} |
Pull Request - Add new FiltrationAttributes POJO and update implementation of Settings, Index.
Related issue
Fixes #838
What does this PR do?
New Additions
FilterableAttribute
is a flattened POJO implementation of the new granular filtration feature. It contains backwards compatibility with legacy data.GsonFilterableAttributeSerializer
allows for the serialization/deserialization of these POJOs from legacy primitive string filter attributes, as well as the persistence of new single-string attributes with default settings in legacy format.Changes
GsonJsonHandler
registers our new serializer.Index
:getFilterableAttributesSettings
is updated to accommodate the new POJO. Backwards compatiblelegacyGetFilterableAttributesSettings
which returns the traditionalString[]
attribute configuration has been added as well.updateFilterableAttributes
has been updated to acceptObject[]
, as polymorphic backwards compatibility which can acceptnull
values, requires no ambiguity; as we cannot do a polymorphic method, this seemed the best fit.Settings
:filterableAttributes
was updated to now be of typeFilterableAttribute[]
.SettingsHandler
:updateFilterableAttributesSettings
now accepts aFilterableAttribute[]
object.getFilterableAttributesSettings
now returns one likewise.PR checklist
Please check if your PR fulfills the following requirements:
Thank you so much for contributing to Meilisearch!
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation