Skip to content

Limit/Offset/Fetch MQL translation #94

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

Open
wants to merge 55 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e897146
sorting MQL translation
NathanQingyangXu Apr 3, 2025
6123f8f
change `Null Precedence` verbigage to `Nulls Precedence`
NathanQingyangXu Apr 23, 2025
422b98b
add ordinal sort field reference testing case
NathanQingyangXu Apr 24, 2025
0e628e0
removed sort key max num validation logic
NathanQingyangXu Apr 24, 2025
0868af3
add logic to throw exception when sort specification does not denote …
NathanQingyangXu Apr 24, 2025
db88430
remove duplicated sort key validation and testing
NathanQingyangXu Apr 28, 2025
9791755
improve testing code in minor details
NathanQingyangXu Apr 29, 2025
d9790db
make AstSortOrder implement AstNode
NathanQingyangXu Apr 29, 2025
606c537
resolve code review comments
NathanQingyangXu May 2, 2025
5260354
rename method names to `createMatchStage` and `createSortStage`
NathanQingyangXu May 2, 2025
852e86b
add unsupported testing case for case-insensitive sort spec
NathanQingyangXu May 5, 2025
651e267
refactor the tuple related code logic
NathanQingyangXu May 5, 2025
6d9d765
add default nullness precedence (from SF) handling logic and testing …
NathanQingyangXu May 5, 2025
cd05d3f
Merge branch 'main' into HIBERNATE-68-new
NathanQingyangXu May 5, 2025
abb26e4
resolve conflict with latest main
NathanQingyangXu May 5, 2025
280ded3
Update src/integrationTest/java/com/mongodb/hibernate/query/select/So…
NathanQingyangXu May 6, 2025
ea0da76
Update src/main/java/com/mongodb/hibernate/internal/translate/Abstrac…
NathanQingyangXu May 6, 2025
c188203
code changes per code review comments
NathanQingyangXu May 6, 2025
75412fa
code changes per code review comments
NathanQingyangXu May 6, 2025
9eab404
Merge remote-tracking branch 'origin/HIBERNATE-68-new' into HIBERNATE…
NathanQingyangXu May 6, 2025
7573ef2
avoid using internal AstSortOrder as EnumSource in SortingSelectQuery…
NathanQingyangXu May 6, 2025
5e4a6fd
Limit/Offset MQL translation
NathanQingyangXu Apr 24, 2025
5e8cd0a
Merge branch 'main' into HIBERNATE-70-new
NathanQingyangXu May 23, 2025
aa9357d
make TestDialect static inner class in LimitOffsetFetchClauseIntegrat…
NathanQingyangXu May 23, 2025
966d099
make TestDialect static inner class in LimitOffsetFetchClauseIntegrat…
NathanQingyangXu May 23, 2025
0a3a659
improve naming
NathanQingyangXu May 23, 2025
73d457c
improve LimitOffsetFetchClauseIntegrationTests
NathanQingyangXu May 23, 2025
fde3cf3
Merge remote-tracking branch 'origin/HIBERNATE-70-new' into HIBERNATE…
NathanQingyangXu May 23, 2025
b596f0d
revert back limit parameter renaming and optimization to make for eas…
NathanQingyangXu May 26, 2025
3b9ca8d
add query validation to QueryPlanCacheTests
NathanQingyangXu May 26, 2025
341826a
add more comments to explain some subtle details
NathanQingyangXu May 26, 2025
8ed3bbf
fine-tune javadoc for TranslatingCacheTestingDialect in LimitOffsetFe…
NathanQingyangXu May 26, 2025
b6845a0
Update src/main/java/com/mongodb/hibernate/internal/translate/SelectM…
NathanQingyangXu May 27, 2025
cf28b15
resolve Slava's code review comments
NathanQingyangXu May 27, 2025
4bf67cd
Merge branch 'main' into HIBERNATE-70-new
NathanQingyangXu May 28, 2025
06c39fd
merge in main branch
NathanQingyangXu May 28, 2025
65c6cdf
rename AstVisitorValueDescriptor.FIELD_VALUE to VALUE
NathanQingyangXu Jun 9, 2025
91c80aa
add literal parameter testing to LimitOffsetFetchClauseIntegrationTests
NathanQingyangXu Jun 9, 2025
0440a9f
Update src/main/java/com/mongodb/hibernate/internal/translate/SelectM…
NathanQingyangXu Jun 9, 2025
c2dbbc6
Merge remote-tracking branch 'origin/HIBERNATE-70-new' into HIBERNATE…
NathanQingyangXu Jun 9, 2025
f5e6d22
simplify AbstractMqlTranslator by removing unnecessary implementation…
NathanQingyangXu Jun 9, 2025
d30e506
revert back deletion of QueryOptions.getFetchSize() validation in Abs…
NathanQingyangXu Jun 9, 2025
81b6693
Update src/main/java/com/mongodb/hibernate/internal/translate/Abstrac…
NathanQingyangXu Jun 9, 2025
98847f0
Update src/main/java/com/mongodb/hibernate/internal/translate/Abstrac…
NathanQingyangXu Jun 9, 2025
a84da60
Merge remote-tracking branch 'origin/HIBERNATE-70-new' into HIBERNATE…
NathanQingyangXu Jun 9, 2025
e95cd00
Update src/main/java/com/mongodb/hibernate/internal/translate/Abstrac…
NathanQingyangXu Jun 9, 2025
e2fb34f
Update src/main/java/com/mongodb/hibernate/internal/translate/Abstrac…
NathanQingyangXu Jun 9, 2025
f4cc230
Merge remote-tracking branch 'origin/HIBERNATE-70-new' into HIBERNATE…
NathanQingyangXu Jun 9, 2025
01717ea
remove final usage on local variables
NathanQingyangXu Jun 9, 2025
f863533
remove capacity spec in new ArrayList<AstStage>(3) in AbstractMqlTran…
NathanQingyangXu Jun 9, 2025
7aa5ae8
add comments to createSkipLimitStages() in AbstractMqlTranslator
NathanQingyangXu Jun 9, 2025
e6cdf55
fix broken LimitOffsetFetchClauseIntegrationTests.QueryPlanCacheTests…
NathanQingyangXu Jun 9, 2025
4df77c8
improve code quality by grouping related stuff together per Valentin'…
NathanQingyangXu Jun 9, 2025
5528f86
throw fail() in AbstractMqlTranslator#getAffectedTableNames()
NathanQingyangXu Jun 10, 2025
c677897
cosmetic improvements (renaming, etc.)
NathanQingyangXu Jun 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import static com.mongodb.hibernate.internal.MongoAssertions.fail;

