) execute(criteria, sort, offset, rows, keyspace);
114 | }
115 |
116 | /**
117 | * @param criteria
118 | * @param keyspace
119 | * @return
120 | */
121 | public abstract long count(@Nullable CRITERIA criteria, String keyspace);
122 |
123 | /**
124 | * Get the {@link KeyValueAdapter} used.
125 | *
126 | * @return
127 | */
128 | protected @Nullable ADAPTER getAdapter() {
129 | return this.adapter;
130 | }
131 |
132 | /**
133 | * Get the required {@link KeyValueAdapter} used or throw {@link IllegalStateException} if the adapter is not set.
134 | *
135 | * @return the required {@link KeyValueAdapter}.
136 | * @throws IllegalStateException if the adapter is not set.
137 | */
138 | protected ADAPTER getRequiredAdapter() {
139 |
140 | ADAPTER adapter = getAdapter();
141 |
142 | if (adapter != null) {
143 | return adapter;
144 | }
145 |
146 | throw new IllegalStateException("Required KeyValueAdapter is not set");
147 | }
148 |
149 | /**
150 | * @param adapter
151 | */
152 | @SuppressWarnings("unchecked")
153 | public void registerAdapter(KeyValueAdapter adapter) {
154 |
155 | if (this.adapter == null) {
156 | this.adapter = (ADAPTER) adapter;
157 | } else {
158 | throw new IllegalArgumentException("Cannot register more than one adapter for this QueryEngine");
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/QueryEngineFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | /**
19 | * Interface for {@code QueryEngineFactory} implementations that provide a {@link QueryEngine} object as part of the
20 | * configuration.
21 | *
22 | * The factory is used during configuration to supply the query engine to be used. When configured, a
23 | * {@code QueryEngineFactory} can be instantiated by accepting a {@link SortAccessor} in its constructor. Otherwise,
24 | * implementations are expected to declare a no-args constructor.
25 | *
26 | * @author Mark Paluch
27 | * @since 3.3.1
28 | */
29 | public interface QueryEngineFactory {
30 |
31 | /**
32 | * Factory method for creating a {@link QueryEngine}.
33 | *
34 | * @return the query engine.
35 | */
36 | QueryEngine, ?, ?> create();
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/SimplePropertyPathAccessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | import org.jspecify.annotations.Nullable;
19 | import org.springframework.beans.BeanWrapper;
20 | import org.springframework.data.mapping.PropertyPath;
21 | import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
22 |
23 | /**
24 | * @author Christoph Strobl
25 | * @since 3.1.10
26 | */
27 | public class SimplePropertyPathAccessor {
28 |
29 | private final Object root;
30 |
31 | public SimplePropertyPathAccessor(Object source) {
32 | this.root = source;
33 | }
34 |
35 | public @Nullable Object getValue(PropertyPath path) {
36 |
37 | Object currentValue = root;
38 | for (PropertyPath current : path) {
39 | currentValue = wrap(currentValue).getPropertyValue(current.getSegment());
40 | if (currentValue == null) {
41 | break;
42 | }
43 | }
44 | return currentValue;
45 | }
46 |
47 | BeanWrapper wrap(Object o) {
48 | return new DirectFieldAccessFallbackBeanWrapper(o);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/SortAccessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | import org.jspecify.annotations.Nullable;
19 | import org.springframework.data.domain.Sort;
20 | import org.springframework.data.keyvalue.core.query.KeyValueQuery;
21 |
22 | /**
23 | * Resolves the {@link Sort} object from given {@link KeyValueQuery} and potentially converts it into a store specific
24 | * representation that can be used by the {@link QueryEngine} implementation.
25 | *
26 | * @author Christoph Strobl
27 | * @author Mark Paluch
28 | * @param
29 | */
30 | public interface SortAccessor {
31 |
32 | /**
33 | * Reads {@link KeyValueQuery#getSort()} of given {@link KeyValueQuery} and applies required transformation to match
34 | * the desired type.
35 | *
36 | * @param query must not be {@literal null}.
37 | * @return {@literal null} in case {@link Sort} has not been defined on {@link KeyValueQuery}.
38 | */
39 | @Nullable
40 | T resolve(KeyValueQuery> query);
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/SpelCriteria.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | import org.springframework.expression.EvaluationContext;
19 | import org.springframework.expression.spel.standard.SpelExpression;
20 | import org.springframework.expression.spel.support.SimpleEvaluationContext;
21 | import org.springframework.util.Assert;
22 |
23 | /**
24 | * {@link SpelCriteria} allows to pass on a {@link SpelExpression} and {@link EvaluationContext} to the actual query
25 | * processor. This decouples the {@link SpelExpression} from the context it is used in.
26 | *
27 | * @author Christoph Strobl
28 | * @author Oliver Gierke
29 | */
30 | public class SpelCriteria {
31 |
32 | private final SpelExpression expression;
33 | private final EvaluationContext context;
34 |
35 | /**
36 | * Creates a new {@link SpelCriteria} for the given {@link SpelExpression}.
37 | *
38 | * @param expression must not be {@literal null}.
39 | */
40 | public SpelCriteria(SpelExpression expression) {
41 | this(expression, SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build());
42 | }
43 |
44 | /**
45 | * Creates new {@link SpelCriteria}.
46 | *
47 | * @param expression must not be {@literal null}.
48 | * @param context must not be {@literal null}.
49 | */
50 | public SpelCriteria(SpelExpression expression, EvaluationContext context) {
51 |
52 | Assert.notNull(expression, "SpEL expression must not be null");
53 | Assert.notNull(context, "EvaluationContext must not be null");
54 |
55 | this.expression = expression;
56 | this.context = context;
57 | }
58 |
59 | /**
60 | * @return will never be {@literal null}.
61 | */
62 | public EvaluationContext getContext() {
63 | return context;
64 | }
65 |
66 | /**
67 | * @return will never be {@literal null}.
68 | */
69 | public SpelExpression getExpression() {
70 | return expression;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | import org.jspecify.annotations.Nullable;
19 | import org.springframework.data.keyvalue.core.query.KeyValueQuery;
20 | import org.springframework.expression.spel.standard.SpelExpression;
21 | import org.springframework.expression.spel.standard.SpelExpressionParser;
22 | import org.springframework.util.Assert;
23 |
24 | /**
25 | * {@link CriteriaAccessor} implementation capable of {@link SpelExpression}s.
26 | *
27 | * @author Christoph Strobl
28 | * @author Oliver Gierke
29 | */
30 | class SpelCriteriaAccessor implements CriteriaAccessor {
31 |
32 | private final SpelExpressionParser parser;
33 |
34 | /**
35 | * Creates a new {@link SpelCriteriaAccessor} using the given {@link SpelExpressionParser}.
36 | *
37 | * @param parser must not be {@literal null}.
38 | */
39 | public SpelCriteriaAccessor(SpelExpressionParser parser) {
40 |
41 | Assert.notNull(parser, "SpelExpressionParser must not be null");
42 |
43 | this.parser = parser;
44 | }
45 |
46 | @Override
47 | public @Nullable SpelCriteria resolve(KeyValueQuery> query) {
48 |
49 | if (query.getCriteria() == null) {
50 | return null;
51 | }
52 |
53 | if (query.getCriteria() instanceof SpelExpression) {
54 | return new SpelCriteria((SpelExpression) query.getCriteria());
55 | }
56 |
57 | if (query.getCriteria() instanceof String) {
58 | return new SpelCriteria(parser.parseRaw((String) query.getCriteria()));
59 | }
60 |
61 | if (query.getCriteria() instanceof SpelCriteria) {
62 | return (SpelCriteria) query.getCriteria();
63 | }
64 |
65 | throw new IllegalArgumentException("Cannot create SpelCriteria for " + query.getCriteria());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | import java.util.Comparator;
19 |
20 | import org.jspecify.annotations.Nullable;
21 | import org.springframework.expression.spel.standard.SpelExpression;
22 | import org.springframework.expression.spel.standard.SpelExpressionParser;
23 | import org.springframework.expression.spel.support.SimpleEvaluationContext;
24 | import org.springframework.lang.Contract;
25 | import org.springframework.util.Assert;
26 |
27 | /**
28 | * {@link Comparator} implementation using {@link SpelExpression}.
29 | *
30 | * @author Christoph Strobl
31 | * @author Oliver Gierke
32 | * @author Mark Paluch
33 | * @param
34 | */
35 | public class SpelPropertyComparator implements Comparator {
36 |
37 | private static final Comparator> NULLS_FIRST = Comparator.nullsFirst(Comparator.naturalOrder());
38 | private static final Comparator> NULLS_LAST = Comparator.nullsLast(Comparator.naturalOrder());
39 |
40 | private final String path;
41 | private final SpelExpressionParser parser;
42 |
43 | private boolean asc = true;
44 | private boolean nullsFirst = true;
45 | private @Nullable SpelExpression expression;
46 |
47 | /**
48 | * Create new {@link SpelPropertyComparator} for the given property path an {@link SpelExpressionParser}.
49 | *
50 | * @param path must not be {@literal null} or empty.
51 | * @param parser must not be {@literal null}.
52 | */
53 | public SpelPropertyComparator(String path, SpelExpressionParser parser) {
54 |
55 | Assert.hasText(path, "Path must not be null or empty");
56 | Assert.notNull(parser, "SpelExpressionParser must not be null");
57 |
58 | this.path = path;
59 | this.parser = parser;
60 | }
61 |
62 | /**
63 | * Sort {@literal ascending}.
64 | *
65 | * @return
66 | */
67 | @Contract("-> this")
68 | public SpelPropertyComparator<@Nullable T> asc() {
69 | this.asc = true;
70 | return this;
71 | }
72 |
73 | /**
74 | * Sort {@literal descending}.
75 | *
76 | * @return
77 | */
78 | @Contract("-> this")
79 | public SpelPropertyComparator<@Nullable T> desc() {
80 | this.asc = false;
81 | return this;
82 | }
83 |
84 | /**
85 | * Sort {@literal null} values first.
86 | *
87 | * @return
88 | */
89 | @Contract("-> this")
90 | public SpelPropertyComparator<@Nullable T> nullsFirst() {
91 | this.nullsFirst = true;
92 | return this;
93 | }
94 |
95 | /**
96 | * Sort {@literal null} values last.
97 | *
98 | * @return
99 | */
100 | @Contract("-> this")
101 | public SpelPropertyComparator<@Nullable T> nullsLast() {
102 | this.nullsFirst = false;
103 | return this;
104 | }
105 |
106 | /**
107 | * Parse values to {@link SpelExpression}
108 | *
109 | * @return
110 | */
111 | protected SpelExpression getExpression() {
112 |
113 | if (this.expression == null) {
114 | this.expression = parser.parseRaw(buildExpressionForPath());
115 | }
116 |
117 | return this.expression;
118 | }
119 |
120 | /**
121 | * Create the expression raw value.
122 | *
123 | * @return
124 | */
125 | protected String buildExpressionForPath() {
126 |
127 | return String.format("#comparator.compare(#arg1?.%s,#arg2?.%s)", path.replace(".", "?."),
128 | path.replace(".", "?."));
129 | }
130 |
131 | @Override
132 | public int compare(@Nullable T arg1, @Nullable T arg2) {
133 |
134 | SpelExpression expressionToUse = getExpression();
135 |
136 | SimpleEvaluationContext ctx = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build();
137 | ctx.setVariable("comparator", nullsFirst ? NULLS_FIRST : NULLS_LAST);
138 | ctx.setVariable("arg1", arg1);
139 | ctx.setVariable("arg2", arg2);
140 |
141 | expressionToUse.setEvaluationContext(ctx);
142 |
143 | Integer value = expressionToUse.getValue(Integer.class);
144 | return (value != null ? value : 0) * (asc ? 1 : -1);
145 | }
146 |
147 | /**
148 | * Get dot path to property.
149 | *
150 | * @return
151 | */
152 | public String getPath() {
153 | return path;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | import java.util.Collection;
19 | import java.util.Comparator;
20 | import java.util.List;
21 | import java.util.stream.Collectors;
22 | import java.util.stream.Stream;
23 |
24 | import org.jspecify.annotations.Nullable;
25 | import org.springframework.data.keyvalue.core.query.KeyValueQuery;
26 | import org.springframework.expression.spel.SpelEvaluationException;
27 | import org.springframework.expression.spel.standard.SpelExpression;
28 | import org.springframework.expression.spel.standard.SpelExpressionParser;
29 |
30 | /**
31 | * {@link QueryEngine} implementation specific for executing {@link SpelExpression} based {@link KeyValueQuery} against
32 | * {@link KeyValueAdapter}.
33 | *
34 | * @author Christoph Strobl
35 | * @author Oliver Gierke
36 | * @author Mark Paluch
37 | */
38 | public class SpelQueryEngine extends QueryEngine> {
39 |
40 | private static final SpelExpressionParser PARSER = new SpelExpressionParser();
41 |
42 | /**
43 | * Creates a new {@link SpelQueryEngine}.
44 | */
45 | public SpelQueryEngine() {
46 | this(new SpelSortAccessor(PARSER));
47 | }
48 |
49 | /**
50 | * Creates a new query engine using provided {@link SortAccessor accessor} for sorting results.
51 | *
52 | * @since 3.1.10
53 | */
54 | public SpelQueryEngine(SortAccessor> sortAccessor) {
55 | super(new SpelCriteriaAccessor(PARSER), sortAccessor);
56 | }
57 |
58 | @Override
59 | public Collection> execute(@Nullable SpelCriteria criteria, @Nullable Comparator> sort, long offset, int rows,
60 | String keyspace) {
61 | return sortAndFilterMatchingRange(getRequiredAdapter().getAllOf(keyspace), criteria, sort, offset, rows);
62 | }
63 |
64 | @Override
65 | public long count(@Nullable SpelCriteria criteria, String keyspace) {
66 | return filterMatchingRange(IterableConverter.toList(getRequiredAdapter().getAllOf(keyspace)), criteria, -1, -1)
67 | .size();
68 | }
69 |
70 | @SuppressWarnings({ "unchecked", "rawtypes" })
71 | private List> sortAndFilterMatchingRange(Iterable> source, @Nullable SpelCriteria criteria,
72 | @Nullable Comparator sort, long offset, int rows) {
73 |
74 | List> tmp = IterableConverter.toList(source);
75 | if (sort != null) {
76 | tmp.sort(sort);
77 | }
78 |
79 | return filterMatchingRange(tmp, criteria, offset, rows);
80 | }
81 |
82 | private static List filterMatchingRange(List source, @Nullable SpelCriteria criteria, long offset,
83 | int rows) {
84 |
85 | Stream stream = source.stream();
86 |
87 | if (criteria != null) {
88 | stream = stream.filter(it -> evaluateExpression(criteria, it));
89 | }
90 | if (offset > 0) {
91 | stream = stream.skip(offset);
92 | }
93 | if (rows > 0) {
94 | stream = stream.limit(rows);
95 | }
96 |
97 | return stream.collect(Collectors.toList());
98 | }
99 |
100 | @SuppressWarnings("NullAway")
101 | private static boolean evaluateExpression(SpelCriteria criteria, Object candidate) {
102 |
103 | try {
104 | return criteria.getExpression().getValue(criteria.getContext(), candidate, Boolean.class);
105 | } catch (SpelEvaluationException e) {
106 | criteria.getContext().setVariable("it", candidate);
107 | return criteria.getExpression().getValue(criteria.getContext()) == null ? false
108 | : criteria.getExpression().getValue(criteria.getContext(), Boolean.class);
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.data.keyvalue.core;
17 |
18 | import java.util.Comparator;
19 | import java.util.Optional;
20 |
21 | import org.jspecify.annotations.Nullable;
22 | import org.springframework.data.domain.Sort.Direction;
23 | import org.springframework.data.domain.Sort.NullHandling;
24 | import org.springframework.data.domain.Sort.Order;
25 | import org.springframework.data.keyvalue.core.query.KeyValueQuery;
26 | import org.springframework.expression.spel.standard.SpelExpressionParser;
27 | import org.springframework.util.Assert;
28 |
29 | /**
30 | * {@link SortAccessor} implementation capable of creating {@link SpelPropertyComparator}.
31 | *
32 | * @author Christoph Strobl
33 | * @author Oliver Gierke
34 | * @author Mark Paluch
35 | */
36 | public class SpelSortAccessor implements SortAccessor> {
37 |
38 | private final SpelExpressionParser parser;
39 |
40 | /**
41 | * Creates a new {@link SpelSortAccessor} given {@link SpelExpressionParser}.
42 | *
43 | * @param parser must not be {@literal null}.
44 | */
45 | public SpelSortAccessor(SpelExpressionParser parser) {
46 |
47 | Assert.notNull(parser, "SpelExpressionParser must not be null");
48 | this.parser = parser;
49 | }
50 |
51 | @Override
52 | public @Nullable Comparator> resolve(KeyValueQuery> query) {
53 |
54 | if (query.getSort().isUnsorted()) {
55 | return null;
56 | }
57 |
58 | Optional> comparator = Optional.empty();
59 | for (Order order : query.getSort()) {
60 |
61 | SpelPropertyComparator