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

[14.0.x] ISPN-14478 Add possibility to project object version in Ickle query result #10724

Merged
merged 10 commits into from Mar 28, 2023
@@ -0,0 +1,76 @@
package org.infinispan.client.hotrod.query.projection;

import static org.assertj.core.api.Assertions.assertThat;
import static org.infinispan.configuration.cache.IndexStorage.LOCAL_HEAP;

import java.util.List;

import org.infinispan.client.hotrod.MetadataValue;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.Search;
import org.infinispan.client.hotrod.test.SingleHotRodServerTest;
import org.infinispan.commons.test.annotation.TestForIssue;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.protostream.SerializationContextInitializer;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
import org.infinispan.query.model.Developer;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.testng.annotations.Test;

@Test(groups = "functional", testName = "org.infinispan.client.hotrod.query.projection.MetaProjectionTest")
@TestForIssue(jiraKey = "ISPN-14478")
public class MetaProjectionTest extends SingleHotRodServerTest {

@Override
protected EmbeddedCacheManager createCacheManager() throws Exception {
ConfigurationBuilder config = new ConfigurationBuilder();
config.indexing().enable()
.storage(LOCAL_HEAP)
.addIndexedEntity("io.dev.Developer");
return TestCacheManagerFactory.createServerModeCacheManager(config);
}

@Override
protected SerializationContextInitializer contextInitializer() {
return Developer.DeveloperSchema.INSTANCE;
}

@Test
public void testVersionProjection() {
RemoteCache<String, Developer> remoteCache = remoteCacheManager.getCache();

remoteCache.put("open-contributor", new Developer("iamopen", "iamopen@redmail.io", "Hibernate developer", 2000));
remoteCache.put("open-contributor", new Developer("iamopen", "iamopen@redmail.io", "Infinispan developer", 2000));
remoteCache.put("another-contributor", new Developer("mycodeisopen", "mycodeisopen@redmail.io",
"Infinispan engineer", 799));

MetadataValue<Developer> metadata = remoteCache.getWithMetadata("open-contributor");
assertThat(metadata.getVersion()).isEqualTo(2L);
metadata = remoteCache.getWithMetadata("another-contributor");
assertThat(metadata.getVersion()).isEqualTo(3L); // version is global to the cache - not to the entry

QueryFactory queryFactory = Search.getQueryFactory(remoteCache);
Query<Object[]> query = queryFactory.create(
"select d.nick, version(d), d.email, d.biography, d.contributions from io.dev.Developer d where d.biography : 'Infinispan' order by d.email");
List<Object[]> list = query.execute().list();

assertThat(list).hasSize(2);
assertThat(list.get(0)).containsExactly("iamopen", 2L, "iamopen@redmail.io",
"Infinispan developer", 2000);
assertThat(list.get(1)).containsExactly("mycodeisopen", 3L, "mycodeisopen@redmail.io",
"Infinispan engineer", 799);

query = queryFactory.create(
"select d, version(d) from io.dev.Developer d where d.biography : 'Infinispan' order by d.email");
list = query.execute().list();

assertThat(list).hasSize(2);
assertThat(list.get(0)[0]).isNotNull().isInstanceOf(Developer.class);
assertThat(list.get(0)[1]).isEqualTo(2L);
assertThat(list.get(1)[0]).isNotNull().isInstanceOf(Developer.class);
assertThat(list.get(1)[1]).isEqualTo(3L);
}

}
20 changes: 20 additions & 0 deletions documentation/src/main/asciidoc/topics/ref_query_ickle_syntax.adoc
Expand Up @@ -108,6 +108,26 @@ a projected attribute.
SELECT title, publicationYear FROM org.infinispan.sample.Book WHERE title like '%Data Grid%' OR description like '%Data Grid%'
----

=== Project cache entry version
It is possible to project the cache entry version, using the `version` projection function.

[source,sql]
----
# return the title, publication year and the cache entry version
SELECT b.title, b.publicationYear, version(b) FROM org.infinispan.sample.Book b WHERE b.title like '%Data Grid%'
----

=== Project cache entry value
It is possible to project the cache entry value together with other projections.
It can be used for instance to project the cache entry value together with the cache entry version
in the same `Object[]` returned hit.

[source,sql]
----
# return the cache entry value and the cache entry version
SELECT b, version(b) FROM org.infinispan.sample.Book b WHERE b.title like '%Data Grid%'
----

[discrete]
== Sorting
Ordering the results based on one or more attributes or attribute paths is done with the `ORDER BY` clause. If multiple sorting criteria
Expand Down
Expand Up @@ -112,6 +112,7 @@ tokens {
SUM;
WHERE;
WITH;
VERSION;
}

@header {
Expand Down
Expand Up @@ -306,6 +306,7 @@ additiveExpression
: quantifiedExpression
| standardFunction
| setFunction
| versionFunction
| collectionExpression
| atom
;
Expand All @@ -329,6 +330,10 @@ indexFunction
: index_key^ LPAREN! aliasReference RPAREN!
;

versionFunction
: version_key^ LPAREN! aliasReference RPAREN!
;

setFunction
@init { boolean generateOmittedElement = true; if (state.backtracking == 0) pushEnableParameterUsage(true); }
@after { popEnableParameterUsage(); }
Expand Down Expand Up @@ -472,6 +477,10 @@ count_key
: {validateSoftKeyword("count")}?=> IDENTIFIER -> COUNT[$IDENTIFIER]
;

version_key
: {validateSoftKeyword("version")}?=> IDENTIFIER -> VERSION[$IDENTIFIER]
;

size_key
: {validateSoftKeyword("size")}?=> IDENTIFIER -> SIZE[$IDENTIFIER]
;
Expand Down
Expand Up @@ -225,6 +225,7 @@ propertyReferenceExpression

function
: setFunction
| versionFunction
| standardFunction
;

Expand All @@ -237,6 +238,10 @@ setFunction
| ^(COUNT (ASTERISK { delegate.activateAggregation(AggregationFunction.COUNT); } | (DISTINCT { delegate.activateAggregation(AggregationFunction.COUNT_DISTINCT); } | ALL { delegate.activateAggregation(AggregationFunction.COUNT); }) countFunctionArguments))
;

versionFunction
: ^(VERSION { delegate.projectVersion(); } ALIAS_REF)
;

standardFunction
: sizeFunction
| indexFunction
Expand Down
Expand Up @@ -226,6 +226,7 @@ propertyReferenceExpression

function
: setFunction
| versionFunction
| standardFunction
;

Expand All @@ -237,6 +238,10 @@ setFunction
| ^(COUNT (ASTERISK | (DISTINCT | ALL) countFunctionArguments))
;

versionFunction
: ^(VERSION ALIAS_REF)
;

standardFunction
: sizeFunction
| indexFunction
Expand Down
Expand Up @@ -39,7 +39,7 @@ public FilterResult filter(Object key, Object instance) {
if (instance == null) {
throw new IllegalArgumentException("instance cannot be null");
}
MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> matcherEvalContext = matcher.startSingleTypeContext(null, null, instance, metadataAdapter);
MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> matcherEvalContext = matcher.startSingleTypeContext(null, null, key, instance, metadataAdapter);
if (matcherEvalContext != null) {
// once we have a successfully created context we already have a match as there are no filter conditions except for entity type
return new FilterResultImpl(key, matcher.convert(instance), null, null);
Expand Down
Expand Up @@ -268,7 +268,9 @@ public void unregisterFilter(FilterSubscription filterSubscription) {
* @param metadataAdapter the metadata adapter of expected instance type
* @return the MatcherEvalContext or {@code null} if no filter was registered for the instance
*/
protected abstract MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> startSingleTypeContext(Object userContext, Object eventType, Object instance, MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> metadataAdapter);
protected abstract MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> startSingleTypeContext(
Object userContext, Object eventType, Object key, Object instance,
MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> metadataAdapter);

protected abstract MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> createMetadataAdapter(TypeMetadata entityType);

Expand Down
Expand Up @@ -199,7 +199,7 @@ public FilterResult filter(Object key, Object instance) {
throw new IllegalArgumentException("instance cannot be null");
}

MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> matcherEvalContext = matcher.startSingleTypeContext(null, null, instance, filterSubscription.getMetadataAdapter());
MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> matcherEvalContext = matcher.startSingleTypeContext(null, null, key, instance, filterSubscription.getMetadataAdapter());
if (matcherEvalContext != null) {
FilterEvalContext filterEvalContext = matcherEvalContext.initSingleFilterContext(filterSubscription);
if (acc != null) {
Expand Down
Expand Up @@ -15,7 +15,7 @@
* @author anistor@redhat.com
* @since 7.0
*/
public final class ProtobufMatcher extends BaseMatcher<Descriptor, FieldDescriptor, Integer> {
public class ProtobufMatcher extends BaseMatcher<Descriptor, FieldDescriptor, Integer> {

private final SerializationContext serializationContext;

Expand All @@ -29,7 +29,7 @@ public ProtobufMatcher(SerializationContext serializationContext, IndexedFieldPr

@Override
protected ProtobufMatcherEvalContext startMultiTypeContext(boolean isDeltaFilter, Object userContext, Object eventType, Object instance) {
ProtobufMatcherEvalContext context = new ProtobufMatcherEvalContext(userContext, eventType, instance, wrappedMessageDescriptor, serializationContext);
ProtobufMatcherEvalContext context = new ProtobufMatcherEvalContext(userContext, eventType, null, instance, wrappedMessageDescriptor, serializationContext);
if (context.getEntityType() != null) {
FilterRegistry<Descriptor, FieldDescriptor, Integer> filterRegistry = getFilterRegistryForType(isDeltaFilter, context.getEntityType());
if (filterRegistry != null) {
Expand All @@ -41,8 +41,9 @@ protected ProtobufMatcherEvalContext startMultiTypeContext(boolean isDeltaFilter
}

@Override
protected ProtobufMatcherEvalContext startSingleTypeContext(Object userContext, Object eventType, Object instance, MetadataAdapter<Descriptor, FieldDescriptor, Integer> metadataAdapter) {
ProtobufMatcherEvalContext ctx = new ProtobufMatcherEvalContext(userContext, eventType, instance, wrappedMessageDescriptor, serializationContext);
protected ProtobufMatcherEvalContext startSingleTypeContext(Object userContext, Object eventType, Object key,
Object instance, MetadataAdapter<Descriptor, FieldDescriptor, Integer> metadataAdapter) {
ProtobufMatcherEvalContext ctx = new ProtobufMatcherEvalContext(userContext, eventType, key, instance, wrappedMessageDescriptor, serializationContext);
return ctx.getEntityType() != null && ctx.getEntityType().getFullName().equals(metadataAdapter.getTypeName()) ? ctx : null;
}

Expand Down
Expand Up @@ -31,17 +31,18 @@ public ReflectionMatcher(EntityNameResolver<Class<?>> entityNameResolver) {
protected ReflectionMatcherEvalContext startMultiTypeContext(boolean isDeltaFilter, Object userContext, Object eventType, Object instance) {
FilterRegistry<Class<?>, ReflectionHelper.PropertyAccessor, String> filterRegistry = getFilterRegistryForType(isDeltaFilter, instance.getClass());
if (filterRegistry != null) {
ReflectionMatcherEvalContext context = new ReflectionMatcherEvalContext(userContext, eventType, instance);
ReflectionMatcherEvalContext context = new ReflectionMatcherEvalContext(userContext, eventType, null, instance);
context.initMultiFilterContext(filterRegistry);
return context;
}
return null;
}

@Override
protected ReflectionMatcherEvalContext startSingleTypeContext(Object userContext, Object eventType, Object instance, MetadataAdapter<Class<?>, ReflectionHelper.PropertyAccessor, String> metadataAdapter) {
protected ReflectionMatcherEvalContext startSingleTypeContext(Object userContext, Object eventType, Object key,
Object instance, MetadataAdapter<Class<?>, ReflectionHelper.PropertyAccessor, String> metadataAdapter) {
if (metadataAdapter.getTypeMetadata() == instance.getClass()) {
return new ReflectionMatcherEvalContext(userContext, eventType, instance);
return new ReflectionMatcherEvalContext(userContext, eventType, key, instance);
} else {
return null;
}
Expand Down
Expand Up @@ -25,17 +25,18 @@ public RowMatcher(RowPropertyHelper.ColumnMetadata[] columns) {
protected RowMatcherEvalContext startMultiTypeContext(boolean isDeltaFilter, Object userContext, Object eventType, Object instance) {
FilterRegistry<RowPropertyHelper.RowMetadata, RowPropertyHelper.ColumnMetadata, Integer> filterRegistry = getFilterRegistryForType(isDeltaFilter, rowMetadata);
if (filterRegistry != null) {
RowMatcherEvalContext context = new RowMatcherEvalContext(userContext, eventType, instance, rowMetadata);
RowMatcherEvalContext context = new RowMatcherEvalContext(userContext, eventType, null, instance, rowMetadata);
context.initMultiFilterContext(filterRegistry);
return context;
}
return null;
}

@Override
protected RowMatcherEvalContext startSingleTypeContext(Object userContext, Object eventType, Object instance, MetadataAdapter<RowPropertyHelper.RowMetadata, RowPropertyHelper.ColumnMetadata, Integer> metadataAdapter) {
protected RowMatcherEvalContext startSingleTypeContext(Object userContext, Object eventType, Object key,
Object instance, MetadataAdapter<RowPropertyHelper.RowMetadata, RowPropertyHelper.ColumnMetadata, Integer> metadataAdapter) {
if (Object[].class == instance.getClass()) {
return new RowMatcherEvalContext(userContext, eventType, instance, rowMetadata);
return new RowMatcherEvalContext(userContext, eventType, key, instance, rowMetadata);
} else {
return null;
}
Expand Down
Expand Up @@ -197,4 +197,11 @@ public void removeProjections(FilterSubscriptionImpl filterSubscription) {
public String toString() {
return "AttributeNode(" + attribute + ')';
}

public Object cacheMetadataProjection(Object key, AttributeId attribute) {
if (!(this instanceof RootNode) || !(metadataAdapter instanceof MetadataProjectable)) {
return null;
}
return ((MetadataProjectable) metadataAdapter).projection(key, attribute);
}
}
Expand Up @@ -29,6 +29,8 @@ public abstract class MatcherEvalContext<TypeMetadata, AttributeMetadata, Attrib

private final Object userContext;

protected final Object key;

private final Object instance;

private final Object eventType;
Expand All @@ -44,12 +46,13 @@ public abstract class MatcherEvalContext<TypeMetadata, AttributeMetadata, Attrib

private Map<Predicate<?>, Counter> suspendedPredicateSubscriptionCounts;

protected MatcherEvalContext(Object userContext, Object eventType, Object instance) {
protected MatcherEvalContext(Object userContext, Object eventType, Object key, Object instance) {
if (instance == null) {
throw new IllegalArgumentException("instance cannot be null");
}
this.userContext = userContext;
this.eventType = eventType;
this.key = key;
this.instance = instance;
}

Expand Down
@@ -0,0 +1,7 @@
package org.infinispan.objectfilter.impl.predicateindex;

public interface MetadataProjectable<AttributeValue> {

Object projection(Object key, AttributeValue attribute);

}
Expand Up @@ -3,6 +3,7 @@
import java.io.IOException;

import org.infinispan.objectfilter.impl.logging.Log;
import org.infinispan.objectfilter.impl.syntax.parser.ProtobufPropertyHelper;
import org.infinispan.protostream.MessageContext;
import org.infinispan.protostream.ProtobufParser;
import org.infinispan.protostream.SerializationContext;
Expand Down Expand Up @@ -32,8 +33,9 @@ public final class ProtobufMatcherEvalContext extends MatcherEvalContext<Descrip

private final SerializationContext serializationContext;

public ProtobufMatcherEvalContext(Object userContext, Object eventType, Object instance, Descriptor wrappedMessageDescriptor, SerializationContext serializationContext) {
super(userContext, eventType, instance);
public ProtobufMatcherEvalContext(Object userContext, Object eventType, Object key, Object instance,
Descriptor wrappedMessageDescriptor, SerializationContext serializationContext) {
super(userContext, eventType, key, instance);
this.serializationContext = serializationContext;
try {
ProtobufParser.INSTANCE.parse(this, wrappedMessageDescriptor, (byte[]) getInstance());
Expand Down Expand Up @@ -157,6 +159,12 @@ public void onEnd() {
protected void processAttributes(AttributeNode<FieldDescriptor, Integer> node, Object instance) {
try {
ProtobufParser.INSTANCE.parse(this, payloadMessageDescriptor, payload);
for (AttributeNode<FieldDescriptor, Integer> childAttribute : node.getChildren()) {
if (childAttribute.getAttribute() >= ProtobufPropertyHelper.MIN_METADATA_FIELD_ATTRIBUTE_ID) {
Object attributeValue = node.cacheMetadataProjection(key, childAttribute.getAttribute());
childAttribute.processValue(attributeValue, this);
}
}
} catch (IOException e) {
throw new RuntimeException(e); // TODO [anistor] proper exception handling needed
}
Expand Down
Expand Up @@ -12,8 +12,8 @@ public final class ReflectionMatcherEvalContext extends MatcherEvalContext<Class

private final Class<?> entityType;

public ReflectionMatcherEvalContext(Object userContext, Object eventType, Object instance) {
super(userContext, eventType, instance);
public ReflectionMatcherEvalContext(Object userContext, Object eventType, Object key, Object instance) {
super(userContext, eventType, key, instance);
entityType = instance.getClass();
}

Expand All @@ -29,6 +29,14 @@ protected void processAttributes(AttributeNode<ReflectionHelper.PropertyAccessor
processAttribute(childAttribute, null);
} else {
ReflectionHelper.PropertyAccessor accessor = childAttribute.getMetadata();
if (accessor == null) {
Object attributeValue = node.cacheMetadataProjection(key, childAttribute.getAttribute());
if (attributeValue != null) {
processAttribute(childAttribute, attributeValue);
}
continue;
}

if (accessor.isMultiple()) {
Iterator valuesIt = accessor.getValueIterator(instance);
if (valuesIt == null) {
Expand Down
Expand Up @@ -10,8 +10,8 @@ public final class RowMatcherEvalContext extends MatcherEvalContext<RowPropertyH

private final RowPropertyHelper.RowMetadata rowMetadata;

public RowMatcherEvalContext(Object userContext, Object eventType, Object instance, RowPropertyHelper.RowMetadata rowMetadata) {
super(userContext, eventType, instance);
public RowMatcherEvalContext(Object userContext, Object eventType, Object key, Object instance, RowPropertyHelper.RowMetadata rowMetadata) {
super(userContext, eventType, key, instance);
this.rowMetadata = rowMetadata;
}

Expand Down