All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsimpledb.IndexInfo Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package org.jsimpledb;

import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;

import java.util.ArrayList;

import org.jsimpledb.core.CoreIndex;
import org.jsimpledb.core.CoreIndex2;
import org.jsimpledb.core.CoreIndex3;
import org.jsimpledb.core.CoreIndex4;
import org.jsimpledb.kv.KeyRange;
import org.jsimpledb.kv.KeyRanges;

/**
 * Information used for index queries.
 */
class IndexInfo {

    private static final KeyRange NULL_RANGE = new KeyRange(new byte[] { (byte)0xff }, null);

    // For simple indexes only
    final JSimpleFieldInfo fieldInfo;
    final JComplexFieldInfo superFieldInfo;

    // For composite indexes only
    final JCompositeIndexInfo indexInfo;

    private final Class startType;
    private final ArrayList filters = new ArrayList<>();

// Constructors

    // Constructor for regular simple index queries
    IndexInfo(JSimpleDB 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)
    IndexInfo(JSimpleDB 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");
        this.indexInfo = null;

        // 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);
        if (path.getReferenceFields().length > 0)
            throw new IllegalArgumentException("invalid field name `" + fieldName + "': contains intermediate reference(s)");

        // Verify target field is simple
        if (!(path.targetFieldInfo instanceof JSimpleFieldInfo))
            throw new IllegalArgumentException(path.targetFieldInfo + " does not support indexing; it is not a simple field");

        // Get target object, field, and complex super-field (if any)
        this.fieldInfo = (JSimpleFieldInfo)path.targetFieldInfo;
        this.superFieldInfo = path.targetSuperFieldInfo;

        // Verify the field is actually indexed
        if (!this.fieldInfo.isIndexed())
            throw new IllegalArgumentException(this.fieldInfo + " is not indexed");

        // Verify value type
        final ArrayList valueChecks = new ArrayList<>(3);
        valueChecks.add(new ValueCheck("value type", valueType, path.getTargetFieldType(), this.fieldInfo, true));

        // Verify target type
        valueChecks.add(new ValueCheck("target type", startType, path.targetType));

        // We should only ever see 'keyType' when field is a map value field
        if (keyType != null) {
            final JMapFieldInfo mapInfo = (JMapFieldInfo)this.superFieldInfo;
            final JSimpleFieldInfo keyInfo = mapInfo.getKeyFieldInfo();
            assert this.fieldInfo.equals(mapInfo.getValueFieldInfo());
            valueChecks.add(new ValueCheck("map key type", keyType, keyInfo.getTypeToken(this.startType), keyInfo, true));
        }

        // Check values
        for (ValueCheck check : valueChecks)
            this.filters.add(check.checkAndGetKeyRanges(jdb, startType, "index query on field `" + fieldName + "'"));
    }

    // Constructor for composite index queries
    IndexInfo(JSimpleDB 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");
        this.fieldInfo = null;
        this.superFieldInfo = null;

        // Get start type
        Preconditions.checkArgument(!startType.isPrimitive() && !startType.isArray(), "invalid startType " + startType);
        this.startType = startType;

        // Find index
        this.indexInfo = IndexInfo.findCompositeIndex(jdb, startType, indexName, valueTypes.length);

        // Verify field types
        final ArrayList valueChecks = new ArrayList<>(3);
        for (int i = 0; i < valueTypes.length; i++) {
            final Class valueType = valueTypes[i];
            final JSimpleFieldInfo jfieldInfo = indexInfo.jfieldInfos.get(i);
            valueChecks.add(new ValueCheck("value type #" + (i + 1), valueType,
              jfieldInfo.getTypeToken(this.startType), jfieldInfo instanceof JReferenceFieldInfo, true));
        }

        // Verify target type
        valueChecks.add(new ValueCheck("target type", startType, startType));

        // Check values
        for (ValueCheck check : valueChecks)
            this.filters.add(check.checkAndGetKeyRanges(jdb, startType, "query on composite index `" + indexName + "'"));
    }

    private static JCompositeIndexInfo findCompositeIndex(JSimpleDB jdb, Class startType, String indexName, int numValues) {
        JCompositeIndexInfo indexInfo = null;
        for (JClass jclass : jdb.getJClasses(startType)) {
            final JCompositeIndex index = jclass.jcompositeIndexesByName.get(indexName);
            if (index != null) {
                final JCompositeIndexInfo candidate = jdb.jcompositeIndexInfos.get(index.storageId);
                if (indexInfo != null && !candidate.equals(indexInfo)) {
                    throw new IllegalArgumentException("ambiguous composite index name `" + indexName
                      + "': multiple 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.jfieldInfos.size()) {
            throw new IllegalArgumentException("composite index `" + indexName
              + "' on " + startType.getName() + " has " + indexInfo.jfieldInfos.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 "IndexInfo"
          + "[startType=" + this.startType
          + (this.fieldInfo != null ? ",fieldInfo=" + this.fieldInfo : "")
          + (this.superFieldInfo != null ? ",superFieldInfo=" + this.superFieldInfo : "")
          + (this.indexInfo != null ? ",indexInfo=" + this.indexInfo : "")
          + ",filters=" + this.filters + "]";
    }

// ValueCheck

    private static class ValueCheck {

        private final String description;
        private final Class actualType;
        private final Class expectedType;
        private final boolean reference;
        private final boolean matchNull;

        ValueCheck(String description,
          Class actualType, TypeToken expectedType, boolean reference, boolean matchNull) {
            this.description = description;
            this.actualType = actualType;
            this.expectedType = expectedType.wrap().getRawType();
            this.reference = reference;
            this.matchNull = matchNull;
        }

        // Constructor for indexed fields
        ValueCheck(String description,
          Class actualType, TypeToken expectedType, JSimpleFieldInfo fieldInfo, boolean matchNull) {
            this(description, actualType, expectedType, fieldInfo instanceof JReferenceFieldInfo, matchNull);
        }

        // Constructor for target type
        ValueCheck(String description, Class actualType, Class expectedType) {
            this(description, actualType, TypeToken.of(expectedType), true, false);
        }

        public KeyRanges checkAndGetKeyRanges(JSimpleDB jdb, Class startType, String queryDescription) {

            // Check expected type
            final boolean equal = this.expectedType.equals(this.actualType);
            final boolean comparable = equal
              || this.expectedType.isAssignableFrom(this.actualType) || this.actualType.isAssignableFrom(this.expectedType);
            if (!(this.reference ? comparable : equal)) {
                throw new IllegalArgumentException("invalid " + this.description + " " + actualType.getName()
                  + " for " + queryDescription + " in " + startType + ": should be "
                  + (this.reference ? "a super-type or sub-type of " : "") + this.expectedType.getName());
            }

            // 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;
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy