io.deephaven.engine.table.impl.AbstractColumnSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of deephaven-engine-table Show documentation
Show all versions of deephaven-engine-table Show documentation
Engine Table: Implementation and closely-coupled utilities
/**
* 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 super Values> destination,
@NotNull final RowSequence rowSequence) {
defaultFillChunk(context, destination, rowSequence);
}
@VisibleForTesting
public final void defaultFillChunk(@SuppressWarnings("unused") @NotNull final FillContext context,
@NotNull final WritableChunk super Values> 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 super Values> destination, @NotNull final RowSequence rowSequence) {
defaultFillPrevChunk(context, destination, rowSequence);
}
protected final void defaultFillPrevChunk(@SuppressWarnings("unused") @NotNull final FillContext context,
@NotNull final WritableChunk super Values> 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