io.deephaven.engine.table.impl.hierarchical.TreeTableImpl 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
package io.deephaven.engine.table.impl.hierarchical;
import io.deephaven.api.ColumnName;
import io.deephaven.api.agg.Partition;
import io.deephaven.api.filter.Filter;
import io.deephaven.base.verify.Assert;
import io.deephaven.chunk.attributes.Values;
import io.deephaven.engine.rowset.RowSetFactory;
import io.deephaven.engine.table.*;
import io.deephaven.engine.table.hierarchical.RollupTable;
import io.deephaven.engine.table.hierarchical.TreeTable;
import io.deephaven.engine.table.impl.*;
import io.deephaven.engine.table.impl.by.AggregationProcessor;
import io.deephaven.engine.table.impl.by.AggregationRowLookup;
import io.deephaven.engine.table.impl.select.WhereFilter;
import io.deephaven.engine.table.impl.sources.NullValueColumnSource;
import org.apache.commons.lang3.mutable.MutableObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.LongUnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.deephaven.engine.rowset.RowSequence.NULL_ROW_KEY;
import static io.deephaven.engine.table.impl.BaseTable.shouldCopyAttribute;
import static io.deephaven.engine.table.impl.hierarchical.HierarchicalTableImpl.LevelExpandable.Undetermined;
import static io.deephaven.engine.table.impl.sources.ReinterpretUtils.maybeConvertToPrimitive;
/**
* {@link RollupTable} implementation.
*/
public class TreeTableImpl extends HierarchicalTableImpl implements TreeTable {
private static final ColumnName TREE_COLUMN = ColumnName.of("__TREE__");
private final ColumnSource> sourceParentIdSource;
private final QueryTable tree;
private final AggregationRowLookup treeRowLookup;
private final ColumnSource treeNodeTableSource;
private final TreeSourceRowLookup sourceRowLookup;
private final boolean filtered;
private final ColumnName identifierColumn;
private final ColumnName parentIdentifierColumn;
private final Set nodeFilterColumns;
private final TreeNodeOperationsRecorder nodeOperations;
private final List> availableColumnDefinitions;
private TreeTableImpl(
@NotNull final Map initialAttributes,
@NotNull final QueryTable source,
@NotNull final QueryTable tree,
@NotNull final TreeSourceRowLookup sourceRowLookup,
@NotNull final ColumnName identifierColumn,
@NotNull final ColumnName parentIdentifierColumn,
@NotNull final Set nodeFilterColumns,
@Nullable final TreeNodeOperationsRecorder nodeOperations,
@Nullable final List> availableColumnDefinitions) {
super(initialAttributes, source, getTreeRoot(tree));
if (source.isRefreshing()) {
manage(tree);
manage(sourceRowLookup);
}
sourceParentIdSource = source.getColumnSource(parentIdentifierColumn.name());
this.tree = tree;
treeRowLookup = AggregationProcessor.getRowLookup(tree);
treeNodeTableSource = tree.getColumnSource(TREE_COLUMN.name(), Table.class);
this.sourceRowLookup = sourceRowLookup;
filtered = !sourceRowLookup.sameSource(source);
this.identifierColumn = identifierColumn;
this.parentIdentifierColumn = parentIdentifierColumn;
this.nodeFilterColumns = nodeFilterColumns;
this.nodeOperations = nodeOperations;
this.availableColumnDefinitions = availableColumnDefinitions == null
? computeAvailableColumnDefinitions(getNodeDefinition())
: availableColumnDefinitions;
if (source.isRefreshing()) {
Assert.assertion(tree.isRefreshing(), "tree.isRefreshing() if source.isRefreshing()");
// The tree aggregation result depends on the source and all the node tables.
manage(tree);
// The reverse lookup just depends on the (original, unfiltered) source, which may not be our direct source.
manage(sourceRowLookup);
}
}
@Override
public String getDescription() {
return "TreeTable(" + source.getDescription()
+ ", " + identifierColumn.name()
+ ", " + parentIdentifierColumn.name()
+ ")";
}
@Override
public Table getEmptyExpansionsTable() {
return makeNullSingleColumnTable(getSource(), getIdentifierColumn(), 0);
}
private static Table makeNullSingleColumnTable(
@NotNull final Table source,
@NotNull final ColumnName column,
final long size) {
final ColumnDefinition columnDefinition = source.getDefinition().getColumn(column.name());
final TableDefinition columnOnlyTableDefinition = TableDefinition.of(columnDefinition);
// noinspection resource
return new QueryTable(columnOnlyTableDefinition, RowSetFactory.flat(size).toTracking(),
NullValueColumnSource.createColumnSourceMap(columnOnlyTableDefinition), null, null);
}
@Override
public ColumnName getIdentifierColumn() {
return identifierColumn;
}
@Override
public ColumnName getParentIdentifierColumn() {
return parentIdentifierColumn;
}
@Override
public TableDefinition getNodeDefinition() {
return nodeOperations == null ? source.getDefinition() : nodeOperations.getResultDefinition();
}
@Override
public List> getAvailableColumnDefinitions() {
return availableColumnDefinitions;
}
private static List> computeAvailableColumnDefinitions(
@NotNull final TableDefinition nodeDefinition) {
return Stream.concat(STRUCTURAL_COLUMN_DEFINITIONS.stream(), nodeDefinition.getColumnStream())
.collect(Collectors.toList());
}
@Override
public TreeTable withNodeFilterColumns(@NotNull final Collection extends ColumnName> columns) {
final Set resultNodeFilterColumns = new HashSet<>(nodeFilterColumns);
resultNodeFilterColumns.addAll(columns);
return new TreeTableImpl(getAttributes(), source, tree, sourceRowLookup, identifierColumn,
parentIdentifierColumn,
Collections.unmodifiableSet(resultNodeFilterColumns), nodeOperations, availableColumnDefinitions);
}
@Override
public TreeTable withFilter(@NotNull Filter filter) {
final WhereFilter[] whereFilters = WhereFilter.fromInternal(filter);
if (whereFilters.length == 0) {
return noopResult();
}
final Map> nodeSuitabilityToFilters = Stream.of(whereFilters)
.peek(wf -> wf.init(source.getDefinition()))
.collect(Collectors.partitioningBy(wf -> {
// Node-level filters have only node-filter columns and use no column arrays
return wf.getColumns().stream().map(ColumnName::of).allMatch(nodeFilterColumns::contains)
&& wf.getColumnArrays().isEmpty();
}));
final List nodeFilters = nodeSuitabilityToFilters.get(true);
final List sourceFilters = nodeSuitabilityToFilters.get(false);
final NodeOperationsRecorder nodeFiltersRecorder =
nodeFilters.isEmpty() ? null : makeNodeOperationsRecorder().where(Filter.and(nodeFilters));
if (sourceFilters.isEmpty()) {
Assert.neqNull(nodeFiltersRecorder, "nodeFiltersRecorder");
return withNodeOperations(makeNodeOperationsRecorder().where(Filter.and(nodeFilters)));
}
final QueryTable filteredSource = (QueryTable) source.apply(
new TreeTableFilter.Operator(this, sourceFilters.toArray(WhereFilter.ZERO_LENGTH_SELECT_FILTER_ARRAY)));
final QueryTable filteredTree = computeTree(filteredSource, parentIdentifierColumn);
return new TreeTableImpl(getAttributes(), filteredSource, filteredTree, sourceRowLookup, identifierColumn,
parentIdentifierColumn, nodeFilterColumns, accumulateOperations(nodeOperations, nodeFiltersRecorder),
availableColumnDefinitions);
}
/**
* @return The TreeSourceRowLookup for this TreeTableImpl
*/
TreeSourceRowLookup getSourceRowLookup() {
return sourceRowLookup;
}
@Override
public NodeOperationsRecorder makeNodeOperationsRecorder() {
return new TreeNodeOperationsRecorder(getNodeDefinition());
}
@Override
public TreeTable withNodeOperations(@NotNull final NodeOperationsRecorder nodeOperations) {
if (nodeOperations.isEmpty()) {
return noopResult();
}
return new TreeTableImpl(getAttributes(), source, tree, sourceRowLookup, identifierColumn,
parentIdentifierColumn, nodeFilterColumns, accumulateOperations(this.nodeOperations, nodeOperations),
((TreeNodeOperationsRecorder) nodeOperations).getRecordedFormats().isEmpty()
? availableColumnDefinitions
: null);
}
private static TreeNodeOperationsRecorder accumulateOperations(
@Nullable final TreeNodeOperationsRecorder existing,
@Nullable final NodeOperationsRecorder added) {
if (added == null) {
return existing;
}
final TreeNodeOperationsRecorder addedTyped = (TreeNodeOperationsRecorder) added;
return existing == null ? addedTyped : existing.withOperations(addedTyped);
}
@Override
protected TreeTableImpl copy() {
return new TreeTableImpl(getAttributes(), source, tree, sourceRowLookup, identifierColumn,
parentIdentifierColumn, nodeFilterColumns, nodeOperations, availableColumnDefinitions);
}
public static TreeTable makeTree(
@NotNull final QueryTable source,
@NotNull final ColumnName identifierColumn,
@NotNull final ColumnName parentIdentifierColumn) {
final QueryTable tree = computeTree(source, parentIdentifierColumn);
final QueryTable sourceRowLookupTable = computeSourceRowLookupTable(source, identifierColumn);
final TreeSourceRowLookup sourceRowLookup = new TreeSourceRowLookup(source, sourceRowLookupTable);
final TreeTableImpl result = new TreeTableImpl(
source.getAttributes(ak -> shouldCopyAttribute(ak, BaseTable.CopyAttributeOperation.Tree)),
source, tree, sourceRowLookup, identifierColumn, parentIdentifierColumn, Set.of(), null, null);
source.copySortableColumns(result, (final String columnName) -> true);
return result;
}
private static QueryTable computeTree(
@NotNull final QueryTable source,
@NotNull final ColumnName parentIdColumn) {
return source.aggNoMemo(AggregationProcessor.forAggregation(List.of(Partition.of(TREE_COLUMN))),
true, makeNullSingleColumnTable(source, parentIdColumn, 1), List.of(parentIdColumn));
}
private static QueryTable getTreeRoot(@NotNull final QueryTable tree) {
// NB: This is "safe" because we rely on the implementation details of aggregation and the partition operator,
// which ensure that the initial groups are bucketed first and the result row set is flat.
return (QueryTable) tree.getColumnSource(TREE_COLUMN.name()).get(0);
}
private static QueryTable computeSourceRowLookupTable(
@NotNull final QueryTable source,
@NotNull final ColumnName idColumn) {
return source.aggNoMemo(AggregationProcessor.forTreeSourceRowLookup(), false, null, List.of(idColumn));
}
@Override
Iterable