import com.mongodb.hibernate.internal.translate.mongoast.AstValue;
import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand;
import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstProjectStageSpecification;
import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstSortField;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
Expand All @@ -34,13 +33,15 @@
@SuppressWarnings("UnusedTypeParameter")
final class AstVisitorValueDescriptor<T> {

static final AstVisitorValueDescriptor<AstCommand> COLLECTION_MUTATION = new AstVisitorValueDescriptor<>();
static final AstVisitorValueDescriptor<AstCommand> COLLECTION_AGGREGATE = new AstVisitorValueDescriptor<>();
static final AstVisitorValueDescriptor<ModelMutationMqlTranslator.Result> MUTATION_RESULT =
new AstVisitorValueDescriptor<>();
static final AstVisitorValueDescriptor<SelectMqlTranslator.Result> SELECT_RESULT =
new AstVisitorValueDescriptor<>();

static final AstVisitorValueDescriptor<String> COLLECTION_NAME = new AstVisitorValueDescriptor<>();

static final AstVisitorValueDescriptor<String> FIELD_PATH = new AstVisitorValueDescriptor<>();
static final AstVisitorValueDescriptor<AstValue> FIELD_VALUE = new AstVisitorValueDescriptor<>();
static final AstVisitorValueDescriptor<AstValue> VALUE = new AstVisitorValueDescriptor<>();

static final AstVisitorValueDescriptor<List<AstProjectStageSpecification>> PROJECT_STAGE_SPECIFICATIONS =
new AstVisitorValueDescriptor<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@

package com.mongodb.hibernate.internal.translate;

import static com.mongodb.hibernate.internal.MongoAssertions.assertNotNull;
import static com.mongodb.hibernate.internal.MongoAssertions.assertNull;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_MUTATION;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MUTATION_RESULT;
import static java.util.Collections.emptyList;

import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.model.ast.TableMutation;
import org.hibernate.sql.model.internal.TableUpdateNoSet;
Expand All @@ -39,15 +44,38 @@ final class ModelMutationMqlTranslator<O extends JdbcMutationOperation> extends
@Override
public O translate(@Nullable JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) {
assertNull(jdbcParameterBindings);
checkQueryOptionsSupportability(queryOptions);
applyQueryOptions(queryOptions);

String mql;
Result result;
if ((TableMutation<?>) tableMutation instanceof TableUpdateNoSet) {
mql = "";
result = Result.empty();
} else {
var mutationCommand = acceptAndYield(tableMutation, COLLECTION_MUTATION);
mql = renderMongoAstNode(mutationCommand);
result = acceptAndYield(tableMutation, MUTATION_RESULT);
}
return result.createJdbcMutationOperation(tableMutation);
}

static final class Result {
private final @Nullable AstCommand command;

private final List<JdbcParameterBinder> parameterBinders;

private Result(@Nullable AstCommand command, List<JdbcParameterBinder> parameterBinders) {
this.command = command;
this.parameterBinders = parameterBinders;
}

static Result create(AstCommand command, List<JdbcParameterBinder> parameterBinders) {
return new Result(assertNotNull(command), parameterBinders);
}

private static Result empty() {
return new Result(null, emptyList());
}

private <O extends JdbcMutationOperation> O createJdbcMutationOperation(TableMutation<O> tableMutation) {
var mql = command == null ? "" : renderMongoAstNode(command);
return tableMutation.createMutationOperation(mql, parameterBinders);
}
return tableMutation.createMutationOperation(mql, getParameterBinders());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,33 @@

package com.mongodb.hibernate.internal.translate;

import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_AGGREGATE;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.SELECT_RESULT;
import static java.lang.Integer.MAX_VALUE;
import static java.util.Collections.emptyMap;
import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst;
import static org.hibernate.sql.exec.spi.JdbcLockStrategy.NONE;

import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand;
import java.util.List;
import java.util.Set;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducerProvider;
import org.jspecify.annotations.Nullable;

final class SelectMqlTranslator extends AbstractMqlTranslator<JdbcOperationQuerySelect> {

private final SelectStatement selectStatement;
private final JdbcValuesMappingProducerProvider jdbcValuesMappingProducerProvider;

SelectMqlTranslator(SessionFactoryImplementor sessionFactory, SelectStatement selectStatement) {
super(sessionFactory);
this.selectStatement = selectStatement;
jdbcValuesMappingProducerProvider =
sessionFactory.getServiceRegistry().requireService(JdbcValuesMappingProducerProvider.class);
}

@Override
Expand All @@ -47,16 +52,51 @@ public JdbcOperationQuerySelect translate(
logSqlAst(selectStatement);

checkJdbcParameterBindingsSupportability(jdbcParameterBindings);
checkQueryOptionsSupportability(queryOptions);
applyQueryOptions(queryOptions);

var aggregateCommand = acceptAndYield((Statement) selectStatement, COLLECTION_AGGREGATE);
var jdbcValuesMappingProducer =
jdbcValuesMappingProducerProvider.buildMappingProducer(selectStatement, getSessionFactory());
var result = acceptAndYield((Statement) selectStatement, SELECT_RESULT);
return result.createJdbcOperationQuerySelect(selectStatement, getSessionFactory());
}

static final class Result {
private final AstCommand command;
private final List<JdbcParameterBinder> parameterBinders;
private final Set<String> affectedTableNames;
private final @Nullable JdbcParameter offsetParameter;
private final @Nullable JdbcParameter limitParameter;

Result(
AstCommand command,
List<JdbcParameterBinder> parameterBinders,
Set<String> affectedTableNames,
@Nullable JdbcParameter offsetParameter,
@Nullable JdbcParameter limitParameter) {
this.command = command;
this.parameterBinders = parameterBinders;
this.affectedTableNames = affectedTableNames;
this.offsetParameter = offsetParameter;
this.limitParameter = limitParameter;
}

return new JdbcOperationQuerySelect(
renderMongoAstNode(aggregateCommand),
getParameterBinders(),
jdbcValuesMappingProducer,
getAffectedTableNames());
private JdbcOperationQuerySelect createJdbcOperationQuerySelect(
SelectStatement selectStatement, SessionFactoryImplementor sessionFactory) {
var jdbcValuesMappingProducerProvider =
sessionFactory.getServiceRegistry().requireService(JdbcValuesMappingProducerProvider.class);
var jdbcValuesMappingProducer =
jdbcValuesMappingProducerProvider.buildMappingProducer(selectStatement, sessionFactory);
return new JdbcOperationQuerySelect(
renderMongoAstNode(command),
parameterBinders,
jdbcValuesMappingProducer,
affectedTableNames,
0,
MAX_VALUE,
emptyMap(),
NONE,
// The following parameters are provided for query plan cache purposes.
// Not setting them could result in reusing the wrong query plan and subsequently the wrong MQL.
offsetParameter,
limitParameter);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2025-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;

import com.mongodb.hibernate.internal.translate.mongoast.AstValue;
import org.bson.BsonWriter;

public record AstLimitStage(AstValue value) implements AstStage {
@Override
public void render(BsonWriter writer) {
writer.writeStartDocument();
{
writer.writeName("$limit");
value.render(writer);
}
writer.writeEndDocument();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2025-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;

import com.mongodb.hibernate.internal.translate.mongoast.AstValue;
import org.bson.BsonWriter;

public record AstSkipStage(AstValue value) implements AstStage {
@Override
public void render(BsonWriter writer) {
writer.writeStartDocument();
{
writer.writeName("$skip");
value.render(writer);
}
writer.writeEndDocument();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ class AstVisitorValueDescriptorTests {

@Test
void testToString() {
assertEquals("COLLECTION_MUTATION", AstVisitorValueDescriptor.COLLECTION_MUTATION.toString());
assertEquals("MUTATION_RESULT", AstVisitorValueDescriptor.MUTATION_RESULT.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

package com.mongodb.hibernate.internal.translate;

import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_MUTATION;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.FIELD_VALUE;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MUTATION_RESULT;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.VALUE;
import static java.util.Collections.emptyList;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;

Expand Down Expand Up @@ -45,9 +46,9 @@ void beforeEach() {
void testSimpleUsage() {

var value = new AstLiteralValue(new BsonString("field_value"));
Runnable valueYielder = () -> astVisitorValueHolder.yield(FIELD_VALUE, value);
Runnable valueYielder = () -> astVisitorValueHolder.yield(VALUE, value);

var valueGotten = astVisitorValueHolder.execute(FIELD_VALUE, valueYielder);
var valueGotten = astVisitorValueHolder.execute(VALUE, valueYielder);

assertSame(value, valueGotten);
}
Expand All @@ -57,42 +58,44 @@ void testRecursiveUsage() {

Runnable tableInserter = () -> {
Runnable fieldValueYielder = () -> {
astVisitorValueHolder.yield(FIELD_VALUE, AstParameterMarker.INSTANCE);
astVisitorValueHolder.yield(VALUE, AstParameterMarker.INSTANCE);
};
var fieldValue = astVisitorValueHolder.execute(FIELD_VALUE, fieldValueYielder);
var fieldValue = astVisitorValueHolder.execute(VALUE, fieldValueYielder);
AstElement astElement = new AstElement("province", fieldValue);
astVisitorValueHolder.yield(
COLLECTION_MUTATION, new AstInsertCommand("city", new AstDocument(List.of(astElement))));
MUTATION_RESULT,
ModelMutationMqlTranslator.Result.create(
new AstInsertCommand("city", new AstDocument(List.of(astElement))), emptyList()));
};

astVisitorValueHolder.execute(COLLECTION_MUTATION, tableInserter);
astVisitorValueHolder.execute(MUTATION_RESULT, tableInserter);
}

@Test
@DisplayName("Exception is thrown when holder is not empty when setting value")
void testHolderNotEmptyWhenSetting() {

Runnable valueYielder = () -> {
astVisitorValueHolder.yield(FIELD_VALUE, new AstLiteralValue(new BsonString("value1")));
astVisitorValueHolder.yield(FIELD_VALUE, new AstLiteralValue(new BsonString("value2")));
astVisitorValueHolder.yield(VALUE, new AstLiteralValue(new BsonString("value1")));
astVisitorValueHolder.yield(VALUE, new AstLiteralValue(new BsonString("value2")));
};

assertThrows(Error.class, () -> astVisitorValueHolder.execute(FIELD_VALUE, valueYielder));
assertThrows(Error.class, () -> astVisitorValueHolder.execute(VALUE, valueYielder));
}

@Test
@DisplayName("Exception is thrown when holder is expecting a descriptor different from that of actual data")
void testHolderExpectingDifferentDescriptor() {

Runnable valueYielder =
() -> astVisitorValueHolder.yield(FIELD_VALUE, new AstLiteralValue(new BsonString("some_value")));
() -> astVisitorValueHolder.yield(VALUE, new AstLiteralValue(new BsonString("some_value")));

assertThrows(Error.class, () -> astVisitorValueHolder.execute(COLLECTION_MUTATION, valueYielder));
assertThrows(Error.class, () -> astVisitorValueHolder.execute(MUTATION_RESULT, valueYielder));
}

@Test
@DisplayName("Exception is thrown when no value is yielded")
void testHolderStillEmpty() {
assertThrows(Error.class, () -> astVisitorValueHolder.execute(FIELD_VALUE, () -> {}));
assertThrows(Error.class, () -> astVisitorValueHolder.execute(VALUE, () -> {}));
}
}
Loading