io.permazen.IndexQueryInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of permazen-main Show documentation
Show all versions of permazen-main Show documentation
Permazen classes that map Java model classes onto the core API.
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package io.permazen;
import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import io.permazen.core.CoreIndex;
import io.permazen.core.CoreIndex2;
import io.permazen.core.CoreIndex3;
import io.permazen.core.CoreIndex4;
import io.permazen.core.FieldType;
import io.permazen.core.type.ReferenceFieldType;
import io.permazen.kv.KeyRange;
import io.permazen.kv.KeyRanges;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Information used for index queries.
*/
class IndexQueryInfo {
private static final KeyRange NULL_RANGE = new KeyRange(new byte[] { (byte)0xff }, null);
final IndexInfo indexInfo;
private final Class> startType;
private final ArrayList filters = new ArrayList<>();
// Constructors
// Constructor for regular simple index queries
IndexQueryInfo(Permazen jdb, Class> startType, String fieldName, Class> valueType) {
this(jdb, startType, fieldName, valueType, null);
}
// Primary constructor (keyType is null except for map value simple index queries)
IndexQueryInfo(Permazen jdb, Class> startType, String fieldName, Class> valueType, Class> keyType) {
// Sanity check
Preconditions.checkArgument(jdb != null, "null jdb");
Preconditions.checkArgument(fieldName != null, "null fieldName");
Preconditions.checkArgument(startType != null, "null startType");
Preconditions.checkArgument(valueType != null, "null valueType");
// Get start type
Preconditions.checkArgument(!startType.isPrimitive() && !startType.isArray(), "invalid startType " + startType);
this.startType = startType;
// Parse reference path
final ReferencePath path = jdb.parseReferencePath(this.startType, fieldName, true, true);
if (path.getReferenceFields().length > 0)
throw new IllegalArgumentException("invalid field name `" + fieldName + "': contains intermediate reference(s)");
// Verify the field is actually indexed in the specified type
if (!path.someTargetFieldIndexed)
throw new IllegalArgumentException("invalid index query on non-indexed field `" + fieldName + "' in " + startType);
// Get field index info (this verifies the field is indexed); keyType != null iff the field is a map value field
final SimpleFieldIndexInfo fieldIndexInfo = jdb.getIndexInfo(path.targetFieldStorageId,
keyType != null ? MapValueIndexInfo.class : SimpleFieldIndexInfo.class);
this.indexInfo = fieldIndexInfo;
// Verify value type
final ArrayList valueChecks = new ArrayList<>(3);
valueChecks.add(new ValueCheck("value type", valueType,
this.wrapRaw(path.getTargetFieldTypes()), fieldIndexInfo.getFieldType()));
// Verify target type
valueChecks.add(new ValueCheck("target type", startType, path.getTargetTypes()));
// Add additional check for the map key type when doing a map value index query
if (keyType != null) {
final MapValueIndexInfo mapValueIndexInfo = (MapValueIndexInfo)fieldIndexInfo;
valueChecks.add(new ValueCheck("map key type", keyType, this.wrapRaw(this.getTypeTokens(
jdb, this.startType, mapValueIndexInfo.getKeyFieldStorageId(), mapValueIndexInfo.getParentStorageId())),
mapValueIndexInfo.getKeyFieldType()));
}
// Check values
valueChecks.stream()
.map(check -> check.checkAndGetKeyRanges(jdb, startType, "index query on field `" + fieldName + "'"))
.forEach(this.filters::add);
}
// Constructor for composite index queries
IndexQueryInfo(Permazen jdb, Class> startType, String indexName, Class>... valueTypes) {
// Sanity check
Preconditions.checkArgument(jdb != null, "null jdb");
Preconditions.checkArgument(indexName != null, "null indexName");
Preconditions.checkArgument(valueTypes != null, "null valueTypes");
// Get start type
Preconditions.checkArgument(!startType.isPrimitive() && !startType.isArray(), "invalid startType " + startType);
this.startType = startType;
// Find index
CompositeIndexInfo compositeIndexInfo = IndexQueryInfo.findCompositeIndex(jdb, startType, indexName, valueTypes.length);
this.indexInfo = compositeIndexInfo;
// Verify field types
final ArrayList valueChecks = new ArrayList<>(valueTypes.length + 1);
for (int i = 0; i < valueTypes.length; i++) {
final Class> valueType = valueTypes[i];
valueChecks.add(new ValueCheck("value type #" + (i + 1), valueType,
this.wrapRaw(this.getTypeTokens(jdb, this.startType, compositeIndexInfo.getStorageIds().get(i))),
compositeIndexInfo.getFieldTypes().get(i)));
}
// Verify target type
valueChecks.add(new ValueCheck("target type", startType, startType));
// Check values
valueChecks.stream()
.map(check -> check.checkAndGetKeyRanges(jdb, startType, "query on composite index `" + indexName + "'"))
.forEach(this.filters::add);
}
private Set> getTypeTokens(Permazen jdb, Class> context, int storageId) {
return this.getTypeTokens(jdb, context, storageId, 0);
}
private Set> getTypeTokens(Permazen jdb, Class> context, int storageId, int parentStorageId) {
final HashSet> contextFieldTypes = new HashSet<>();
for (JClass> jclass : jdb.jclasses.values()) {
// Check if jclass is under consideration
if (!context.isAssignableFrom(jclass.type))
continue;
// Find the simple field field in jclass, if it exists
JSimpleField simpleField = null;
if (parentStorageId != 0) {
final JComplexField parentField = (JComplexField)jclass.jfields.get(parentStorageId);
if (parentField != null)
simpleField = parentField.getSubField(storageId);
} else
simpleField = (JSimpleField)jclass.jfields.get(storageId);
if (simpleField == null)
continue;
// Add field's type in jclass
contextFieldTypes.add(simpleField.typeToken);
}
if (contextFieldTypes.isEmpty()) {
throw new IllegalArgumentException("no sub-type of " + context
+ " contains and indexed simple field with storage ID " + storageId);
}
return Util.findLowestCommonAncestors(contextFieldTypes);
}
private Set> wrapRaw(Set> typeTokens) {
final HashSet> classes = new HashSet<>(typeTokens.size());
for (TypeToken> typeToken : typeTokens)
classes.add(typeToken.wrap().getRawType());
return classes;
}
private static CompositeIndexInfo findCompositeIndex(Permazen jdb, Class> startType, String indexName, int numValues) {
CompositeIndexInfo indexInfo = null;
for (JClass> jclass : jdb.getJClasses(startType)) {
final JCompositeIndex index = jclass.jcompositeIndexesByName.get(indexName);
if (index != null) {
final CompositeIndexInfo candidate = jdb.getIndexInfo(index.storageId, CompositeIndexInfo.class);
if (indexInfo != null && !candidate.equals(indexInfo)) {
throw new IllegalArgumentException("ambiguous composite index name `" + indexName
+ "': multiple incompatible composite indexes with that name exist on sub-types of " + startType.getName());
}
indexInfo = candidate;
}
}
if (indexInfo == null) {
throw new IllegalArgumentException("no composite index named `" + indexName
+ "' exists on any sub-type of " + startType.getName());
}
if (numValues != indexInfo.getFieldTypes().size()) {
throw new IllegalArgumentException("composite index `" + indexName
+ "' on " + startType.getName() + " has " + indexInfo.getFieldTypes().size() + " fields, not " + numValues);
}
return indexInfo;
}
// Public Methods
public CoreIndex applyFilters(CoreIndex index) {
for (int i = 0; i < this.filters.size(); i++) {
final KeyRanges filter = this.filters.get(i);
if (filter != null && !filter.isFull())
index = index.filter(i, filter);
}
return index;
}
public CoreIndex2 applyFilters(CoreIndex2 index) {
for (int i = 0; i < this.filters.size(); i++) {
final KeyRanges filter = this.filters.get(i);
if (filter != null && !filter.isFull())
index = index.filter(i, filter);
}
return index;
}
public CoreIndex3 applyFilters(CoreIndex3 index) {
for (int i = 0; i < this.filters.size(); i++) {
final KeyRanges filter = this.filters.get(i);
if (filter != null && !filter.isFull())
index = index.filter(i, filter);
}
return index;
}
public CoreIndex4 applyFilters(CoreIndex4 index) {
for (int i = 0; i < this.filters.size(); i++) {
final KeyRanges filter = this.filters.get(i);
if (filter != null && !filter.isFull())
index = index.filter(i, filter);
}
return index;
}
// COMPOSITE-INDEX
@Override
public String toString() {
return "IndexQueryInfo"
+ "[startType=" + this.startType
+ ",indexInfo=" + this.indexInfo
+ ",filters=" + this.filters + "]";
}
// ValueCheck
private static class ValueCheck {
private final String description;
private final Class> actualType;
private final Set> expectedTypes;
private final boolean reference;
private final boolean matchNull;
// Primary constructor
ValueCheck(String description, Class> actualType, Set> expectedTypes, boolean reference, boolean matchNull) {
this.description = description;
this.actualType = actualType;
this.expectedTypes = expectedTypes;
this.reference = reference;
this.matchNull = matchNull;
}
// Constructor for indexed fields
ValueCheck(String description, Class> actualType, Set> expectedTypes, FieldType> fieldType) {
this(description, actualType, expectedTypes, fieldType instanceof ReferenceFieldType, true);
}
// Constructor for target type (simple index)
ValueCheck(String description, Class> actualType, Set> expectedTypes) {
this(description, actualType, expectedTypes, true, false);
}
// Constructor for target type (composite index)
ValueCheck(String description, Class> actualType, Class> expectedType) {
this(description, actualType, Collections.>singleton(expectedType));
}
public KeyRanges checkAndGetKeyRanges(Permazen jdb, Class> startType, String queryDescription) {
// Check whether actual type matches expected type. For non-reference types, we allow any super-type; for reference
// types, we allow any super-type or any sub-type (and in the latter case, we apply corresponding key filters).
boolean match = this.expectedTypes.contains(this.actualType)
|| this.expectedTypes.contains(TypeToken.of(this.actualType).wrap().getRawType());
if (!match) {
for (Class> expectedType : this.expectedTypes) {
if (this.actualType.isAssignableFrom(expectedType)
|| (this.reference && expectedType.isAssignableFrom(this.actualType))) {
match = true;
break;
}
}
}
if (!match) {
final StringBuilder expectedTypesDescription = new StringBuilder();
if (this.expectedTypes.size() == 1)
expectedTypesDescription.append(this.expectedTypes.iterator().next().getName());
else {
for (Class> expectedType : this.expectedTypes) {
expectedTypesDescription.append(expectedTypesDescription.length() == 0 ? "one or more of: " : ", ");
expectedTypesDescription.append(expectedType.getName());
}
}
throw new IllegalArgumentException("invalid " + this.description + " " + actualType.getName()
+ " for " + queryDescription + " in " + startType + ": should be a super-type"
+ (this.reference ? " or sub-type" : "") + " of " + expectedTypesDescription);
}
// For non reference fields, we don't have any restrictions on 'type'
if (!this.reference)
return null;
// If actual type includes all JObject's no filter is necessary
if (actualType.isAssignableFrom(JObject.class))
return null;
// Create a filter for the actual type
final KeyRanges filter = jdb.keyRangesFor(this.actualType);
// For values other than the target value, we need to also accept null values in the index
if (this.matchNull)
filter.add(NULL_RANGE);
// Done
return filter;
}
@Override
public String toString() {
return "ValueCheck"
+ "[description=\"" + this.description + "\""
+ ",actualType=" + this.actualType.getSimpleName()
+ ",expectedTypes=" + this.expectedTypes
+ ",reference=" + this.reference
+ ",matchNull=" + this.matchNull
+ "]";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy