Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.engine.table.impl.hierarchical;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import io.deephaven.api.ColumnName;
import io.deephaven.base.log.LogOutput;
import io.deephaven.base.verify.Assert;
import io.deephaven.chunk.*;
import io.deephaven.chunk.attributes.Any;
import io.deephaven.chunk.attributes.Values;
import io.deephaven.engine.context.ExecutionContext;
import io.deephaven.engine.liveness.LivenessArtifact;
import io.deephaven.engine.liveness.LivenessScopeStack;
import io.deephaven.engine.rowset.*;
import io.deephaven.engine.table.*;
import io.deephaven.engine.table.hierarchical.HierarchicalTable;
import io.deephaven.engine.table.impl.*;
import io.deephaven.engine.table.impl.remote.ConstructSnapshot;
import io.deephaven.engine.table.impl.remote.ConstructSnapshot.SnapshotControl;
import io.deephaven.engine.table.impl.sources.immutable.ImmutableConstantIntSource;
import io.deephaven.engine.table.iterators.ByteColumnIterator;
import io.deephaven.engine.table.iterators.ChunkedByteColumnIterator;
import io.deephaven.engine.table.iterators.ChunkedColumnIterator;
import io.deephaven.engine.table.iterators.ColumnIterator;
import io.deephaven.engine.updategraph.NotificationQueue;
import io.deephaven.hash.*;
import io.deephaven.internal.log.LoggerFactory;
import io.deephaven.io.logger.Logger;
import io.deephaven.util.SafeCloseable;
import io.deephaven.util.SafeCloseableArray;
import io.deephaven.util.datastructures.linked.IntrusiveDoublyLinkedNode;
import io.deephaven.util.datastructures.linked.IntrusiveDoublyLinkedQueue;
import org.apache.commons.lang3.mutable.MutableObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Function;
import java.util.function.LongUnaryOperator;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static io.deephaven.engine.rowset.RowSequence.NULL_ROW_KEY;
import static io.deephaven.engine.table.impl.hierarchical.HierarchicalTableImpl.LevelExpandable.None;
import static io.deephaven.engine.table.impl.hierarchical.HierarchicalTableImpl.VisitAction.*;
import static io.deephaven.engine.table.impl.remote.ConstructSnapshot.*;
import static io.deephaven.util.BooleanUtils.*;
import static io.deephaven.util.QueryConstants.*;
/**
* Base result class for operations that produce hierarchical tables, for example {@link Table#rollup rollup} and
* {@link Table#tree(String, String) tree}.
*/
abstract class HierarchicalTableImpl, IMPL_TYPE extends HierarchicalTableImpl>
extends BaseGridAttributes
implements HierarchicalTable {
private static final Logger log = LoggerFactory.getLogger(HierarchicalTableImpl.class);
static final int EXTRA_COLUMN_COUNT = 2;
static final ColumnName ROW_DEPTH_COLUMN = ColumnName.of("__DEPTH__");
static final int ROW_DEPTH_COLUMN_INDEX = 0;
static final ColumnDefinition ROW_DEPTH_COLUMN_DEFINITION =
ColumnDefinition.ofInt(ROW_DEPTH_COLUMN.name());
static final ColumnName ROW_EXPANDED_COLUMN = ColumnName.of("__EXPANDED__");
static final int ROW_EXPANDED_COLUMN_INDEX = 1;
private static final ColumnDefinition ROW_EXPANDED_COLUMN_DEFINITION =
ColumnDefinition.ofBoolean(ROW_EXPANDED_COLUMN.name());
static final List> STRUCTURAL_COLUMN_DEFINITIONS =
List.of(ROW_DEPTH_COLUMN_DEFINITION, ROW_EXPANDED_COLUMN_DEFINITION);
private static final int CHUNK_SIZE = 512;
@SuppressWarnings("unchecked")
private static volatile ColumnSource[] cachedDepthSources =
ColumnSource.ZERO_LENGTH_COLUMN_SOURCE_ARRAY;
/**
* The source table that operations were applied to in order to produce this hierarchical table.
*/
final QueryTable source;
/**
* The root node of the hierarchy.
*/
private final QueryTable root;
protected HierarchicalTableImpl(
@NotNull final Map initialAttributes,
@NotNull final QueryTable source,
@NotNull final QueryTable root) {
super(initialAttributes);
this.source = source;
this.root = root;
}
@Override
public Table getSource() {
return source;
}
@Override
public Table getRoot() {
return root;
}
@Override
public ColumnName getRowExpandedColumn() {
return ROW_EXPANDED_COLUMN;
}
@Override
public ColumnName getRowDepthColumn() {
return ROW_DEPTH_COLUMN;
}
@Override
public List> getStructuralColumnDefinitions() {
return STRUCTURAL_COLUMN_DEFINITIONS;
}
@Override
public HierarchicalTable.SnapshotState makeSnapshotState() {
return new SnapshotStateImpl();
}
IFACE_TYPE noopResult() {
if (getSource().isRefreshing()) {
manageWithCurrentScope();
}
// noinspection unchecked
return (IFACE_TYPE) this;
}
@Override
protected final void checkAvailableColumns(@NotNull final Collection columns) {
root.getDefinition().checkHasColumns(columns);
}
/**
* Re-usable {@link HierarchicalTable.SnapshotState} implementation, used for keeping track of clock information and
* node caching.
*/
final class SnapshotStateImpl extends LivenessArtifact implements HierarchicalTable.SnapshotState {
private final KeyedLongObjectHashMap nodeTableStates =
new KeyedLongObjectHashMap<>(new NodeTableStateIdKey());
private final TIntObjectMap perLevelFillContextArrays = new TIntObjectHashMap<>();
private final TIntObjectMap perLevelSharedContexts = new TIntObjectHashMap<>();
// region Per-snapshot parameters and state
private BitSet columns;
private long firstRowPositionToInclude = NULL_LONG;
private int targetIncludedSize = NULL_INT;
private WritableChunk super Values>[] destinations;
private ResettableWritableChunk[] destinationSlices;
private WritableByteChunk super Values> expandedDestination;
ResettableWritableByteChunk expandedDestinationSlice;
// endregion Per-snapshot parameters and state
// region Per-attempt and intra-attempt parameters and state
private boolean usePrev;
private int currentDepth = NULL_INT;
private boolean expandingAll;
private long visitedSize = NULL_LONG;
private int includedSize = NULL_INT;
// endregion Per-attempt and intra-attempt parameters and state
/**
* Logical clock, incremented per-attempt, used to decide which node table states can be released upon success.
*/
private int snapshotClock = 0;
private SnapshotStateImpl() {
if (HierarchicalTableImpl.this.getSource().isRefreshing()) {
manage(HierarchicalTableImpl.this);
}
}
@Nullable
NodeTableState getNodeTableState(final long nodeId) {
final NodeTableState existing = nodeTableStates.get(nodeId);
if (existing != null) {
return existing;
}
final Table base = nodeIdToNodeBaseTable(nodeId);
if (base == null) {
return null;
}
final NodeTableState created = new NodeTableState(nodeId, base);
nodeTableStates.put(nodeId, created);
return created;
}
@NotNull
private ChunkSource.FillContext[] getFillContextArrayForLevel() {
ChunkSource.FillContext[] result = perLevelFillContextArrays.get(currentDepth);
if (result == null) {
perLevelFillContextArrays.put(currentDepth, result = new ChunkSource.FillContext[destinations.length]);
}
return result;
}
@NotNull
private SharedContext getSharedContextForLevel() {
SharedContext result = perLevelSharedContexts.get(currentDepth);
if (result == null) {
perLevelSharedContexts.put(currentDepth, result = SharedContext.makeSharedContext());
}
return result;
}
/**
* Initialize structures that will be used across multiple attempts with the same inputs.
*
* @param columns The included columns, or {@code null} to include all
* @param rows The included row positions from the total set of expanded rows
* @param destinations The destination writable chunks (which should be in reinterpreted-to-primitive types)
* @return A {@link SafeCloseable} that will clean up pooled state that was allocated for this snapshot
*/
private SafeCloseable initializeSnapshot(
@Nullable final BitSet columns,
@NotNull final RowSequence rows,
@NotNull final WritableChunk super Values>[] destinations) {
this.columns = Objects.requireNonNullElseGet(columns, () -> {
// This is a bit of a hack; we're assuming that destinations map to all possible columns. This would
// also work correctly for a prefix of the columns, but that doesn't seem like a terrible side effect.
final BitSet result = new BitSet(destinations.length);
result.set(0, destinations.length);
return result;
});
firstRowPositionToInclude = rows.firstRowKey();
targetIncludedSize = rows.intSize("hierarchical table snapshot");
this.destinations = destinations;
// noinspection unchecked
destinationSlices = Arrays.stream(destinations)
.peek(dc -> dc.setSize(targetIncludedSize))
.map(Chunk::getChunkType)
.map(ChunkType::makeResettableWritableChunk)
.toArray(ResettableWritableChunk[]::new);
expandedDestination = destinations[ROW_EXPANDED_COLUMN_INDEX].asWritableByteChunk();
expandedDestinationSlice = destinationSlices[ROW_EXPANDED_COLUMN_INDEX].asResettableWritableByteChunk();
return this::releaseSnapshotResources;
}
BitSet getColumns() {
return columns;
}
/**
* Initialize state fields for the next snapshot attempt.
*/
private void beginSnapshotAttempt(final boolean usePrev) {
this.usePrev = usePrev;
currentDepth = 0; // We always start at the root depth, which is zero by convention
expandingAll = false;
visitedSize = 0;
includedSize = 0;
snapshotClock++;
}
boolean usePrev() {
return usePrev;
}
int getCurrentDepth() {
return currentDepth;
}
private boolean filling() {
return includedSize < targetIncludedSize;
}
private long remainingToSkip() {
return Math.max(0, firstRowPositionToInclude - visitedSize);
}
private int remainingToFill() {
return targetIncludedSize - includedSize;
}
private void updateExpansionTypeAndValidateAction(
@NotNull final VisitAction action,
final boolean levelExpandable) {
switch (action) {
case Expand:
// There are two reasonable ways to handle "expand" directives when currently expanding-all:
// (1) treat them like "linkage" directives, or
// (2) have them turn off expand-all for the subtree rooted at their node.
// We've opted for option 2, as it creates a mechanism for coarse-grained control of expansions
// that is richer than only supporting fine-grained contraction when under expand-all.
expandingAll = false;
break;
case ExpandAll:
// Ignore moot directives to expand-all when we've reached a leaf
expandingAll = levelExpandable;
break;
case Linkage:
Assert.assertion(expandingAll, "expanding all",
"visited a linkage node when not expanding all");
if (expandingAll) {
// If we're at a leaf, be sure to not try to expand children
expandingAll = levelExpandable;
}
break;
case Contract:
// noinspection ThrowableNotThrown
Assert.statementNeverExecuted("visit contraction node");
break;
case Undefined:
// noinspection ThrowableNotThrown
Assert.statementNeverExecuted("visit undefined node");
break;
}
}
/**
* Child-directive rules for traversal:
*
*
We should always ignore directives for children that don't occur in this node (the parent); they will
* have rowKeyInParentUnsorted == NULL_ROW_KEY. We should never see such a directive here.
*
We never expect to encounter any directives with action Undefined, that would imply a bug in
* {@link #linkKeyTableNodeDirectives(Collection, boolean)}.
*
If we're expanding all, we need to process all present and defined directives. We want to visit the nodes
* for all rows that don't have a directive with action Contract. If we're visiting a node with no directive, we
* visit with action Linkage.
*
If we're not expanding all, we can ignore directives with action Linkage and Contract; we just want to
* visit the nodes for directives with action Expand or ExpandAll.
*
*
* @param childDirective The {@link LinkedDirective} to evaluate
* @return Whether to skip {@code childDirective}
*/
private boolean shouldSkipChildDirective(@NotNull final LinkedDirective childDirective) {
final VisitAction action = childDirective.getAction();
if (action == Undefined) {
// noinspection ThrowableNotThrown
Assert.statementNeverExecuted("encounter undefined child directive during visit");
return true;
}
if (expandingAll) {
return false;
}
return action == Linkage || action == Contract;
}
private WritableByteChunk super Values> getExpandedDestinationToFill() {
return expandedDestinationSlice.resetFromChunk(expandedDestination, includedSize, remainingToFill());
}
/**
* Do post-snapshot maintenance upon successful snapshot, and set destinations sizes.
*
* @return The total number of expanded rows traversed
*/
private long finalizeSuccessfulSnapshot() {
final Iterator cachedNodesIter = nodeTableStates.iterator();
while (cachedNodesIter.hasNext()) {
final NodeTableState nodeTableState = cachedNodesIter.next();
if (!nodeTableState.visited(snapshotClock)) {
nodeTableState.release();
cachedNodesIter.remove();
}
}
Arrays.stream(destinations).forEach(dc -> dc.setSize(includedSize));
return visitedSize;
}
private void releaseSnapshotResources() {
usePrev = false;
currentDepth = NULL_INT;
expandingAll = false;
visitedSize = NULL_LONG;
includedSize = NULL_INT;
columns = null;
firstRowPositionToInclude = NULL_LONG;
targetIncludedSize = NULL_INT;
destinations = null;
Arrays.stream(destinationSlices).forEach(SafeCloseable::close);
destinationSlices = null;
expandedDestination = null;
expandedDestinationSlice = null;
perLevelFillContextArrays.clear();
perLevelSharedContexts.clear();
}
@Override
protected void destroy() {
super.destroy();
nodeTableStates.forEach(NodeTableState::release);
nodeTableStates.clear();
}
private void verifyOwner(@NotNull final HierarchicalTableImpl, ?> owner) {
if (HierarchicalTableImpl.this != owner) {
throw new UnsupportedOperationException(
"Snapshot state must only be used with the hierarchical table it was created from");
}
}
private final class NodeTableStateIdKey extends KeyedLongObjectKey.BasicStrict {
@Override
public long getLongKey(@NotNull final NodeTableState nodeTableState) {
return nodeTableState.id;
}
}
/**
* State tracking for node tables in this HierarchicalTableImpl.
*/
final class NodeTableState {
/**
* Node identifier, a type-specific identifier that uniquely maps to a single table node in the
* HierarchicalTable.
*/
private final long id;
/**
* The base table at this node, before any node-level operations are applied.
*/
private final Table base;
/**
* The table at this node, after any formatting or node-level filters have been applied.
*
* This table may be used to determine node size or traverse children where order is irrelevant. While
* formatting is not necessary for this use case, we apply it first in order to allow for memoization; we
* expect most views to share the same formatting given that it is not configurable from the UI.
*/
private Table filtered;
/**
* The table at this node, after any node-level sorting has been applied.
*/
private Table sorted;
/**
* The sort {@link LongUnaryOperator} that maps the appropriate unsorted row key space to {@link #sorted
* sorted's} outer row space.
*/
private LongUnaryOperator sortReverseLookup;
/**
* Chunk sources to be used for retrieving data from {@link #sorted sorted}.
*/
private ChunkSource.WithPrev extends Values>[] dataSources;
/**
* The last {@link SnapshotStateImpl#snapshotClock snapshot clock} value this node was visited on.
*/
private int visitedSnapshotClock;
private NodeTableState(final long nodeId, @NotNull final Table base) {
this.id = nodeId;
this.base = base;
// NB: No current implementation requires NodeTableState to ensure liveness for its base. If that
// changes, we'll need to add liveness retention for base here.
}
private void ensureFilteredCreated() {
if (filtered != null) {
return;
}
try (final SafeCloseable ignored = LivenessScopeStack.open()) {
filtered = applyNodeFormatsAndFilters(id, base);
if (filtered != base && filtered.isRefreshing()) {
filtered.retainReference();
}
}
}
private void ensureSortedCreated() {
ensureFilteredCreated();
if (sorted != null) {
return;
}
try (final SafeCloseable ignored = LivenessScopeStack.open()) {
sorted = applyNodeSorts(id, filtered);
if (sorted != filtered) {
// NB: We can safely assume that sorted != base if sorted != filtered
if (sorted.isRefreshing()) {
sorted.retainReference();
}
// NB: We could drop our reference to filtered here, but there's no real reason to do it now
// rather than in release
sortReverseLookup = SortOperation.getReverseLookup(filtered, sorted);
}
}
}
/**
* Ensure that we've prepared the node table with any structural modifications (e.g. formatting and
* filtering), and that the result is safe to access during this snapshot attempt. Initializes the data
* structures necessary for {@link #getTraversalTable()}.
*/
void ensurePreparedForTraversal() {
if (hasNodeFiltersToApply(id)) {
ensureFilteredCreated();
if (filtered != base) {
maybeWaitForSatisfaction(filtered);
}
}
visitedSnapshotClock = snapshotClock;
}
/**
* Ensure that we've prepared the node table with any structural modifications (e.g. formatting and
* filtering), or ordering modifications (e.g. sorting), and that the result is safe to access during this
* snapshot attempt. Initializes the data structures necessary for {@link #getTraversalTable()},
* {@link #getDataRetrievalTable()}, {@link #getDataSortReverseLookup()}, and {@link #getDataSources()}.
*/
private void ensurePreparedForDataRetrieval() {
ensureSortedCreated();
if (sorted != base) {
maybeWaitForSatisfaction(sorted);
}
visitedSnapshotClock = snapshotClock;
}
/**
* @return The node Table after any structural modifications (e.g. formatting and filtering) have been
* applied
* @apiNote {@link #ensurePreparedForTraversal()} or {@link #ensurePreparedForDataRetrieval()} must have
* been called previously during this snapshot attempt
*/
Table getTraversalTable() {
return filtered != null ? filtered : base;
}
/**
* @return The node Table after any structural (e.g. formatting and filtering) or ordering modifications
* (e.g. sorting) have been applied
* @apiNote {@link #ensurePreparedForDataRetrieval()} must have been called previously during this snapshot
* attempt
*/
private Table getDataRetrievalTable() {
return sorted;
}
/**
* If we're still trying to fill our destination chunks, we need to consider this node and possibly its
* descendants after applying any node sorts. If we're just traversing for our expanded size calculation, we
* proceed in arbitrary order.
*
* @param filling Whether the enclosing SnapshotStateImpl is still {@link #filling() filling} the snapshot
* chunks. Passed in order to ensure consistency for the duration of a "visit".
* @return The table to expand, which will be the (prepared) traversal table or data retrieval table
*/
private Table prepareAndGetTableForExpansion(final boolean filling) {
if (filling) {
// TODO (https://github.com/deephaven/deephaven-core/issues/3247): Avoid sorting when not needed to
// determine order in viewport
ensurePreparedForDataRetrieval();
return getDataRetrievalTable();
} else {
ensurePreparedForTraversal();
return getTraversalTable();
}
}
/**
* Filter irrelevant {@code childDirectives}, then stamp the remaining with their
* {@link LinkedDirective#rowKeyInParentSorted sorted row key}, and sort them into expansion order by the
* appropriate row key in parent. THIS MUTATES {@code childDirectives}!
*
* @param childDirectives The {@link LinkedDirective child directives} to filter, key, and sort
* @param filling Whether the enclosing SnapshotStateImpl is still {@link #filling() filling} the snapshot
* chunks. Passed in order to ensure consistency for the duration of a "visit".
*/
private void filterKeyAndSortChildDirectives(
@NotNull final List childDirectives,
final boolean filling) {
int numChildDirectives = childDirectives.size();
for (int cdi = 0; cdi < numChildDirectives;) {
final LinkedDirective childDirective = childDirectives.get(cdi);
if (shouldSkipChildDirective(childDirective)) {
childDirectives.set(cdi, childDirectives.get(numChildDirectives - 1));
childDirectives.remove(--numChildDirectives);
} else {
++cdi;
}
}
final LongUnaryOperator dataSortReverseLookup = filling
? getDataSortReverseLookup()
: null;
for (final LinkedDirective childDirective : childDirectives) {
childDirective.setRowKeyInParentSorted(dataSortReverseLookup);
}
childDirectives.sort(LinkedDirective.SORTED_ROW_KEY_COMPARATOR);
}
/**
* @return Redirections that need to be applied when determining child order in the result of
* {@link #getDataRetrievalTable()}
* @apiNote {@link #ensurePreparedForDataRetrieval()} must have been called previously during this snapshot
* attempt
*/
@Nullable
private LongUnaryOperator getDataSortReverseLookup() {
return sortReverseLookup;
}
/**
* @return The chunk sources that should be used when retrieving actual data from the result of
* {@link #getDataRetrievalTable()}, at their respective column indices; other column indices may
* contain {@code null}, or values cached from previous snapshots
* @apiNote The {@link SnapshotStateImpl#currentDepth current depth} must be up-to-date and
* {@link #ensurePreparedForDataRetrieval()} must have been called previously during this snapshot
* attempt. This call should only be made once per node per snapshot attempt.
*/
private ChunkSource.WithPrev extends Values>[] getDataSources() {
return dataSources = makeOrFillChunkSourceArray(SnapshotStateImpl.this, id, sorted, dataSources);
}
/**
* @param snapshotClock The {@link SnapshotStateImpl#snapshotClock snapshot clock} value
* @return Whether this NodeTableState was last visited in on the supplied snapshot clock value
*/
private boolean visited(final long snapshotClock) {
return snapshotClock == visitedSnapshotClock;
}
private void release() {
if (sorted != null && sorted != filtered && sorted.isRefreshing()) {
sorted.dropReference();
}
if (filtered != null && filtered != base && filtered.isRefreshing()) {
filtered.dropReference();
}
}
}
}
enum VisitAction {
/**
* Expand a node. Only expand descendants if they have their own directives with action {@code Expand} or
* {@code ExpandAll}.
*/
Expand,
/**
* Expand a node. Expand its descendants unless they have a directive with action {@code Contract}, or their
* direct parent has a directive with action {@code Expand} .
*/
ExpandAll,
/**
* Take no action. Allows descendant {@code Expand} or {@code Contract} directives to be linked to an ancestor
* with action {@code ExpandAll}.
*/
Linkage,
/**
* Don't expand a node or its descendants. Only meaningful for descendants of a node whose directive has action
* {@code ExpandAll}.
*/
Contract,
/**
* Placeholder action for a newly-created, as-yet-undefined directive. Used where {@code null} might suffice.
*/
Undefined;
static VisitAction lookup(final byte wireValue) {
switch (wireValue) {
case KEY_TABLE_ACTION_EXPAND:
return Expand;
case KEY_TABLE_ACTION_EXPAND_ALL:
return ExpandAll;
case KEY_TABLE_ACTION_CONTRACT:
return Contract;
default:
throw new IllegalArgumentException("Unrecognized key table action " + wireValue);
}
}
}
private static final LogOutput.ObjFormatter