Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6905515
Add projection/select support for JPA, MongoDB, Elasticsearch
Dec 5, 2025
6e7d2f2
Add unit tests for select functionality
Dec 5, 2025
4115d5f
Add SelectDeserializer for consistent Jackson deserialization
Dec 5, 2025
36282bd
Add SortDeserializer for consistent Jackson deserialization
Dec 5, 2025
8d8618f
Add additional tests to improve coverage
Dec 5, 2025
58f0235
Add additional tests to improve coverage
Dec 5, 2025
a926ad6
Add tests to improve coverage to 90%+ on all modules
Dec 6, 2025
e52a1c3
Add selectBy tests and SelectionSpecification for native JPA projections
Dec 6, 2025
dc406c2
Merge branch 'main' into feature/projection-safe
Dec 6, 2025
f1369dc
Add field-to-field comparison support
Dec 7, 2025
cf8a8b4
Fix field-to-field comparison tests for MongoDB null handling
Dec 7, 2025
53fe6a0
Add comprehensive documentation and tests for field-to-field comparison
Dec 7, 2025
53e86bd
Fix SelectTests for database-specific projection behaviors
Dec 7, 2025
7094197
Merge branch 'feature/projection-safe' into feature/field-to-field-co…
Dec 7, 2025
f9db0ed
Remove $ prefix from field references in parser, grammar, tests, and …
Dec 7, 2025
7ca13c3
Move selectByNative tests from DB-specific to generic test class
Dec 8, 2025
e26da7c
Merge branch 'feature/projection-safe' into feature/field-to-field-co…
Dec 8, 2025
10949ea
Relocate tests from SpringH2QuerityJpaImplTests to QuerityJpaImplTests
Dec 9, 2025
20e72d0
Merge branch 'feature/projection-safe' into feature/field-to-field-co…
Dec 9, 2025
1f277c2
Merge branch 'main' into feature/field-to-field-comparison
Dec 9, 2025
39bea15
optimize imports on QuerityGenericTestSuite.java
Dec 9, 2025
8d01dda
Delete .claude/settings.local.json
brunomendola Dec 9, 2025
78817d0
Remove commented line
brunomendola Dec 9, 2025
bad6741
Merge branch 'main' into feature/field-to-field-comparison
Dec 15, 2025
fc11f8e
Add unit tests for Elasticsearch, JPA, and MongoDB select functionali…
Dec 15, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package io.github.queritylib.querity.common.util;

