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

io.deephaven.engine.table.impl.AbstractColumnSource Maven / Gradle / Ivy

There is a newer version: 0.37.1
Show newest version
/**
 * Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
 */
package io.deephaven.engine.table.impl;

import io.deephaven.base.string.cache.CharSequenceUtils;
import io.deephaven.base.verify.Assert;
import io.deephaven.chunk.attributes.Values;
import io.deephaven.chunk.Chunk;
import io.deephaven.chunk.WritableChunk;
import io.deephaven.engine.context.ExecutionContext;
import io.deephaven.engine.rowset.*;
import io.deephaven.engine.rowset.RowSetFactory;
import io.deephaven.engine.table.ColumnSource;
import io.deephaven.engine.table.impl.chunkfillers.ChunkFiller;
import io.deephaven.engine.table.impl.chunkfilter.ChunkFilter;
import io.deephaven.engine.table.impl.chunkfilter.ChunkMatchFilterFactory;
import io.deephaven.engine.table.impl.sources.UnboxedLongBackedColumnSource;
import io.deephaven.engine.updategraph.UpdateGraph;
import io.deephaven.vector.*;
import io.deephaven.hash.KeyedObjectHashSet;
import io.deephaven.hash.KeyedObjectKey;
import io.deephaven.util.annotations.VisibleForTesting;
import io.deephaven.util.type.TypeUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractColumnSource implements
        ColumnSource,
        DefaultChunkSource.WithPrev {

    /**
     * Minimum average run length in an {@link RowSequence} that should trigger {@link Chunk}-filling by key ranges
     * instead of individual keys.
     */
    public static final long USE_RANGES_AVERAGE_RUN_LENGTH = 5;

    protected final Class type;
    protected final Class componentType;

    protected final UpdateGraph updateGraph = ExecutionContext.getContext().getUpdateGraph();

    protected volatile Map groupToRange;
    protected volatile List> rowSetIndexerKey;

    protected AbstractColumnSource(@NotNull final Class type) {
        this(type, Object.class);
    }

    public AbstractColumnSource(@NotNull final Class type, @Nullable final Class elementType) {
        if (type == boolean.class) {
            // noinspection unchecked
            this.type = (Class) Boolean.class;
        } else if (type == Boolean.class) {
            this.type = type;
        } else {
            // noinspection rawtypes
            final Class unboxedType = TypeUtils.getUnboxedType(type);
            // noinspection unchecked
            this.type = unboxedType != null ? unboxedType : type;
        }
        if (type.isArray()) {
            componentType = type.getComponentType();
        } else if (Vector.class.isAssignableFrom(type)) {
            if (ByteVector.class.isAssignableFrom(type)) {
                componentType = byte.class;
            } else if (CharVector.class.isAssignableFrom(type)) {
                componentType = char.class;
            } else if (DoubleVector.class.isAssignableFrom(type)) {
                componentType = double.class;
            } else if (FloatVector.class.isAssignableFrom(type)) {
                componentType = float.class;
            } else if (IntVector.class.isAssignableFrom(type)) {
                componentType = int.class;
            } else if (LongVector.class.isAssignableFrom(type)) {
                componentType = long.class;
            } else if (ShortVector.class.isAssignableFrom(type)) {
                componentType = short.class;
            } else {
                componentType = elementType;
            }
        } else {
            componentType = null;
        }
    }

    @Override
    public Class getType() {
        return type;
    }

    @Override
    public Class getComponentType() {
        return componentType;
    }

    @Override
    public ColumnSource getPrevSource() {
        return new PrevColumnSource<>(this);
    }

    @Override
    public List> getColumnSources() {
        List> localRowSetIndexerKey;
        if ((localRowSetIndexerKey = rowSetIndexerKey) == null) {
            synchronized (this) {
                if ((localRowSetIndexerKey = rowSetIndexerKey) == null) {
                    rowSetIndexerKey = localRowSetIndexerKey = Collections.singletonList(this);
                }
            }
        }
        return localRowSetIndexerKey;
    }

    @Override
    public Map getGroupToRange() {
        return groupToRange;
    }

    @Override
    public Map getGroupToRange(RowSet rowSet) {
        return groupToRange;
    }

    public final void setGroupToRange(@Nullable Map groupToRange) {
        this.groupToRange = groupToRange;
    }

    @Override
    public WritableRowSet match(
            final boolean invertMatch,
            final boolean usePrev,
            final boolean caseInsensitive,
            @NotNull final RowSet mapper,
            final Object... keys) {
        final Map groupToRange = (isImmutable() || !usePrev) ? getGroupToRange(mapper) : null;
        if (groupToRange != null) {
            RowSetBuilderRandom allInMatchingGroups = RowSetFactory.builderRandom();

            if (caseInsensitive && (type == String.class)) {
                KeyedObjectHashSet keySet = new KeyedObjectHashSet<>(new CIStringKey());
                Collections.addAll(keySet, keys);

                for (Map.Entry ent : groupToRange.entrySet()) {
                    if (keySet.containsKey(ent.getKey())) {
                        allInMatchingGroups.addRowSet(ent.getValue());
                    }
                }
            } else {
                for (Object key : keys) {
                    RowSet range = groupToRange.get(key);
                    if (range != null) {
                        allInMatchingGroups.addRowSet(range);
                    }
                }
            }

            final WritableRowSet matchingValues;
            try (final RowSet matchingGroups = allInMatchingGroups.build()) {
                if (invertMatch) {
                    matchingValues = mapper.minus(matchingGroups);
                } else {
                    matchingValues = mapper.intersect(matchingGroups);
                }
            }
            return matchingValues;
        } else {
            return ChunkFilter.applyChunkFilter(mapper, this, usePrev,
                    ChunkMatchFilterFactory.getChunkFilter(type, caseInsensitive, invertMatch, keys));
        }
    }

    private static final class CIStringKey implements KeyedObjectKey {
        @Override
        public String getKey(String s) {
            return s;
        }

        @Override
        public int hashKey(String s) {
            return (s == null) ? 0 : CharSequenceUtils.caseInsensitiveHashCode(s);
        }

        @Override
        public boolean equalKey(String s, String s2) {
            return (s == null) ? s2 == null : s.equalsIgnoreCase(s2);
        }
    }

    @Override
    public Map getValuesMapping(RowSet subRange) {
        Map result = new LinkedHashMap<>();
        final Map groupToRange = getGroupToRange();

        // if we have a grouping we can use it to avoid iterating the entire subRange. The issue is that our grouping
        // could be bigger than the RowSet we care about, by a very large margin. In this case we could be spinning
        // on RowSet intersect operations that are actually useless. This check says that if our subRange is smaller
        // than the number of keys in our grouping, we should just fetch the keys instead and generate the grouping
        // from scratch.
        boolean useGroupToRange = (groupToRange != null) && (groupToRange.size() < subRange.size());
        if (useGroupToRange) {
            for (Map.Entry typeEntry : groupToRange.entrySet()) {
                RowSet mapping = subRange.intersect(typeEntry.getValue());
                if (mapping.size() > 0) {
                    result.put(typeEntry.getKey(), mapping);
                }
            }
        } else {
            Map valueToIndexSet = new LinkedHashMap<>();

            for (RowSet.Iterator it = subRange.iterator(); it.hasNext();) {
                long key = it.nextLong();
                T value = get(key);
                RowSetBuilderSequential indexes = valueToIndexSet.get(value);
                if (indexes == null) {
                    indexes = RowSetFactory.builderSequential();
                }
                indexes.appendKey(key);
                valueToIndexSet.put(value, indexes);
            }
            for (Map.Entry entry : valueToIndexSet.entrySet()) {
                result.put(entry.getKey(), entry.getValue().build());
            }
        }
        return result;
    }

    @Override
    public void fillChunk(@NotNull final FillContext context, @NotNull final WritableChunk destination,
            @NotNull final RowSequence rowSequence) {
        defaultFillChunk(context, destination, rowSequence);
    }

    @VisibleForTesting
    public final void defaultFillChunk(@SuppressWarnings("unused") @NotNull final FillContext context,
            @NotNull final WritableChunk destination,
            @NotNull final RowSequence rowSequence) {
        final ChunkFiller filler = ChunkFiller.forChunkType(destination.getChunkType());
        if (rowSequence.getAverageRunLengthEstimate() >= USE_RANGES_AVERAGE_RUN_LENGTH) {
            filler.fillByRanges(this, rowSequence, destination);
        } else {
            filler.fillByIndices(this, rowSequence, destination);
        }
    }

    @Override
    public void fillPrevChunk(@NotNull final FillContext context,
            @NotNull final WritableChunk destination, @NotNull final RowSequence rowSequence) {
        defaultFillPrevChunk(context, destination, rowSequence);
    }

    protected final void defaultFillPrevChunk(@SuppressWarnings("unused") @NotNull final FillContext context,
            @NotNull final WritableChunk destination,
            @NotNull final RowSequence rowSequence) {
        final ChunkFiller filler = ChunkFiller.forChunkType(destination.getChunkType());
        if (rowSequence.getAverageRunLengthEstimate() >= USE_RANGES_AVERAGE_RUN_LENGTH) {
            filler.fillPrevByRanges(this, rowSequence, destination);
        } else {
            filler.fillPrevByIndices(this, rowSequence, destination);
        }
    }

    @Override
    public  boolean allowsReinterpret(
            @NotNull final Class alternateDataType) {
        return false;
    }

    @Override
    public final  ColumnSource reinterpret(
            @NotNull final Class alternateDataType) throws IllegalArgumentException {
        if (!allowsReinterpret(alternateDataType)) {
            throw new IllegalArgumentException("Unsupported reinterpret for " + getClass().getSimpleName()
                    + ": type=" + getType()
                    + ", alternateDataType=" + alternateDataType);
        }
        return doReinterpret(alternateDataType);
    }

    /**
     * Supply allowed reinterpret results. The default implementation handles the most common case to avoid code
     * duplication.
     *
     * @param alternateDataType The alternate data type
     * @return The resulting {@link ColumnSource}
     */
    protected  ColumnSource doReinterpret(
            @NotNull final Class alternateDataType) {
        if (getType() == Instant.class || getType() == ZonedDateTime.class) {
            Assert.eq(alternateDataType, "alternateDataType", long.class);
            // noinspection unchecked
            return (ColumnSource) new UnboxedLongBackedColumnSource<>(this);
        }
        throw new IllegalArgumentException("Unsupported reinterpret for " + getClass().getSimpleName()
                + ": type=" + getType()
                + ", alternateDataType=" + alternateDataType);
    }

    public static abstract class DefaultedMutable extends AbstractColumnSource
            implements MutableColumnSourceGetDefaults.ForObject {

        protected DefaultedMutable(@NotNull final Class type) {
            super(type);
        }

        protected DefaultedMutable(@NotNull final Class type, @Nullable final Class elementType) {
            super(type, elementType);
        }
    }

    public static abstract class DefaultedImmutable extends AbstractColumnSource
            implements ImmutableColumnSourceGetDefaults.ForObject {

        protected DefaultedImmutable(@NotNull final Class type) {
            super(type);
        }

        protected DefaultedImmutable(@NotNull final Class type, @Nullable final Class elementType) {
            super(type, elementType);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy