This is an automated email from the ASF dual-hosted git repository.
cwylie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 6d4c8abe312 refactor: add VirtualColumns support for getting
VirtualColumn dependency tree structure (#19281)
6d4c8abe312 is described below
commit 6d4c8abe3121628438d945b2439888f91783cc22
Author: Clint Wylie <[email protected]>
AuthorDate: Tue Apr 14 16:36:44 2026 -0700
refactor: add VirtualColumns support for getting VirtualColumn dependency
tree structure (#19281)
changes:
* add class `VirtualColumns.Node` capturing a `VirtualColumn` and its
transitive `VirtualColumn` dependencies
* add `VirtualColumns.getNode` method which takes a virtual column name and
returns a `VirtualColumns.Node` from a memoized map supplier
* modified `VirtualColumns.findEquivalent` to take a `VirtualColumns.Node`
as an argument, replacing the previous two-arg `findEquivalent(VirtualColumns,
VirtualColumn)`, which iterates `node.getDependencies()` directly instead of
calling `virtualColumn.requiredColumns()` + `virtualColumns.getVirtualColumn()`
+ null-checking, which simplifies both the implementation and all call sites
* removed `ShardVirtualColumnCacheEntry` from `FilterSegmentPruner`, the
shard equivalence cache now uses `VirtualColumns.Node` as the key instead of
allocating a new tree-structure per call
* `Projections` updated to use `getNode()` + `findEquivalent(Node)`
* `SegmentGenerationStageSpec` method
`addRequiredVirtualColumns(VirtualColumns, VirtualColumn, Map)` replaced by
`addRequiredFromNode(Node, Map)` which walks `getDependencies()` of the node
rather than manually calling `requiredColumns()` + `getVirtualColumn`
---
.../destination/SegmentGenerationStageSpec.java | 36 +++---
.../druid/query/filter/FilterSegmentPruner.java | 58 ++--------
.../org/apache/druid/segment/VirtualColumn.java | 2 +-
.../org/apache/druid/segment/VirtualColumns.java | 126 ++++++++++++++++++---
.../druid/segment/projections/Projections.java | 9 +-
.../apache/druid/timeline/partition/ShardSpec.java | 2 +-
.../apache/druid/segment/VirtualColumnsTest.java | 113 ++++++++++++++++--
7 files changed, 244 insertions(+), 102 deletions(-)
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/destination/SegmentGenerationStageSpec.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/destination/SegmentGenerationStageSpec.java
index 4d00d19f3fc..d760d262a51 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/destination/SegmentGenerationStageSpec.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/destination/SegmentGenerationStageSpec.java
@@ -138,26 +138,26 @@ public class SegmentGenerationStageSpec implements
TerminalStageSpec
{
final Map<String, VirtualColumn> clusterByVirtualColumns = new
LinkedHashMap<>();
if (query instanceof GroupByQuery groupByQuery) {
- final Map<String, VirtualColumn> outputToVc = new LinkedHashMap<>();
+ final Map<String, VirtualColumns.Node> outputToVc = new
LinkedHashMap<>();
for (DimensionSpec spec : groupByQuery.getDimensions()) {
- final VirtualColumn vc =
groupByQuery.getVirtualColumns().getVirtualColumn(spec.getDimension());
+ final VirtualColumns.Node vc =
groupByQuery.getVirtualColumns().getNode(spec.getDimension());
if (vc != null) {
outputToVc.put(spec.getOutputName(), vc);
}
}
for (KeyColumn column : queryClusterBy.getColumns()) {
- final VirtualColumn vc = outputToVc.get(column.columnName());
+ final VirtualColumns.Node vc = outputToVc.get(column.columnName());
if (vc != null) {
- clusterByVirtualColumns.put(column.columnName(), vc);
- addRequiredVirtualColumns(groupByQuery.getVirtualColumns(), vc,
clusterByVirtualColumns);
+ clusterByVirtualColumns.put(column.columnName(),
vc.getVirtualColumn());
+ addRequiredFromNode(vc, clusterByVirtualColumns);
}
}
} else if (query instanceof ScanQuery scanQuery) {
for (KeyColumn column : queryClusterBy.getColumns()) {
- final VirtualColumn vc =
scanQuery.getVirtualColumns().getVirtualColumn(column.columnName());
+ final VirtualColumns.Node vc =
scanQuery.getVirtualColumns().getNode(column.columnName());
if (vc != null) {
- clusterByVirtualColumns.put(column.columnName(), vc);
- addRequiredVirtualColumns(scanQuery.getVirtualColumns(), vc,
clusterByVirtualColumns);
+ clusterByVirtualColumns.put(column.columnName(),
vc.getVirtualColumn());
+ addRequiredFromNode(vc, clusterByVirtualColumns);
}
}
}
@@ -165,24 +165,16 @@ public class SegmentGenerationStageSpec implements
TerminalStageSpec
}
/**
- * Recursively adds any {@link VirtualColumn#requiredColumns()} which are
also virtual columns. This handles cases
- * where a cluster-by virtual column depends on other virtual columns, such
as when clustering by something like
+ * Adds all transitive virtual column dependencies of {@code vc} into {@code
collected}. This handles cases where a
+ * cluster-by virtual column depends on other virtual columns, such as when
clustering by something like
* {@code LOWER(JSON_VALUE(obj, '$.path'))} which creates an
ExpressionVirtualColumn that references a
* NestedFieldVirtualColumn.
*/
- private static void addRequiredVirtualColumns(
- VirtualColumns allVirtualColumns,
- VirtualColumn vc,
- Map<String, VirtualColumn> collected
- )
+ private static void addRequiredFromNode(VirtualColumns.Node node,
Map<String, VirtualColumn> collected)
{
- for (String requiredColumn : vc.requiredColumns()) {
- if (!collected.containsKey(requiredColumn)) {
- final VirtualColumn requiredVc =
allVirtualColumns.getVirtualColumn(requiredColumn);
- if (requiredVc != null) {
- collected.put(requiredColumn, requiredVc);
- addRequiredVirtualColumns(allVirtualColumns, requiredVc, collected);
- }
+ for (VirtualColumns.Node dep : node.getDependencies()) {
+ if (collected.putIfAbsent(dep.getVirtualColumn().getOutputName(),
dep.getVirtualColumn()) == null) {
+ addRequiredFromNode(dep, collected);
}
}
}
diff --git
a/processing/src/main/java/org/apache/druid/query/filter/FilterSegmentPruner.java
b/processing/src/main/java/org/apache/druid/query/filter/FilterSegmentPruner.java
index 32854ac0d32..6ef7565302b 100644
---
a/processing/src/main/java/org/apache/druid/query/filter/FilterSegmentPruner.java
+++
b/processing/src/main/java/org/apache/druid/query/filter/FilterSegmentPruner.java
@@ -47,7 +47,7 @@ public class FilterSegmentPruner implements SegmentPruner
private final Set<String> filterFields;
private final VirtualColumns virtualColumns;
private final Map<String, Optional<RangeSet<String>>> rangeCache;
- private final Map<ShardVirtualColumnCacheEntry, Optional<VirtualColumn>>
shardEquivalenceCache;
+ private final Map<VirtualColumns.Node, Optional<VirtualColumn>>
shardEquivalenceCache;
public FilterSegmentPruner(
DimFilter filter,
@@ -79,12 +79,9 @@ public class FilterSegmentPruner implements SegmentPruner
final Map<String, RangeSet<String>> filterDomain = new HashMap<>();
final List<String> dimensions = shard.getDomainDimensions();
for (String dimension : dimensions) {
- final VirtualColumn shardVirtualColumn =
shard.getDomainVirtualColumns().getVirtualColumn(dimension);
- if (shardVirtualColumn != null) {
- final VirtualColumn queryEquivalent = getQueryEquivalent(
- shard.getDomainVirtualColumns(),
- shardVirtualColumn
- );
+ final VirtualColumns.Node shardNode =
shard.getDomainVirtualColumns().getNode(dimension);
+ if (shardNode != null) {
+ final VirtualColumn queryEquivalent = getQueryEquivalent(shardNode);
if (queryEquivalent != null) {
if (filterFields == null ||
filterFields.contains(queryEquivalent.getOutputName())) {
final Optional<RangeSet<String>> optFilterRangeSet = rangeCache
@@ -93,7 +90,7 @@ public class FilterSegmentPruner implements SegmentPruner
d -> Optional.ofNullable(filter.getDimensionRangeSet(d))
);
optFilterRangeSet.ifPresent(stringRangeSet -> filterDomain.put(
- shardVirtualColumn.getOutputName(),
+ shardNode.getVirtualColumn().getOutputName(),
stringRangeSet
));
}
@@ -168,51 +165,12 @@ public class FilterSegmentPruner implements SegmentPruner
}
@Nullable
- private VirtualColumn getQueryEquivalent(VirtualColumns shardVirtualColumns,
VirtualColumn shardVirtualColumn)
+ private VirtualColumn getQueryEquivalent(VirtualColumns.Node node)
{
final Optional<VirtualColumn> cached =
shardEquivalenceCache.computeIfAbsent(
- new ShardVirtualColumnCacheEntry(shardVirtualColumn,
shardVirtualColumns),
- virtualColumn ->
Optional.ofNullable(virtualColumns.findEquivalent(shardVirtualColumns,
virtualColumn.shardVirtualColumn))
+ node,
+ n -> Optional.ofNullable(virtualColumns.findEquivalent(n))
);
return cached.orElse(null);
}
-
- /**
- * Structure to preserve the VirtualColumn 'tree' to use as a cache key so
that we can distinguish otherwise
- * identical {@link VirtualColumn} that depend on other virtual columns that
have the same name but are different
- */
- private static final class ShardVirtualColumnCacheEntry
- {
- private final VirtualColumn shardVirtualColumn;
- private final List<ShardVirtualColumnCacheEntry> dependents;
-
- public ShardVirtualColumnCacheEntry(VirtualColumn shardVirtualColumn,
VirtualColumns shardVirtualColumns)
- {
- this.shardVirtualColumn = shardVirtualColumn;
- this.dependents = new ArrayList<>();
- for (String required : shardVirtualColumn.requiredColumns()) {
- final VirtualColumn dependent =
shardVirtualColumns.getVirtualColumn(required);
- if (dependent != null) {
- dependents.add(new ShardVirtualColumnCacheEntry(dependent,
shardVirtualColumns));
- }
- }
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ShardVirtualColumnCacheEntry that = (ShardVirtualColumnCacheEntry) o;
- return Objects.equals(shardVirtualColumn, that.shardVirtualColumn) &&
- Objects.equals(dependents, that.dependents);
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hash(shardVirtualColumn, dependents);
- }
- }
}
diff --git
a/processing/src/main/java/org/apache/druid/segment/VirtualColumn.java
b/processing/src/main/java/org/apache/druid/segment/VirtualColumn.java
index 5768ce4c04b..3c2fdcf5f69 100644
--- a/processing/src/main/java/org/apache/druid/segment/VirtualColumn.java
+++ b/processing/src/main/java/org/apache/druid/segment/VirtualColumn.java
@@ -540,7 +540,7 @@ public interface VirtualColumn extends Cacheable
* virtual column, regardless of the output name. If this method returns
null, it does not participate in equivalence
* comparisons.
*
- * @see VirtualColumns#findEquivalent(VirtualColumns, VirtualColumn)
+ * @see VirtualColumns#findEquivalent(VirtualColumns.Node)
*/
@Nullable
default EquivalenceKey getEquivalanceKey()
diff --git
a/processing/src/main/java/org/apache/druid/segment/VirtualColumns.java
b/processing/src/main/java/org/apache/druid/segment/VirtualColumns.java
index 089931ac792..48d1decb559 100644
--- a/processing/src/main/java/org/apache/druid/segment/VirtualColumns.java
+++ b/processing/src/main/java/org/apache/druid/segment/VirtualColumns.java
@@ -57,6 +57,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -138,6 +139,8 @@ public class VirtualColumns implements Cacheable
private final List<String> virtualColumnNames;
// For equivalence
private final Supplier<Map<VirtualColumn.EquivalenceKey, VirtualColumn>>
equivalence;
+ // For getNode
+ private final Supplier<Map<String, Node>> dependencyNodes;
// For getVirtualColumn:
private final Map<String, VirtualColumn> withDotSupport;
@@ -170,6 +173,31 @@ public class VirtualColumns implements Cacheable
}
return equiv;
});
+ this.dependencyNodes = Suppliers.memoize(() -> {
+ final Map<String, Node> nodes =
Maps.newHashMapWithExpectedSize(virtualColumns.size());
+ for (VirtualColumn vc : virtualColumns) {
+ buildNode(vc, nodes);
+ }
+ return nodes;
+ });
+ }
+
+ private Node buildNode(VirtualColumn vc, Map<String, Node> nodes)
+ {
+ final Node existing = nodes.get(vc.getOutputName());
+ if (existing != null) {
+ return existing;
+ }
+ final List<Node> deps = new ArrayList<>();
+ for (String required : vc.requiredColumns()) {
+ final VirtualColumn dep = getVirtualColumn(required);
+ if (dep != null) {
+ deps.add(buildNode(dep, nodes));
+ }
+ }
+ final Node node = new Node(vc, deps.isEmpty() ? List.of() :
List.copyOf(deps));
+ nodes.put(vc.getOutputName(), node);
+ return node;
}
/**
@@ -199,34 +227,48 @@ public class VirtualColumns implements Cacheable
}
/**
- * Check if {@link #virtualColumns} contains a virtual column which is
equivalent to some other virtual column,
- * ignoring output name, returning it if it exists or null if there is no
equivalent virtual column.
+ * Returns the {@link Node} for the given virtual column name, providing
access to the virtual
+ * column and all of its transitive virtual column dependencies within this
instance. Returns null if the
+ * column is not a virtual column in this instance.
+ */
+ @Nullable
+ public Node getNode(String columnName)
+ {
+ final VirtualColumn vc = getVirtualColumn(columnName);
+ if (vc == null) {
+ return null;
+ }
+ return dependencyNodes.get().get(vc.getOutputName());
+ }
+
+ /**
+ * Check if {@link #virtualColumns} contains a virtual column which is
equivalent to the virtual column in the
+ * supplied {@link Node}, ignoring output name, returning it if it exists or
null if there is no
+ * equivalent virtual column.
* <p>
- * If the other virtual column depends on other virtual columns (from the
supplied {@link VirtualColumns}), this
- * method will attempt to locate equivalent entries in {@link
#virtualColumns} to build a map of equivalent output
- * names. Then, we rewrite the inputs of the other virtual column using
+ * If the virtual column has virtual column dependencies (indicated by
non-empty {@link Node#getDependencies()}),
+ * this method will attempt to locate equivalent entries in {@link
#virtualColumns} to build a map of equivalent
+ * output names. Then, we rewrite the inputs of the other virtual column
using
* {@link VirtualColumn#rewriteRequiredColumns(Map)} so that differently
named inputs are normalized prior to testing
* for equivalence.
*/
@Nullable
- public VirtualColumn findEquivalent(VirtualColumns otherVirtualColumns,
VirtualColumn otherVirtualColumn)
+ public VirtualColumn findEquivalent(Node otherNode)
{
// check to see if the virtual column refers to other virtual columns to
see if we need to normalize it
// by rewriting its inputs first to refer to the equivalent virtual columns
final Map<String, String> equivalenceRewriteMap = new HashMap<>();
- for (String column : otherVirtualColumn.requiredColumns()) {
- final VirtualColumn dependent =
otherVirtualColumns.getVirtualColumn(column);
- if (dependent != null) {
- final VirtualColumn equivalentDependent =
findEquivalent(otherVirtualColumns, dependent);
- if (equivalentDependent != null) {
- equivalenceRewriteMap.put(dependent.getOutputName(),
equivalentDependent.getOutputName());
- } else {
- // missing an equivalent dependent, that means we cannot be
equivalent so just bail early
- return null;
- }
+ for (Node dep : otherNode.getDependencies()) {
+ final VirtualColumn equivalentDependent = findEquivalent(dep);
+ if (equivalentDependent != null) {
+ equivalenceRewriteMap.put(dep.getVirtualColumn().getOutputName(),
equivalentDependent.getOutputName());
+ } else {
+ // missing an equivalent dependent, that means we cannot be equivalent
so just bail early
+ return null;
}
}
+ final VirtualColumn otherVirtualColumn = otherNode.getVirtualColumn();
if (!equivalenceRewriteMap.isEmpty() &&
!otherVirtualColumn.supportsRequiredRewrite()) {
// cannot safely check for equivalence if the rewrite map is not empty
and rewrites are not supported
return null;
@@ -559,6 +601,58 @@ public class VirtualColumns implements Cacheable
return virtualColumns.toString();
}
+ /**
+ * A node in the virtual column dependency tree, capturing a {@link
VirtualColumn} and all of its transitive
+ * virtual column dependencies within a {@link VirtualColumns} instance.
Leaf virtual columns (those whose
+ * {@link VirtualColumn#requiredColumns()} contain no other virtual columns)
have an empty {@link #getDependencies()}
+ * list.
+ *
+ * @see VirtualColumns#getNode(String)
+ */
+ public static final class Node
+ {
+ private final VirtualColumn virtualColumn;
+ private final List<Node> dependencies;
+
+ private Node(VirtualColumn virtualColumn, List<Node> dependencies)
+ {
+ this.virtualColumn = virtualColumn;
+ this.dependencies = dependencies;
+ }
+
+ public VirtualColumn getVirtualColumn()
+ {
+ return virtualColumn;
+ }
+
+ /**
+ * The virtual column nodes that this virtual column directly depends on,
containing only dependencies
+ * that are themselves virtual columns. An empty list does not imply
{@link VirtualColumn#requiredColumns()}
+ * is empty, as physical column inputs are not represented here.
+ */
+ public List<Node> getDependencies()
+ {
+ return dependencies;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Node that = (Node) o;
+ return Objects.equals(virtualColumn, that.virtualColumn) &&
+ Objects.equals(dependencies, that.dependencies);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(virtualColumn, dependencies);
+ }
+ }
+
/**
* {@link JsonInclude} filter for {@code getVirtualColumns()}.
*
diff --git
a/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
b/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
index 0609ad926db..0415d540e24 100644
---
a/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
+++
b/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
@@ -35,6 +35,7 @@ import org.apache.druid.segment.AggregateProjectionMetadata;
import org.apache.druid.segment.CursorBuildSpec;
import org.apache.druid.segment.CursorHolder;
import org.apache.druid.segment.VirtualColumn;
+import org.apache.druid.segment.VirtualColumns;
import org.apache.druid.segment.column.ColumnHolder;
import org.apache.druid.utils.CollectionUtils;
import org.joda.time.DateTime;
@@ -405,10 +406,10 @@ public class Projections
)
{
// check to see if we have an equivalent virtual column defined in the
projection, if so we can
- final VirtualColumn projectionEquivalent =
projection.getVirtualColumns().findEquivalent(
- queryCursorBuildSpec.getVirtualColumns(),
- queryVirtualColumn
- );
+ final VirtualColumns.Node queryNode =
+
queryCursorBuildSpec.getVirtualColumns().getNode(queryVirtualColumn.getOutputName());
+ final VirtualColumn projectionEquivalent =
+ queryNode != null ?
projection.getVirtualColumns().findEquivalent(queryNode) : null;
if (projectionEquivalent != null) {
final String remapColumnName;
if (Objects.equals(projectionEquivalent.getOutputName(),
projection.getTimeColumnName())) {
diff --git
a/processing/src/main/java/org/apache/druid/timeline/partition/ShardSpec.java
b/processing/src/main/java/org/apache/druid/timeline/partition/ShardSpec.java
index e881b32484f..ab70cf9f39d 100644
---
a/processing/src/main/java/org/apache/druid/timeline/partition/ShardSpec.java
+++
b/processing/src/main/java/org/apache/druid/timeline/partition/ShardSpec.java
@@ -150,7 +150,7 @@ public interface ShardSpec
/**
* If any of the columns in {@link #getDomainDimensions()} was computed with
an expression and was not stored, the
* {@link VirtualColumn} which computes it is stored here. This allows
matching ranges even when the value is not
- * stored in the shard so long as {@link
VirtualColumns#findEquivalent(VirtualColumns, VirtualColumn)} exists.
+ * stored in the shard so long as {@link
VirtualColumns#findEquivalent(VirtualColumns.Node)} exists.
*
* @return {@link VirtualColumns} associated with columns listed in {@link
#getDomainDimensions()}.
*/
diff --git
a/processing/src/test/java/org/apache/druid/segment/VirtualColumnsTest.java
b/processing/src/test/java/org/apache/druid/segment/VirtualColumnsTest.java
index b6323f7de2f..8cff8c15dfb 100644
--- a/processing/src/test/java/org/apache/druid/segment/VirtualColumnsTest.java
+++ b/processing/src/test/java/org/apache/druid/segment/VirtualColumnsTest.java
@@ -458,6 +458,7 @@ public class VirtualColumnsTest extends
InitializedNullHandlingTest
.withIgnoredFields(
"virtualColumnNames",
"equivalence",
+ "dependencyNodes",
"withDotSupport",
"withoutDotSupport",
"hasNoDotColumns"
@@ -465,6 +466,102 @@ public class VirtualColumnsTest extends
InitializedNullHandlingTest
.verify();
}
+ @Test
+ public void testGetNodeNullForNonVirtualColumn()
+ {
+ final VirtualColumns virtualColumns = makeVirtualColumns();
+ Assert.assertNull(virtualColumns.getNode(REAL_COLUMN_NAME));
+ Assert.assertNull(virtualColumns.getNode("doesNotExist"));
+ Assert.assertNull(VirtualColumns.EMPTY.getNode("anything"));
+ }
+
+ @Test
+ public void testGetNodeLeaf()
+ {
+ // "expr" is "1", a constant expression with no required columns, so no VC
deps
+ final VirtualColumns virtualColumns = makeVirtualColumns();
+ final VirtualColumns.Node node = virtualColumns.getNode("expr");
+ Assert.assertNotNull(node);
+ Assert.assertEquals(virtualColumns.getVirtualColumn("expr"),
node.getVirtualColumn());
+ Assert.assertTrue(node.getDependencies().isEmpty());
+ }
+
+ @Test
+ public void testGetNodeWithVcDependency()
+ {
+ // "expr2" is "expr2i + real_column", depends on expr2i (a VC) and
REAL_COLUMN_NAME (physical)
+ final VirtualColumns virtualColumns = makeVirtualColumns();
+ final VirtualColumns.Node node = virtualColumns.getNode("expr2");
+ Assert.assertNotNull(node);
+ Assert.assertEquals(virtualColumns.getVirtualColumn("expr2"),
node.getVirtualColumn());
+ Assert.assertEquals(1, node.getDependencies().size());
+ final VirtualColumns.Node depNode = node.getDependencies().get(0);
+ Assert.assertEquals(virtualColumns.getVirtualColumn("expr2i"),
depNode.getVirtualColumn());
+ Assert.assertTrue(depNode.getDependencies().isEmpty());
+ }
+
+ @Test
+ public void testGetNodeTransitiveDependencies()
+ {
+ // v0 = v1 + v2, v2 = 1 + v1, v1 = 1 + x (physical)
+ final ExpressionVirtualColumn v1 = new ExpressionVirtualColumn("v1", "1 +
x", ColumnType.LONG, TestExprMacroTable.INSTANCE);
+ final ExpressionVirtualColumn v2 = new ExpressionVirtualColumn("v2", "1 +
v1", ColumnType.LONG, TestExprMacroTable.INSTANCE);
+ final ExpressionVirtualColumn v0 = new ExpressionVirtualColumn("v0", "v1 +
v2", ColumnType.LONG, TestExprMacroTable.INSTANCE);
+ final VirtualColumns virtualColumns = VirtualColumns.create(v0, v1, v2);
+
+ final VirtualColumns.Node v1Node = virtualColumns.getNode("v1");
+ final VirtualColumns.Node v2Node = virtualColumns.getNode("v2");
+ final VirtualColumns.Node v0Node = virtualColumns.getNode("v0");
+
+ // v1 is a leaf
+ Assert.assertNotNull(v1Node);
+ Assert.assertEquals(v1, v1Node.getVirtualColumn());
+ Assert.assertTrue(v1Node.getDependencies().isEmpty());
+
+ // v2 has v1 as a dependency
+ Assert.assertNotNull(v2Node);
+ Assert.assertEquals(v2, v2Node.getVirtualColumn());
+ Assert.assertEquals(1, v2Node.getDependencies().size());
+ Assert.assertSame(v1Node, v2Node.getDependencies().get(0));
+
+ // v0 has v1 and v2 as direct dependencies; the same node instances are
reused
+ Assert.assertNotNull(v0Node);
+ Assert.assertEquals(v0, v0Node.getVirtualColumn());
+ Assert.assertEquals(2, v0Node.getDependencies().size());
+ Assert.assertTrue(v0Node.getDependencies().contains(v1Node));
+ Assert.assertTrue(v0Node.getDependencies().contains(v2Node));
+ }
+
+ @Test
+ public void testGetNodeStructuralEquality()
+ {
+ final NestedFieldVirtualColumn n0 = new NestedFieldVirtualColumn("obj",
"$.a", "n0", ColumnType.STRING);
+ final ExpressionVirtualColumn e0 = new ExpressionVirtualColumn(
+ "e0", "lower(\"n0\")", ColumnType.STRING, TestExprMacroTable.INSTANCE
+ );
+ final VirtualColumns vc1 = VirtualColumns.create(n0, e0);
+
+ // Same structure, different instances, nodes must be equal
+ final NestedFieldVirtualColumn n0copy = new
NestedFieldVirtualColumn("obj", "$.a", "n0", ColumnType.STRING);
+ final ExpressionVirtualColumn e0copy = new ExpressionVirtualColumn(
+ "e0", "lower(\"n0\")", ColumnType.STRING, TestExprMacroTable.INSTANCE
+ );
+ final VirtualColumns vc2 = VirtualColumns.create(n0copy, e0copy);
+
+ Assert.assertEquals(vc1.getNode("e0"), vc2.getNode("e0"));
+ Assert.assertEquals(vc1.getNode("e0").hashCode(),
vc2.getNode("e0").hashCode());
+
+ // Different underlying dependency ($.b instead of $.a), even though e0's
expression looks the same,
+ // the node must differ because the dependency subtree differs
+ final NestedFieldVirtualColumn n0different = new
NestedFieldVirtualColumn("obj", "$.b", "n0", ColumnType.STRING);
+ final ExpressionVirtualColumn e0sameName = new ExpressionVirtualColumn(
+ "e0", "lower(\"n0\")", ColumnType.STRING, TestExprMacroTable.INSTANCE
+ );
+ final VirtualColumns vc3 = VirtualColumns.create(n0different, e0sameName);
+
+ Assert.assertNotEquals(vc1.getNode("e0"), vc3.getNode("e0"));
+ }
+
@Test
public void testEquivalence()
{
@@ -496,10 +593,10 @@ public class VirtualColumnsTest extends
InitializedNullHandlingTest
);
VirtualColumns otherVirtualColumns = VirtualColumns.create(v1, v2, v3);
- Assert.assertEquals(v0,
virtualColumns.findEquivalent(VirtualColumns.EMPTY, v0));
- Assert.assertEquals(v0, virtualColumns.findEquivalent(otherVirtualColumns,
v1));
- Assert.assertNull(virtualColumns.findEquivalent(otherVirtualColumns, v2));
- Assert.assertNull(virtualColumns.findEquivalent(otherVirtualColumns, v3));
+ Assert.assertEquals(v0,
virtualColumns.findEquivalent(virtualColumns.getNode(v0.getOutputName())));
+ Assert.assertEquals(v0,
virtualColumns.findEquivalent(otherVirtualColumns.getNode(v1.getOutputName())));
+
Assert.assertNull(virtualColumns.findEquivalent(otherVirtualColumns.getNode(v2.getOutputName())));
+
Assert.assertNull(virtualColumns.findEquivalent(otherVirtualColumns.getNode(v3.getOutputName())));
}
@Test
@@ -520,9 +617,9 @@ public class VirtualColumnsTest extends
InitializedNullHandlingTest
);
final VirtualColumns otherVirtualColumns = VirtualColumns.create(n1, e1);
- Assert.assertEquals(n0, virtualColumns.findEquivalent(otherVirtualColumns,
n1));
+ Assert.assertEquals(n0,
virtualColumns.findEquivalent(otherVirtualColumns.getNode(n1.getOutputName())));
- Assert.assertEquals(e0, virtualColumns.findEquivalent(otherVirtualColumns,
e1));
+ Assert.assertEquals(e0,
virtualColumns.findEquivalent(otherVirtualColumns.getNode(e1.getOutputName())));
// a different nested field path produces no equivalence, even if it has
the same name
final NestedFieldVirtualColumn n0different = new
NestedFieldVirtualColumn("obj", "$.b", "n0", ColumnType.STRING);
@@ -532,8 +629,8 @@ public class VirtualColumnsTest extends
InitializedNullHandlingTest
TestExprMacroTable.INSTANCE
);
final VirtualColumns notEquivalent = VirtualColumns.create(n0different,
e0different);
- Assert.assertNull(virtualColumns.findEquivalent(notEquivalent,
n0different));
- Assert.assertNull(virtualColumns.findEquivalent(notEquivalent,
e0different));
+
Assert.assertNull(virtualColumns.findEquivalent(notEquivalent.getNode(n0different.getOutputName())));
+
Assert.assertNull(virtualColumns.findEquivalent(notEquivalent.getNode(e0different.getOutputName())));
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]