import io.github.queritylib.querity.api.NativeSelectWrapper;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for SelectUtils focusing on type matching behavior with NativeSelectWrapper.
* These tests verify that the SelectUtils can correctly match implementation classes
* based on their constructor's generic parameter type.
*/
class SelectUtilsExtractRawTypeTests {

@Test
void givenImplementationWithGenericListParameter_whenGetSelectImplementation_thenMatchByListElementType() {
// This tests the ParameterizedType branch in extractRawType()
// The wrapper contains ArrayList (which implements List)
NativeSelectWrapper<List<String>> wrapper = NativeSelectWrapper.<List<String>>builder()
.nativeSelection(List.of("field1", "field2"))
.build();
Set<Class<? extends TestSelect>> implementationClasses = Set.of(ListBasedTestSelect.class);

Optional<TestSelect> result = SelectUtils.getSelectImplementation(implementationClasses, wrapper);

assertThat(result).isPresent();
assertThat(result.get()).isInstanceOf(ListBasedTestSelect.class);
assertThat(result.get().getPropertyNames()).containsExactly("field1", "field2");
}

@Test
void givenMultipleImplementationsWithDifferentTypes_whenGetSelectImplementation_thenSelectCorrectOne() {
NativeSelectWrapper<String> stringWrapper = NativeSelectWrapper.<String>builder()
.nativeSelection("testField")
.build();
NativeSelectWrapper<Integer> intWrapper = NativeSelectWrapper.<Integer>builder()
.nativeSelection(42)
.build();

Set<Class<? extends TestSelect>> implementationClasses = Set.of(
StringBasedTestSelect.class,
IntegerBasedTestSelect.class
);

Optional<TestSelect> stringResult = SelectUtils.getSelectImplementation(implementationClasses, stringWrapper);
Optional<TestSelect> intResult = SelectUtils.getSelectImplementation(implementationClasses, intWrapper);

assertThat(stringResult).isPresent();
assertThat(stringResult.get()).isInstanceOf(StringBasedTestSelect.class);
assertThat(stringResult.get().getPropertyNames()).containsExactly("testField");

assertThat(intResult).isPresent();
assertThat(intResult.get()).isInstanceOf(IntegerBasedTestSelect.class);
assertThat(intResult.get().getPropertyNames()).containsExactly("42");
}

@Test
void givenSubtypeOfExpectedType_whenGetSelectImplementation_thenMatchByInheritance() {
// StringBuilder extends CharSequence, so it should match a CharSequence-based implementation
NativeSelectWrapper<StringBuilder> wrapper = NativeSelectWrapper.<StringBuilder>builder()
.nativeSelection(new StringBuilder("dynamicField"))
.build();
Set<Class<? extends TestSelect>> implementationClasses = Set.of(CharSequenceBasedTestSelect.class);

Optional<TestSelect> result = SelectUtils.getSelectImplementation(implementationClasses, wrapper);

assertThat(result).isPresent();
assertThat(result.get()).isInstanceOf(CharSequenceBasedTestSelect.class);
assertThat(result.get().getPropertyNames()).containsExactly("dynamicField");
}

// Test interface
interface TestSelect {
List<String> getPropertyNames();
}

// Implementation accepting List<String> - tests ParameterizedType handling
public static class ListBasedTestSelect implements TestSelect {
private final NativeSelectWrapper<List<String>> wrapper;

public ListBasedTestSelect(NativeSelectWrapper<List<String>> wrapper) {
this.wrapper = wrapper;
}

@Override
public List<String> getPropertyNames() {
return wrapper.getNativeSelections().stream()
.flatMap(List::stream)
.toList();
}
}

// Implementation accepting String
public static class StringBasedTestSelect implements TestSelect {
private final NativeSelectWrapper<String> wrapper;

public StringBasedTestSelect(NativeSelectWrapper<String> wrapper) {
this.wrapper = wrapper;
}

@Override
public List<String> getPropertyNames() {
return wrapper.getNativeSelections();
}
}

// Implementation accepting Integer
public static class IntegerBasedTestSelect implements TestSelect {
private final NativeSelectWrapper<Integer> wrapper;

public IntegerBasedTestSelect(NativeSelectWrapper<Integer> wrapper) {
this.wrapper = wrapper;
}

@Override
public List<String> getPropertyNames() {
return wrapper.getNativeSelections().stream()
.map(Object::toString)
.toList();
}
}

// Implementation accepting CharSequence - tests inheritance matching
public static class CharSequenceBasedTestSelect implements TestSelect {
private final NativeSelectWrapper<? extends CharSequence> wrapper;

public CharSequenceBasedTestSelect(NativeSelectWrapper<? extends CharSequence> wrapper) {
this.wrapper = wrapper;
}

@Override
public List<String> getPropertyNames() {
return wrapper.getNativeSelections().stream()
.map(CharSequence::toString)
.toList();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.github.queritylib.querity.jpa;

import io.github.queritylib.querity.api.NativeSortWrapper;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.Metamodel;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static io.github.queritylib.querity.api.Querity.sortByNative;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

class JpaOrderSpecificationTests {

@Nested
class JpaOrderSpecificationSortWrapperTests {
@SuppressWarnings("unchecked")
@Test
void givenOrderSpecification_whenOf_thenReturnJpaOrderSpecificationSortWrapper() {
Order mockOrder = mock(Order.class);
OrderSpecification<Object> orderSpec = (root, cb) -> mockOrder;
NativeSortWrapper<OrderSpecification<?>> wrapper = sortByNative(orderSpec);

JpaSort jpaSort = JpaSort.of(wrapper);

assertThat(jpaSort).isInstanceOf(JpaOrderSpecificationSortWrapper.class);
}

@SuppressWarnings("unchecked")
@Test
void givenOrderSpecificationSortWrapper_whenToOrder_thenReturnOrderFromSpec() {
Order mockOrder = mock(Order.class);
OrderSpecification<Object> orderSpec = (root, cb) -> mockOrder;
NativeSortWrapper<OrderSpecification<?>> wrapper = sortByNative(orderSpec);
JpaOrderSpecificationSortWrapper sortWrapper = new JpaOrderSpecificationSortWrapper(wrapper);

Metamodel metamodel = mock(Metamodel.class);
Root<Object> root = mock(Root.class);
CriteriaBuilder cb = mock(CriteriaBuilder.class);

Order result = sortWrapper.toOrder(metamodel, root, cb);

assertThat(result).isSameAs(mockOrder);
}

@Test
void givenOrderSpecificationSortWrapper_whenGetNativeSort_thenReturnOrderSpecification() {
Order mockOrder = mock(Order.class);
OrderSpecification<Object> orderSpec = (root, cb) -> mockOrder;
NativeSortWrapper<OrderSpecification<?>> wrapper = sortByNative(orderSpec);
JpaOrderSpecificationSortWrapper sortWrapper = new JpaOrderSpecificationSortWrapper(wrapper);

assertThat(sortWrapper.getNativeSort()).isSameAs(orderSpec);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package io.github.queritylib.querity.jpa;

import io.github.queritylib.querity.api.NativeSelectWrapper;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.metamodel.Metamodel;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.List;

import static io.github.queritylib.querity.api.Querity.selectByNative;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class JpaSelectionSpecificationTests {

@Nested
class JpaSelectionSpecificationSelectWrapperTests {
@SuppressWarnings("unchecked")
@Test
void givenSelectionSpecificationWithoutAlias_whenToSelections_thenReturnSelectionsWithoutAlias() {
Selection<?> mockSelection = mock(Selection.class);
SelectionSpecification<Object> spec = (root, cb) -> mockSelection;
NativeSelectWrapper<SelectionSpecification<?>> wrapper = selectByNative(spec);
JpaSelectionSpecificationSelectWrapper selectWrapper = new JpaSelectionSpecificationSelectWrapper(wrapper);

Metamodel metamodel = mock(Metamodel.class);
Root<Object> root = mock(Root.class);
CriteriaQuery<?> cq = mock(CriteriaQuery.class);
CriteriaBuilder cb = mock(CriteriaBuilder.class);

List<Selection<?>> selections = selectWrapper.toSelections(metamodel, root, cq, cb);

assertThat(selections).hasSize(1);
assertThat(selections.get(0)).isSameAs(mockSelection);
}

@SuppressWarnings("unchecked")
@Test
void givenSelectionSpecificationWithAlias_whenToSelections_thenReturnSelectionsWithAlias() {
Selection<?> mockSelection = mock(Selection.class);
Selection<?> aliasedSelection = mock(Selection.class);
when(mockSelection.alias("myAlias")).thenReturn((Selection) aliasedSelection);

SelectionSpecification<Object> spec = AliasedSelectionSpecification.of(
(root, cb) -> mockSelection,
"myAlias"
);
NativeSelectWrapper<SelectionSpecification<?>> wrapper = selectByNative(spec);
JpaSelectionSpecificationSelectWrapper selectWrapper = new JpaSelectionSpecificationSelectWrapper(wrapper);

Metamodel metamodel = mock(Metamodel.class);
Root<Object> root = mock(Root.class);
CriteriaQuery<?> cq = mock(CriteriaQuery.class);
CriteriaBuilder cb = mock(CriteriaBuilder.class);

List<Selection<?>> selections = selectWrapper.toSelections(metamodel, root, cq, cb);

assertThat(selections).hasSize(1);
assertThat(selections.get(0)).isSameAs(aliasedSelection);
}

@Test
void givenSelectionSpecificationWithoutAlias_whenGetPropertyNames_thenReturnEmptyList() {
SelectionSpecification<Object> spec = (root, cb) -> root.get("id");
NativeSelectWrapper<SelectionSpecification<?>> wrapper = selectByNative(spec);
JpaSelectionSpecificationSelectWrapper selectWrapper = new JpaSelectionSpecificationSelectWrapper(wrapper);

List<String> propertyNames = selectWrapper.getPropertyNames();

assertThat(propertyNames).isEmpty();
}

@Test
void givenSelectionSpecificationWithAlias_whenGetPropertyNames_thenReturnAlias() {
SelectionSpecification<Object> spec = AliasedSelectionSpecification.of(
(root, cb) -> root.get("id"),
"customAlias"
);
NativeSelectWrapper<SelectionSpecification<?>> wrapper = selectByNative(spec);
JpaSelectionSpecificationSelectWrapper selectWrapper = new JpaSelectionSpecificationSelectWrapper(wrapper);

List<String> propertyNames = selectWrapper.getPropertyNames();

assertThat(propertyNames).containsExactly("customAlias");
}

@Test
void givenMultipleSelectionSpecifications_whenGetPropertyNames_thenReturnOnlyAliases() {
SelectionSpecification<Object> specWithAlias = AliasedSelectionSpecification.of(
(root, cb) -> root.get("id"),
"idAlias"
);
SelectionSpecification<Object> specWithoutAlias = (root, cb) -> root.get("name");
SelectionSpecification<Object> specWithAnotherAlias = AliasedSelectionSpecification.of(
(root, cb) -> root.get("email"),
"emailAlias"
);

NativeSelectWrapper<SelectionSpecification<?>> wrapper = selectByNative(
specWithAlias, specWithoutAlias, specWithAnotherAlias
);
JpaSelectionSpecificationSelectWrapper selectWrapper = new JpaSelectionSpecificationSelectWrapper(wrapper);

List<String> propertyNames = selectWrapper.getPropertyNames();

assertThat(propertyNames).containsExactly("idAlias", "emailAlias");
}

@Test
void givenSelectionSpecification_whenJpaNativeSelectWrapperOf_thenReturnJpaSelectionSpecificationSelectWrapper() {
SelectionSpecification<Object> spec = (root, cb) -> root.get("id");
NativeSelectWrapper<SelectionSpecification<?>> wrapper = selectByNative(spec);

JpaSelect jpaSelect = JpaNativeSelectWrapper.of(wrapper);

assertThat(jpaSelect).isInstanceOf(JpaSelectionSpecificationSelectWrapper.class);
}
}

@Nested
class AliasedSelectionSpecificationTests {
@Test
void givenAliasedSelectionSpecification_whenGetAlias_thenReturnAlias() {
AliasedSelectionSpecification<Object> spec = AliasedSelectionSpecification.of(
(root, cb) -> root.get("id"),
"customAlias"
);

assertThat(spec.getAlias()).isEqualTo("customAlias");
}

@SuppressWarnings("unchecked")
@Test
void givenAliasedSelectionSpecification_whenToSelection_thenDelegatesToUnderlyingSpec() {
Selection<?> mockSelection = mock(Selection.class);
SelectionSpecification<Object> delegate = (root, cb) -> mockSelection;
AliasedSelectionSpecification<Object> spec = AliasedSelectionSpecification.of(delegate, "alias");

Root<Object> root = mock(Root.class);
CriteriaBuilder cb = mock(CriteriaBuilder.class);

Selection<?> result = spec.toSelection(root, cb);

assertThat(result).isSameAs(mockSelection);
}
}

@Nested
class SelectionSpecificationDefaultMethodTests {
@Test
void givenSelectionSpecificationLambda_whenGetAlias_thenReturnNull() {
SelectionSpecification<Object> spec = (root, cb) -> root.get("id");

assertThat(spec.getAlias()).isNull();
}
}
}
Loading
Loading