### Question:
JoinStatsRule extends SimpleStatsRule<JoinNode> { @VisibleForTesting PlanNodeStatsEstimate addJoinComplementStats( PlanNodeStatsEstimate sourceStats, PlanNodeStatsEstimate innerJoinStats, PlanNodeStatsEstimate joinComplementStats) { double innerJoinRowCount = innerJoinStats.getOutputRowCount(); double joinComplementRowCount = joinComplementStats.getOutputRowCount(); if (joinComplementRowCount == 0) { return innerJoinStats; } double outputRowCount = innerJoinRowCount + joinComplementRowCount; PlanNodeStatsEstimate.Builder outputStats = PlanNodeStatsEstimate.buildFrom(innerJoinStats); outputStats.setOutputRowCount(outputRowCount); for (Symbol symbol : joinComplementStats.getSymbolsWithKnownStatistics()) { SymbolStatsEstimate leftSymbolStats = sourceStats.getSymbolStatistics(symbol); SymbolStatsEstimate innerJoinSymbolStats = innerJoinStats.getSymbolStatistics(symbol); SymbolStatsEstimate joinComplementSymbolStats = joinComplementStats.getSymbolStatistics(symbol); double newNullsFraction = (innerJoinSymbolStats.getNullsFraction() * innerJoinRowCount + joinComplementSymbolStats.getNullsFraction() * joinComplementRowCount) / outputRowCount; outputStats.addSymbolStatistics(symbol, SymbolStatsEstimate.buildFrom(innerJoinSymbolStats) .setLowValue(leftSymbolStats.getLowValue()) .setHighValue(leftSymbolStats.getHighValue()) .setDistinctValuesCount(leftSymbolStats.getDistinctValuesCount()) .setNullsFraction(newNullsFraction) .build()); } for (Symbol symbol : difference(innerJoinStats.getSymbolsWithKnownStatistics(), joinComplementStats.getSymbolsWithKnownStatistics())) { SymbolStatsEstimate innerJoinSymbolStats = innerJoinStats.getSymbolStatistics(symbol); double newNullsFraction = (innerJoinSymbolStats.getNullsFraction() * innerJoinRowCount + joinComplementRowCount) / outputRowCount; outputStats.addSymbolStatistics(symbol, innerJoinSymbolStats.mapNullsFraction(nullsFraction -> newNullsFraction)); } return outputStats.build(); } JoinStatsRule(FilterStatsCalculator filterStatsCalculator, StatsNormalizer normalizer); @VisibleForTesting  JoinStatsRule(FilterStatsCalculator filterStatsCalculator, StatsNormalizer normalizer, double unmatchedJoinComplementNdvsCoefficient); @Override Pattern<JoinNode> getPattern();  }### Answer:
@Test public void testAddJoinComplementStats() { double statsToAddNdv = 5; PlanNodeStatsEstimate statsToAdd = planNodeStats( RIGHT_ROWS_COUNT, symbolStatistics(LEFT_JOIN_COLUMN, 0.0, 5.0, 0.2, statsToAddNdv)); PlanNodeStatsEstimate addedStats = planNodeStats( TOTAL_ROWS_COUNT, symbolStatistics(LEFT_JOIN_COLUMN, 0.0, 20.0, (LEFT_ROWS_COUNT * LEFT_JOIN_COLUMN_NULLS + RIGHT_ROWS_COUNT * 0.2) / TOTAL_ROWS_COUNT, LEFT_JOIN_COLUMN_NDV), symbolStatistics(LEFT_OTHER_COLUMN, 42, 42, (0.42 * LEFT_ROWS_COUNT + RIGHT_ROWS_COUNT) / TOTAL_ROWS_COUNT, 1)); assertThat(JOIN_STATS_RULE.addJoinComplementStats( LEFT_STATS, LEFT_STATS, statsToAdd)) .equalTo(addedStats); }