Skip to content

Commit

Permalink
PARTIAL PROJECTIONS ON CACHED DATA WITH INTERFACES LETS GO
Browse files Browse the repository at this point in the history
  • Loading branch information
orbyfied committed Dec 10, 2023
1 parent e315eaa commit 3673840
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 89 deletions.
12 changes: 2 additions & 10 deletions inset-core/src/main/java/slatepowered/inset/codec/DataCodec.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package slatepowered.inset.codec;

import slatepowered.inset.datastore.DataItem;
import slatepowered.inset.internal.ProjectionType;
import slatepowered.inset.operation.Projection;
import slatepowered.inset.query.Query;

Expand All @@ -13,7 +14,7 @@
* @param <K> The key type.
* @param <T> The data value type.
*/
public interface DataCodec<K, T> extends ValueCodec<T> {
public interface DataCodec<K, T> extends ValueCodec<T>, ProjectionType {

/**
* Retrieve the primary key from the given value if present.
Expand Down Expand Up @@ -48,13 +49,4 @@ public interface DataCodec<K, T> extends ValueCodec<T> {
*/
Predicate<T> getFilterPredicate(Query query);

/**
* Create a new projection which only includes the fields
* applicable to this data.
*
* @param primaryKeyName The primary key field name override.
* @return The projection.
*/
Projection createExclusiveProjection(String primaryKeyName);

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package slatepowered.inset.datastore;

import slatepowered.inset.codec.*;
import slatepowered.inset.internal.ProjectionInterface;
import slatepowered.inset.operation.Sorting;
import slatepowered.inset.query.FindAllOperation;
import slatepowered.inset.query.FoundItem;
import slatepowered.inset.query.Query;
import slatepowered.inset.source.DataSourceFindResult;
Expand Down Expand Up @@ -240,6 +242,11 @@ public DataItem<K, T> fetchSync() {
return decode(queryResult.input()).fetchedNow();
}

@Override
protected Datastore<K, T> assertQualified() {
return this.datastore;
}

@Override
public boolean isPartial() {
return false;
Expand All @@ -266,13 +273,19 @@ public <V> V getField(String fieldName, Type expectedType) {
}

@Override
public <V> V project(Class<V> vClass) {
return null; // TODO
public DataItem<K, T> fetch() {
return this;
}

@Override
public DataItem<K, T> fetch() {
return this;
@SuppressWarnings("unchecked")
protected <V> V projectInterface(ProjectionInterface projectionInterface) {
if (projectionInterface.getKlass().isInstance(value)) {
return (V) value;
}

final DataCodec<K, T> codec = datastore.getDataCodec();
return (V) projectionInterface.createProxy(() -> key, (name, type) -> codec.getField(value, name));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package slatepowered.inset.internal;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import slatepowered.inset.operation.Projection;
import slatepowered.inset.util.Reflections;
import slatepowered.veru.reflect.ReflectUtil;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

@RequiredArgsConstructor
@Getter
public class ProjectionInterface implements ProjectionType {

/**
* The interface class.
*/
protected final Class<?> klass;

/**
* The method representing the key field.
*/
protected final Method keyMethod;

/**
* The other data field methods.
*/
protected final List<Method> fieldMethods;

/**
* Create a new proxy for this interface with the given parameters.
*
* @return The proxy instance.
*/
public Object createProxy(Supplier<Object> keySupplier,
BiFunction<String, Type, Object> fieldGetter) {
return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] { klass }, (proxy, method, args) -> {
if (method.isDefault()) {
return ReflectUtil.invokeDefault(proxy, method, args);
}

if (method.equals(keyMethod)) {
return keySupplier.get();
}

if (method.equals(Reflections.METHOD_OBJECT_EQUALS)) {
return false; // todo
} else if (method.equals(Reflections.METHOD_OBJECT_TOSTRING)) {
return "partial projection of key " + keySupplier.get().toString();
} else if (method.equals(Reflections.METHOD_OBJECT_HASHCODE)) {
return keySupplier.get().hashCode();
}

return fieldGetter.apply(method.getName(), method.getGenericReturnType());
});
}

@Override
public Projection createExclusiveProjection(String primaryKeyNameOverride) {
List<String> fields = new ArrayList<>();

// add applicable data fields
for (Method method : fieldMethods) {
fields.add(method.getName());
}

// add primary key field
if (primaryKeyNameOverride == null)
primaryKeyNameOverride = keyMethod.getName();
fields.add(primaryKeyNameOverride);

return Projection.include(fields);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package slatepowered.inset.internal;

import slatepowered.inset.operation.Projection;

/**
* Represents a type which can be used as a projection.
*/
public interface ProjectionType {

/**
* Create a new projection which only includes the fields
* applicable to this data.
*
* @param primaryKeyNameOverride The primary key field name override.
* @return The projection.
*/
Projection createExclusiveProjection(String primaryKeyNameOverride);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package slatepowered.inset.internal;

import slatepowered.inset.codec.DataCodec;
import slatepowered.inset.datastore.Datastore;
import slatepowered.inset.operation.Projection;
import slatepowered.inset.reflective.Key;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Support with projection-defining interfaces.
*/
public final class ProjectionTypes {

/**
* All compiled projection interfaces.
*/
private static final Map<Class<?>, ProjectionInterface> compiledInterfaces = new ConcurrentHashMap<>();

/**
* Get or compile the projection data for the given interface.
*
* @param klass The interface class.
* @return The interface.
*/
public static ProjectionInterface compileProjectionInterface(Class<?> klass) {
ProjectionInterface projectionInterface = compiledInterfaces.get(klass);
if (projectionInterface != null) {
return projectionInterface;
}

if (!klass.isInterface()) {
return null;
}

Method[] methods = klass.getMethods();
List<Method> fieldMethods = new ArrayList<>();
Method keyMethod = null;
for (Method method : methods) {
// check for primary key method
if (method.isAnnotationPresent(Key.class)) {
keyMethod = method;
continue;
}

fieldMethods.add(method);
}

compiledInterfaces.put(klass, projectionInterface = new ProjectionInterface(klass, keyMethod, fieldMethods));
return projectionInterface;
}

/**
* Get or compile a {@link ProjectionType} for the given class/type in the context
* of the given datastore.
*
* @param klass The class.
* @param datastore The datastore.
* @return The {@link ProjectionType} instance.
*/
@SuppressWarnings("unchecked")
public static <K, T, V> ProjectionType getProjectionType(Class<V> klass, Datastore<K, T> datastore) {
ProjectionType projectionType = null;

// check for projection interface
if (klass.isInterface()) {
projectionType = compileProjectionInterface(klass);
}

// check for class
else {
projectionType = datastore.getCodecRegistry().getCodec(klass).expect(DataCodec.class);
}

if (projectionType != null)
return projectionType;
throw new UnsupportedOperationException("Unsupported type for projection of potentially partial data: " + klass);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import slatepowered.inset.datastore.Datastore;
import slatepowered.inset.datastore.OperationStatus;
import slatepowered.inset.internal.CachedStreams;
import slatepowered.inset.internal.ProjectionType;
import slatepowered.inset.internal.ProjectionTypes;
import slatepowered.inset.operation.Projection;
import slatepowered.inset.operation.Sorting;
import slatepowered.inset.source.DataSourceBulkIterable;
import slatepowered.inset.source.SourceFoundItem;
import sun.security.util.Cache;

import java.util.Iterator;
Expand Down Expand Up @@ -224,8 +227,8 @@ public FindAllOperation<K, T> projection(Projection projection) {
* @return This.
*/
public <V> FindAllOperation<K, T> projection(Class<V> vClass) {
DataCodec<K, V> dataCodec = datastore.getCodecRegistry().getCodec(vClass).expect(DataCodec.class);
Projection projection = dataCodec.createExclusiveProjection(iterable.getPrimaryKeyFieldOverride());
ProjectionType projectionType = ProjectionTypes.getProjectionType(vClass, datastore);
Projection projection = projectionType.createExclusiveProjection(iterable.getPrimaryKeyFieldOverride());
return projection(projection);
}

Expand Down Expand Up @@ -262,7 +265,7 @@ private <A> CompletableFuture<A> async(Supplier<A> supplier) {
}

// qualify the given item for this query
private FoundItem<K, T> qualify(FoundItem<?, ?> item) {
private SourceFoundItem<K, T> qualify(SourceFoundItem<?, ?> item) {
return item.qualify(this);
}

Expand Down Expand Up @@ -342,8 +345,8 @@ public List<? extends FoundItem<K, T>> list() {
return stream.collect(Collectors.toList());
}

List<? extends FoundItem<K, T>> list = (List<? extends FoundItem<K,T>>) (Object) iterable.list();
for (FoundItem<K, T> item : list) {
List<SourceFoundItem<K, T>> list = (List<SourceFoundItem<K,T>>) (Object) iterable.list();
for (SourceFoundItem<K, T> item : list) {
this.qualify(item);
}

Expand Down
Loading

0 comments on commit 3673840

Please sign in to comment.