Skip to content

Conversation

stIncMale
Copy link
Member

@stIncMale stIncMale self-assigned this Aug 15, 2025
Copy link
Contributor

@NathanQingyangXu NathanQingyangXu left a comment

Choose a reason for hiding this comment

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

some random and trivial comments out of personal interest; no need to take seriously.

@@ -343,7 +342,7 @@ boolean isUsed() {
* <p>Note that only find expression is involved before HIBERNATE-74. TODO-HIBERNATE-74 delete this temporary method
*/
private static void checkComparatorNotComparingWithNullValues(BsonDocument document) {
var comparisonOperators = Set.of("$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in", "$nin");
var comparisonOperators = Set.of("$ne", "$gt", "$gte", "$lt", "$lte", "$in", "$nin");
Copy link
Member Author

Choose a reason for hiding this comment

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

I removed $eq from here, so that I could use nulls in FunctionForArrayIntegrationTests (for now this seems better than removing the protection completely). As a result of this change, I had to make the change to SimpleSelectQueryIntegrationTests.java.

@@ -693,7 +693,7 @@ void testComparisonBetweenParametersNotSupported() {
@Test
void testNullParameterNotSupported() {
assertSelectQueryFailure(
"from Contact where country = :country",
"from Contact where country != :country",
Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -0,0 +1 @@
junit.jupiter.params.displayname.default=[{index}]{displayName}[{argumentSetNameOrArgumentsWithNames}]
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that ParameterizedInvocationNameFormatter.DEFAULT_DISPLAY_NAME_PATTERN is [{index}] {argumentSetNameOrArgumentsWithNames}, i.e., it is missing {displayName} for some reason, which is why I had to change it.

@@ -0,0 +1 @@
junit.jupiter.params.displayname.default=[{index}]{displayName}[{argumentSetNameOrArgumentsWithNames}]
Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines -104 to -108
public static <T> T assertInstanceOf(@Nullable Object value, Class<? extends T> type) {
if (!type.isInstance(value)) {
public static <T> T assertInstanceOf(Object value, Class<? extends T> type) {
try {
return type.cast(value);
} catch (ClassCastException e) {
throw fail();
}
return type.cast(value);
Copy link
Member Author

Choose a reason for hiding this comment

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

type.isInstance was just duplicating the check that is anyway done by type.cast.

Comment on lines +140 to +143
/**
*
*
* <table>
Copy link
Member Author

Choose a reason for hiding this comment

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

Our auto-formatter produces this ridiculous formatting, but that's OK.

@stIncMale stIncMale marked this pull request as ready for review August 21, 2025 00:55
@stIncMale stIncMale requested a review from a team as a code owner August 21, 2025 00:55
This is done because before introduction of `AstArray` in this PR, `AstLiteralValue` name was off: other `AstValue` subtypes did not have the `Value` suffix.
Copy link
Contributor

@NathanQingyangXu NathanQingyangXu left a comment

Choose a reason for hiding this comment

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

I left some minor and cusory comments. I am not able to resolve previous dialogs for I am not official reviewer. Feel free to delete them.

I would also inspect the implication of index coverages to ensure our translation choice won't have perf issue (creating index for the array field and check whether the new MQL won't end up with COLLSCAN).

private static final String COLLECTION_NAME = "items";

@InjectMongoCollection(COLLECTION_NAME)
private static MongoCollection<BsonDocument> mongoCollection;
Copy link
Contributor

Choose a reason for hiding this comment

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

seems the above mongoCollection is never used, though it is harmless to leave it there, maybe puzzling code reader a little bit.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was incorrectly thinking that without @InjectMongoCollection, the collection is not going to be dropped. But we drop the whole DB each time, so @InjectMongoCollection is indeed not needed here.

Done in 7cbfc84.

assertThatThrownBy(shouldRaiseThrowable)
.isInstanceOf(IllegalArgumentException.class)
.hasCauseInstanceOf(FunctionArgumentException.class)
.hasMessageMatching(".*Parameter .* of function .* requires a singular value, but argument is plural");
Copy link
Contributor

Choose a reason for hiding this comment

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

super minor. a little inconsistency: Parameter .* is used here (more robust) whereas Parameter . is used elsewhere

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in 3db8f16.

@Nested
@ParameterizedClass
@ValueSource(booleans = {true, false})
class ArrayContains {
Copy link
Contributor

Choose a reason for hiding this comment

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

so it seems this ArrayContains focuses on testing non-null needle for both functions, and the other ArrayContainsNullable focuses on null needle for array_contains_nullable function alone.

The naming is a little bit confusing for it easily confused code reader that we are testing the two functions instead of needle's nullness. I would choose ArrayContainsFunctionWithNonnullNeedle and ArrayContainsFunctionWithNullNeedle instead.

Also, do we need to cover the missing testing combination for the following:

  • function is 'array_contains`
  • needle is null

Copy link
Member Author

@stIncMale stIncMale Aug 26, 2025

Choose a reason for hiding this comment

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

The naming is a little bit confusing

I think, the reason it is confusing is that I used "nullable" instead of "null". "array contains null" and "array includes null" seem clear and meaningful, so I renamed the test classes ArrayContainsNullable -> ArrayContainsNull, ArrayIncludesNullable -> ArrayIncludesNull.

Done in 3db8f16.


We do not miss the array_contains + "needle is null" (or array_includes + "needle contains null") combination because of the documentation states:

The result of the array_contains function is undefined when the second argument, the element to search, is null.

The result of the array_includes function is undefined when the second argument contains a null.

The Hibernate ORM user guide does not specify what "undefined" means, which must mean that the common meaning that came from C and C++ applies. That meaning (see https://en.cppreference.com/w/c/language/behavior.html and https://en.cppreference.com/w/cpp/language/ub.html) is: forbidden, and there is no requirement for an implementation to either help you to understand what the behavior may be, or mitigate the negative consequences, or help you to diagnose them. Thus, unless we want to tighten the aforementioned Hibernate ORM requirements and turn then into implementation-defined behaviors, which I think we don't, we do not need to test the scenario you mentioned.

@NathanQingyangXu
Copy link
Contributor

I left some minor and cusory comments. I am not able to resolve previous dialogs for I am not official reviewer. Feel free to delete them.

I would also inspect the implication of index coverages to ensure our translation choice won't have perf issue (creating index for the array field and check whether the new MQL won't end up with COLLSCAN).

I experimented locally and found the new MQL translation is index-friendly. Might be an idea to cover this important feature in integration testing though.
Given our product is only used in replica set mode and typically big data is involved, so the importance of whether our MQL translation is index-friendly can'te be overestimated, IMHO.

throw new FunctionArgumentException(format(
"Parameter %d of function '%s()' requires an array, but argument is a list",
needleParameterIndex, functionName));
}
Copy link
Contributor

@NathanQingyangXu NathanQingyangXu Aug 21, 2025

Choose a reason for hiding this comment

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

From my understanding, Hibernate ORM's parameter expanding is a generic separate mechanism so it doesn't matter the original parameter value type (list, set, or even Iterable). As long as it is multi-valued parameter value, they will all end up with SqlTuple AST model.

It is a handy Hibernate parameter usage feature and I think it seems natural to use for array include function's needle.

Did you compare Hibernate ORM existing behaviour (e.g. in PostgreSQL dialect or even h2 which supports array inherently)?

From my testing, the following demo succeeds without issue for h2:

@DomainModel(annotatedClasses = Demo.Book.class)
@SessionFactory
class Demo {

    @Test
    void testMultiValuedParameters(SessionFactoryScope scope) {
        scope.inTransaction(session -> {
            session.createSelectionQuery("from Book b where array_includes(b.tags, :needle)")
                    .setParameter("needle", List.of("classic", "essay"))
                    .getResultList();
        });
    }

    @Entity(name = "Book")
    @Table(name = "books")
    static class Book {
        @Id int id;
        String title;
        String[] tags;
    }
}

tbh, I would be surprised our product won't accept multi-valued parameter value as the needle of the array include function.
I think the array here aligns with our previous array implementation so it should work not restricted to Java array literally, inasmuch as we support any collection domain fields to map to array.

Copy link
Contributor

Choose a reason for hiding this comment

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

well, I found the above code won't work. Seems the current implementation of array_include in Hibernate ORM is not full-fledged and I guess your implementation is correct.

But the exception message might be misleading, for it is not necessarily of List. multi-valued parameter value might be more appropriate. You can test by setting different collection types to needle and all of them would trigger the same exception with same messages. Did some cursory look at Christian's commit, seems he didn't cover the scenario that needle in array_include could be set by parameter, :(.

Copy link
Member Author

Choose a reason for hiding this comment

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

I replaced "list" with "collection" in the error message in 2d5fe11.

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