Skip to content
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

simple selection query implementation #81

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

NathanQingyangXu
Copy link
Contributor

https://jira.mongodb.org/browse/HIBERNATE-62

This PR supports all the comparison operators at https://www.mongodb.com/docs/manual/reference/operator/query/#query-and-projection-operators except for $nin which has no Hibernate counterpart.

It also supports all the logical operators at https://www.mongodb.com/docs/manual/reference/operator/query/#query-and-projection-operators except for $nor which has no Hibernate counterpart.

Comparison with missing or null field is out of scope of this PR.

@NathanQingyangXu NathanQingyangXu requested a review from jyemin April 3, 2025 00:52
@katcharov katcharov requested review from vbabanin and removed request for jyemin April 3, 2025 13:35
Comment on lines 90 to 96
void testComparisonByGTE(SessionFactoryScope scope) {
scope.inTransaction(session -> assertContactQueryResult(
session, "from Contact where age >= :age", q -> q.setParameter("age", 18), List.of(1, 2, 4, 5)));
}

@Test
void andFilterTest(SessionFactoryScope scope) {
Copy link
Contributor

Choose a reason for hiding this comment

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

(consistency) Let's start all tests with test.

Copy link
Member

Choose a reason for hiding this comment

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

We are already not doing that in the codebase. But if we are to come up with a rule, maybe, given that "test" in the name is noise that makes names longer, we consider the style where "test" is never specified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

currently multiple testing method naming conventions have co-existed in the repo already. I didn't object to dropping "test" from method names in @stIncMale 's testing code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but I agree, in this PR, there is no reason not to change xxxTest to testxxx. Updated.

}

@Test
void testComparisonByLTE(SessionFactoryScope scope) {
Copy link
Contributor

Choose a reason for hiding this comment

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

(consistency) Acronyms should use init-upper case, as for example you did with SqlAstTranslatorFactory

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to testComparisonByLte, together with other similar changes.


@Test
void testComparisonByEQ(SessionFactoryScope scope) {
scope.inTransaction(session -> assertContactQueryResult(
Copy link
Contributor

Choose a reason for hiding this comment

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

(duplication) Each of these tests begins with scope.inTransaction(session -> assertContactQueryResult(. Can this logic moved into before and after?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it is assertion related logic so I think it should not be moved to life cycle methods.

}

@Test
void testFieldNonNumericLiteralValue(SessionFactoryScope scope) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is more than one non-numeric literal value type, and as it stands this test is essentially the same as testFieldStringLiteralValue below.

Let's test all the literal value types that are supported, each with its own test, e.g. testFieldBooleanLiteralValue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a separate testing class for query literal testing, and removed the duplicated ones from SimpleSelectQueryIntegrationTests.

@@ -161,6 +172,8 @@ abstract class AbstractMqlTranslator<T extends JdbcOperation> implements SqlAstT

private final Set<String> affectedTableNames = new HashSet<>();

private final Set<Predicate> subPredicatesInNegatedGroupedPredicate = new HashSet<>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's notable whenever we have to add a new field to this class in order to share state as we visit the HQL AST, as it creates opportunities for that state to get out of sync with reality. In this case, for example, I see code which adds to the set, and code that queries for a predicate in the set, but no code that removes anything from the set. I'm not sure, but it seems like there is a bug lurking in here somewhere.

Copy link
Contributor Author

@NathanQingyangXu NathanQingyangXu Apr 7, 2025

Choose a reason for hiding this comment

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

We don't need to remove it for such translator is one-time usage stuff. It is dynamically created for each translation input and once the translation is done, it is subject to garbage collecting. The transalation factory is singleton global object, but the various translators are purely dynamic stuff. It is perfectly as expected to store ad-hoc data sharable among various visitor methods without considering thread safety issue, for the translator is meant to be run sequentially.

If we want to help a little bit on garbage collecting, maybe we can do something like in Hibernate's clearup method below:

	protected void cleanup() {
		if ( lazySessionWrapperOptions != null ) {
			lazySessionWrapperOptions.cleanup();
			lazySessionWrapperOptions = null;
		}
		this.jdbcParameterBindings = null;
		this.lockOptions = null;
		this.limit = null;
		setOffsetParameter( null );
		setLimitParameter( null );
	}

so we could set the set to null, but do we need to do that? Seems early optimization for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another similar doubt is Hibernate often adopts lazy-initialization to save perf. So the pattern would be:

private Set<Predicate> subPredicatesInNegatedGroupedPredicate;

// create it first when it is used and it is null

I didn't do that for the same reason. Overkill?

Copy link
Collaborator

Choose a reason for hiding this comment

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

My concern is not at all related to performance, but rather correctness. Is it possible that with a more complicated query the subPredicatesInNegatedGroupedPredicate field will need to be used more than once to pass information up the stack? If so, it seems like it would need to be cleared after the value is transmitted, to prepare it to be used once again. We were very careful with that in our use of astVisitorValueHolder, so I wonder if some of the same concerns apply to this field.


private BsonTypeUtils() {}

public static BsonValue toBsonValue(@Nullable Object value) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we already know that this method will be used in more places than just AbstractMqlTranslator? If not, we can make it a private static method of AbstractMqlTranslator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this PR it is not meant to be reused so I moved it as private static method in AbstractMqlTranslator as you suggested.

@@ -47,9 +46,6 @@ public JdbcOperationQuerySelect translate(

logSqlAst(selectStatement);

if (jdbcParameterBindings != null) {
throw new FeatureNotSupportedException();
Copy link
Member

Choose a reason for hiding this comment

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

We did not start handling jdbcParameterBindings here, but we removed the check. Why is this correct, was the check placement was wrong originally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We start using it a lot in this PR, as follows:

@Test
    void testProjectOperation(SessionFactoryScope scope) {
        scope.inTransaction(session -> {
            var results = session.createSelectionQuery(
                            "select name, age from Contact where country = :country", Object[].class)
                    .setParameter("country", Country.CANADA.name())
                    .getResultList();
            assertThat(results)
                    .containsExactly(new Object[] {"Mary", 35}, new Object[] {"Dylan", 7}, new Object[] {"Lucy", 78});
        });
    }

the setParameter() will end up with non-null jdbcParametersBinding.

@@ -782,9 +816,6 @@ void checkQueryOptionsSupportability(QueryOptions queryOptions) {
&& !queryOptions.getLockOptions().isEmpty()) {
throw new FeatureNotSupportedException("'lockOptions' in QueryOptions not supported");
}
if (queryOptions.getComment() != null) {
throw new FeatureNotSupportedException("TO-DO-HIBERNATE-53 https://jira.mongodb.org/browse/HIBERNATE-53");
}
Copy link
Member

Choose a reason for hiding this comment

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

Why was this check removed?

Copy link
Contributor Author

@NathanQingyangXu NathanQingyangXu Apr 7, 2025

Choose a reason for hiding this comment

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

because from this PR when HQL is involved, the queryOptions.getComment() would be set to the HQL string, so the above statement had to be removed.

@NathanQingyangXu NathanQingyangXu requested a review from jyemin April 7, 2025 18:44
…at it works when field path shows up as left or right hand side; add testing case
# Conflicts:
#	src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java
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.

4 participants