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

org.dizitart.no2.index.CompoundIndex Maven / Gradle / Ivy

/*
 * Copyright (c) 2017-2021 Nitrite author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.dizitart.no2.index;

import lombok.Getter;
import org.dizitart.no2.collection.FindPlan;
import org.dizitart.no2.collection.NitriteId;
import org.dizitart.no2.common.DBNull;
import org.dizitart.no2.common.DBValue;
import org.dizitart.no2.common.FieldValues;
import org.dizitart.no2.common.Fields;
import org.dizitart.no2.common.tuples.Pair;
import org.dizitart.no2.exceptions.IndexingException;
import org.dizitart.no2.filters.ComparableFilter;
import org.dizitart.no2.store.NitriteMap;
import org.dizitart.no2.store.NitriteStore;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

import static org.dizitart.no2.common.util.IndexUtils.deriveIndexMapName;
import static org.dizitart.no2.common.util.ObjectUtils.convertToObjectArray;

/**
 * @author Anindya Chatterjee
 * @since 4.0
 */
public class CompoundIndex implements NitriteIndex {
    @Getter
    private final IndexDescriptor indexDescriptor;
    private final NitriteStore nitriteStore;

    /**
     * Instantiates a new Compound index.
     *
     * @param indexDescriptor the index descriptor
     * @param nitriteStore    the nitrite store
     */
    public CompoundIndex(IndexDescriptor indexDescriptor, NitriteStore nitriteStore) {
        this.indexDescriptor = indexDescriptor;
        this.nitriteStore = nitriteStore;
    }

    public void write(FieldValues fieldValues) {
        Fields fields = fieldValues.getFields();
        List fieldNames = fields.getFieldNames();

        String firstField = fieldNames.get(0);
        Object firstValue = fieldValues.get(firstField);

        // NOTE: only first field can have array or iterable value, subsequent fields can not
        validateIndexField(firstValue, firstField);

        NitriteMap> indexMap = findIndexMap();
        if (firstValue == null) {
            addIndexElement(indexMap, fieldValues, DBNull.getInstance());
        } else if (firstValue instanceof Comparable) {
            //wrap around a db value
            DBValue dbValue = new DBValue((Comparable) firstValue);
            addIndexElement(indexMap, fieldValues, dbValue);
        } else if (firstValue.getClass().isArray()) {
            Object[] array = convertToObjectArray(firstValue);

            for (Object item : array) {
                // wrap around db value
                DBValue dbValue = item == null ? DBNull.getInstance() : new DBValue((Comparable) item);
                addIndexElement(indexMap, fieldValues, dbValue);
            }
        } else if (firstValue instanceof Iterable) {
            Iterable iterable = (Iterable) firstValue;

            for (Object item : iterable) {
                // wrap around db value
                DBValue dbValue = item != null ? new DBValue((Comparable) item) : DBNull.getInstance();
                addIndexElement(indexMap, fieldValues, dbValue);
            }
        }
    }

    @Override
    public void remove(FieldValues fieldValues) {
        Fields fields = fieldValues.getFields();
        List fieldNames = fields.getFieldNames();

        String firstField = fieldNames.get(0);
        Object firstValue = fieldValues.get(firstField);

        // NOTE: only first field can have array or iterable value, subsequent fields can not
        validateIndexField(firstValue, firstField);
        NitriteMap> indexMap = findIndexMap();

        if (firstValue == null) {
            removeIndexElement(indexMap, fieldValues, DBNull.getInstance());
        } else if (firstValue instanceof Comparable) {
            // wrap around db value
            DBValue dbValue = new DBValue((Comparable) firstValue);
            removeIndexElement(indexMap, fieldValues, dbValue);
        } else if (firstValue.getClass().isArray()) {
            Object[] array = convertToObjectArray(firstValue);

            for (Object item : array) {
                // wrap around db value
                DBValue dbValue = item == null ? DBNull.getInstance() : new DBValue((Comparable) item);
                removeIndexElement(indexMap, fieldValues, dbValue);
            }
        } else if (firstValue instanceof Iterable) {
            Iterable iterable = (Iterable) firstValue;

            for (Object item : iterable) {
                // wrap around db value
                DBValue dbValue = item != null ? new DBValue((Comparable) item) : DBNull.getInstance();
                removeIndexElement(indexMap, fieldValues, dbValue);
            }
        }
    }

    @Override
    public void drop() {
        NitriteMap> indexMap = findIndexMap();
        indexMap.clear();
        indexMap.drop();
    }

    @Override
    public LinkedHashSet findNitriteIds(FindPlan findPlan) {
        if (findPlan.getIndexScanFilter() == null) return new LinkedHashSet<>();

        NitriteMap> indexMap = findIndexMap();
        return scanIndex(findPlan, indexMap);
    }

    private void addIndexElement(NitriteMap> indexMap,
                                 FieldValues fieldValues, DBValue element) {
        NavigableMap subMap = indexMap.get(element);
        if (subMap == null) {
            // index are always in ascending order
            subMap = new ConcurrentSkipListMap<>();
        }

        populateSubMap(subMap, fieldValues, 1);
        indexMap.put(element, subMap);
    }

    private void removeIndexElement(NitriteMap> indexMap,
                                    FieldValues fieldValues, DBValue element) {
        NavigableMap subMap = indexMap.get(element);
        if (subMap != null && !subMap.isEmpty()) {
            deleteFromSubMap(subMap, fieldValues, 1);
            indexMap.put(element, subMap);
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void populateSubMap(NavigableMap subMap, FieldValues fieldValues, int depth) {
        if (depth >= fieldValues.getValues().size()) return;

        Pair pair = fieldValues.getValues().get(depth);
        Object value = pair.getSecond();
        DBValue dbValue;
        if (value == null) {
            dbValue = DBNull.getInstance();
        } else {
            if (Iterable.class.isAssignableFrom(value.getClass()) || value.getClass().isArray()) {
                throw new IndexingException("Compound multikey index is supported on the first field of the index only");
            }

            if (!(value instanceof Comparable)) {
                throw new IndexingException(value + " is not a comparable type");
            }
            dbValue = new DBValue((Comparable) value);
        }

        if (depth == fieldValues.getValues().size() - 1) {
            // terminal field
            List nitriteIds = (List) subMap.get(dbValue);
            nitriteIds = addNitriteIds(nitriteIds, fieldValues);
            subMap.put(dbValue, nitriteIds);
        } else {
            // intermediate fields
            NavigableMap subMap2 = (NavigableMap) subMap.get(dbValue);
            if (subMap2 == null) {
                // index are always in ascending order
                subMap2 = new ConcurrentSkipListMap<>();
            }

            subMap.put(dbValue, subMap2);
            populateSubMap(subMap2, fieldValues, depth + 1);
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void deleteFromSubMap(NavigableMap subMap, FieldValues fieldValues, int depth) {
        Pair pair = fieldValues.getValues().get(depth);
        Object value = pair.getSecond();
        DBValue dbValue;
        if (value == null) {
            dbValue = DBNull.getInstance();
        } else {
            if (!(value instanceof Comparable)) {
                return;
            }
            dbValue = new DBValue((Comparable) value);
        }

        if (depth == fieldValues.getValues().size() - 1) {
            // terminal field
            List nitriteIds = (List) subMap.get(dbValue);
            nitriteIds = removeNitriteIds(nitriteIds, fieldValues);
            if (nitriteIds == null || nitriteIds.isEmpty()) {
                subMap.remove(dbValue);
            } else {
                subMap.put(dbValue, nitriteIds);
            }
        } else {
            // intermediate fields
            NavigableMap subMap2 = (NavigableMap) subMap.get(dbValue);
            if (subMap2 == null) {
                return;
            }

            deleteFromSubMap(subMap2, fieldValues, depth + 1);
            subMap.put(dbValue, subMap2);
        }
    }

    private NitriteMap> findIndexMap() {
        String mapName = deriveIndexMapName(indexDescriptor);
        return nitriteStore.openMap(mapName, DBValue.class, ConcurrentSkipListMap.class);
    }

    private LinkedHashSet scanIndex(FindPlan findPlan,
                                               NitriteMap> indexMap) {
        List filters = findPlan.getIndexScanFilter().getFilters();
        IndexMap iMap = new IndexMap(indexMap);
        IndexScanner indexScanner = new IndexScanner(iMap);
        return indexScanner.doScan(filters, findPlan.getIndexScanOrder());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy