This is an automated email from the ASF dual-hosted git repository.
jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new bebd2b43a3 [Multi-stage] Support lookup join (#13966)
bebd2b43a3 is described below
commit bebd2b43a304a3cebbdba3214bde38c101a85422
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Tue Oct 8 15:49:22 2024 -0700
[Multi-stage] Support lookup join (#13966)
---
.gitignore | 3 +-
pinot-common/src/main/proto/plan.proto | 6 +
.../pinot/calcite/rel/hint/PinotHintOptions.java | 11 +-
.../rel/rules/PinotJoinExchangeNodeInsertRule.java | 38 +-
.../rel/rules/PinotJoinToDynamicBroadcastRule.java | 48 +-
.../planner/logical/RelToPlanNodeConverter.java | 70 +-
.../pinot/query/planner/plannode/JoinNode.java | 21 +-
.../pinot/query/planner/plannode/PlanNode.java | 3 +-
.../query/planner/serde/PlanNodeDeserializer.java | 14 +-
.../query/planner/serde/PlanNodeSerializer.java | 14 +-
.../apache/pinot/query/routing/WorkerManager.java | 92 +-
.../query/runtime/InStageStatsTreeBuilder.java | 7 +-
.../query/runtime/operator/HashJoinOperator.java | 45 +-
.../LeafStageTransferableBlockOperator.java | 12 +
.../query/runtime/operator/LookupJoinOperator.java | 260 +++
.../query/runtime/operator/MultiStageOperator.java | 11 +-
.../query/runtime/plan/PlanNodeToOpChain.java | 11 +-
.../plan/server/ServerPlanRequestVisitor.java | 51 +-
.../runtime/operator/HashJoinOperatorTest.java | 20 +-
.../runtime/operator/MultiStageAccountingTest.java | 2 +-
.../plan/pipeline/PipelineBreakerExecutorTest.java | 6 +-
.../pinot/tools/LookupJoinEngineQuickStart.java | 67 +
.../colocated/userGroups/userGroups_schema.json | 6 +-
.../lookup/userGroupsDim/ingestionJobSpec.yaml | 140 ++
.../batch/lookup/userGroupsDim/rawdata/p0.csv | 8 +
.../batch/lookup/userGroupsDim/rawdata/p1.csv | 7 +
.../batch/lookup/userGroupsDim/rawdata/p2.csv | 9 +
.../batch/lookup/userGroupsDim/rawdata/p3.csv | 2455 ++++++++++++++++++++
.../batch/lookup/userGroupsDim/rawdata/p4.csv | 5 +
.../batch/lookup/userGroupsDim/rawdata/p5.csv | 5 +
.../batch/lookup/userGroupsDim/rawdata/p6.csv | 5 +
.../batch/lookup/userGroupsDim/rawdata/p7.csv | 8 +
.../userGroupsDim_offline_table_config.json | 18 +
.../userGroupsDim/userGroupsDim_schema.json} | 7 +-
34 files changed, 3327 insertions(+), 158 deletions(-)
diff --git a/.gitignore b/.gitignore
index 00404492f8..71aa91be6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,8 @@ cscope.*
.externalToolBuilders/
maven-eclipse.xml
target/
-examples/
+/examples/
+/logs/
bin/
*/bin/
.idea
diff --git a/pinot-common/src/main/proto/plan.proto
b/pinot-common/src/main/proto/plan.proto
index 9c5caa5edc..06b2f0910c 100644
--- a/pinot-common/src/main/proto/plan.proto
+++ b/pinot-common/src/main/proto/plan.proto
@@ -83,11 +83,17 @@ enum JoinType {
ANTI = 5;
}
+enum JoinStrategy {
+ HASH = 0;
+ LOOKUP = 1;
+}
+
message JoinNode {
JoinType joinType = 1;
repeated int32 leftKeys = 2;
repeated int32 rightKeys = 3;
repeated Expression nonEquiConditions = 4;
+ JoinStrategy joinStrategy = 5;
}
enum ExchangeType {
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/hint/PinotHintOptions.java
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/hint/PinotHintOptions.java
index f19fa8a705..431d741e4c 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/hint/PinotHintOptions.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/hint/PinotHintOptions.java
@@ -61,20 +61,27 @@ public class PinotHintOptions {
public static class JoinHintOptions {
public static final String JOIN_STRATEGY = "join_strategy";
+ // "hash" is the default strategy for non-SEMI joins
+ public static final String HASH_JOIN_STRATEGY = "hash";
+ // "dynamic_broadcast" is the default strategy for SEMI joins
public static final String DYNAMIC_BROADCAST_JOIN_STRATEGY =
"dynamic_broadcast";
- public static final String HASH_TABLE_JOIN_STRATEGY = "hash_table";
+ // "lookup" can be used when the right table is a dimension table
replicated to all workers
+ public static final String LOOKUP_JOIN_STRATEGY = "lookup";
+
/**
* Max rows allowed to build the right table hash collection.
*/
public static final String MAX_ROWS_IN_JOIN = "max_rows_in_join";
+
/**
* Mode when join overflow happens, supported values: THROW or BREAK.
* THROW(default): Break right table build process, and throw exception,
no JOIN with left table performed.
* BREAK: Break right table build process, continue to perform JOIN
operation, results might be partial.
*/
public static final String JOIN_OVERFLOW_MODE = "join_overflow_mode";
+
/**
- * Indicat that the join operator(s) within a certain selection scope are
colocated
+ * Indicates that the join operator(s) within a certain selection scope
are colocated
*/
public static final String IS_COLOCATED_BY_JOIN_KEYS =
"is_colocated_by_join_keys";
}
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java
index 37f12fbd08..bb7ee8aed7 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java
@@ -25,6 +25,8 @@ import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.JoinInfo;
import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.pinot.calcite.rel.hint.PinotHintOptions;
+import org.apache.pinot.calcite.rel.hint.PinotHintStrategyTable;
import org.apache.pinot.calcite.rel.logical.PinotLogicalExchange;
@@ -48,24 +50,32 @@ public class PinotJoinExchangeNodeInsertRule extends
RelOptRule {
@Override
public void onMatch(RelOptRuleCall call) {
Join join = call.rel(0);
- RelNode leftInput = join.getInput(0);
- RelNode rightInput = join.getInput(1);
-
- RelNode leftExchange;
- RelNode rightExchange;
+ RelNode left = PinotRuleUtils.unboxRel(join.getInput(0));
+ RelNode right = PinotRuleUtils.unboxRel(join.getInput(1));
JoinInfo joinInfo = join.analyzeCondition();
-
- if (joinInfo.leftKeys.isEmpty()) {
- // when there's no JOIN key, use broadcast.
- leftExchange = PinotLogicalExchange.create(leftInput,
RelDistributions.RANDOM_DISTRIBUTED);
- rightExchange = PinotLogicalExchange.create(rightInput,
RelDistributions.BROADCAST_DISTRIBUTED);
+ String joinStrategy =
PinotHintStrategyTable.getHintOption(join.getHints(),
PinotHintOptions.JOIN_HINT_OPTIONS,
+ PinotHintOptions.JoinHintOptions.JOIN_STRATEGY);
+ RelNode newLeft;
+ RelNode newRight;
+ if
(PinotHintOptions.JoinHintOptions.LOOKUP_JOIN_STRATEGY.equals(joinStrategy)) {
+ // Lookup join - add local exchange on the left side
+ newLeft = PinotLogicalExchange.create(left, RelDistributions.SINGLETON);
+ newRight = right;
} else {
- // when join key exists, use hash distribution.
- leftExchange = PinotLogicalExchange.create(leftInput,
RelDistributions.hash(joinInfo.leftKeys));
- rightExchange = PinotLogicalExchange.create(rightInput,
RelDistributions.hash(joinInfo.rightKeys));
+ // Regular join - add exchange on both sides
+ if (joinInfo.leftKeys.isEmpty()) {
+ // Broadcast the right side if there is no join key
+ newLeft = PinotLogicalExchange.create(left,
RelDistributions.RANDOM_DISTRIBUTED);
+ newRight = PinotLogicalExchange.create(right,
RelDistributions.BROADCAST_DISTRIBUTED);
+ } else {
+ // Use hash exchange when there are join keys
+ newLeft = PinotLogicalExchange.create(left,
RelDistributions.hash(joinInfo.leftKeys));
+ newRight = PinotLogicalExchange.create(right,
RelDistributions.hash(joinInfo.rightKeys));
+ }
}
- call.transformTo(join.copy(join.getTraitSet(), join.getCondition(),
leftExchange, rightExchange, join.getJoinType(),
+ // TODO: Consider creating different JOIN Rel for each join strategy
+ call.transformTo(join.copy(join.getTraitSet(), join.getCondition(),
newLeft, newRight, join.getJoinType(),
join.isSemiJoinDone()));
}
}
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java
index ed86a6fcc2..c1924eb1d5 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java
@@ -18,8 +18,6 @@
*/
package org.apache.pinot.calcite.rel.rules;
-import java.util.Collections;
-import java.util.List;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.hep.HepRelVertex;
@@ -35,7 +33,6 @@ import org.apache.pinot.calcite.rel.hint.PinotHintOptions;
import org.apache.pinot.calcite.rel.hint.PinotHintStrategyTable;
import org.apache.pinot.calcite.rel.logical.PinotLogicalExchange;
import org.apache.pinot.calcite.rel.logical.PinotRelExchangeType;
-import org.apache.zookeeper.common.StringUtils;
/**
@@ -125,25 +122,27 @@ public class PinotJoinToDynamicBroadcastRule extends
RelOptRule {
@Override
public boolean matches(RelOptRuleCall call) {
Join join = call.rel(0);
- String joinStrategyString =
- PinotHintStrategyTable.getHintOption(join.getHints(),
PinotHintOptions.JOIN_HINT_OPTIONS,
- PinotHintOptions.JoinHintOptions.JOIN_STRATEGY);
- List<String> joinStrategies =
- joinStrategyString != null ? StringUtils.split(joinStrategyString,
",") : Collections.emptyList();
- boolean explicitOtherStrategy = !joinStrategies.isEmpty() &&
!joinStrategies.contains(
- PinotHintOptions.JoinHintOptions.DYNAMIC_BROADCAST_JOIN_STRATEGY);
+ // Do not apply this rule if join strategy is explicitly set to something
other than dynamic broadcast
+ String joinStrategy =
PinotHintStrategyTable.getHintOption(join.getHints(),
PinotHintOptions.JOIN_HINT_OPTIONS,
+ PinotHintOptions.JoinHintOptions.JOIN_STRATEGY);
+ if (joinStrategy != null && !joinStrategy.equals(
+ PinotHintOptions.JoinHintOptions.DYNAMIC_BROADCAST_JOIN_STRATEGY)) {
+ return false;
+ }
+
+ // Do not apply this rule if it is not a SEMI join
JoinInfo joinInfo = join.analyzeCondition();
+ if (join.getJoinType() != JoinRelType.SEMI ||
!joinInfo.nonEquiConditions.isEmpty()
+ || joinInfo.leftKeys.size() != 1) {
+ return false;
+ }
+
+ // Apply this rule if the left side can be pushed as dynamic exchange
RelNode left = ((HepRelVertex) join.getLeft()).getCurrentRel();
RelNode right = ((HepRelVertex) join.getRight()).getCurrentRel();
- return left instanceof Exchange && right instanceof Exchange
- // left side can be pushed as dynamic exchange
- && PinotRuleUtils.canPushDynamicBroadcastToLeaf(left.getInput(0))
- // default enable dynamic broadcast for SEMI join unless other join
strategy were specified
- && !explicitOtherStrategy
- // condition for SEMI join
- && join.getJoinType() == JoinRelType.SEMI &&
joinInfo.nonEquiConditions.isEmpty()
- && joinInfo.leftKeys.size() == 1;
+ return left instanceof Exchange && right instanceof Exchange &&
PinotRuleUtils.canPushDynamicBroadcastToLeaf(
+ left.getInput(0));
}
@Override
@@ -158,15 +157,10 @@ public class PinotJoinToDynamicBroadcastRule extends
RelOptRule {
boolean isColocatedJoin =
PinotHintStrategyTable.isHintOptionTrue(join.getHints(),
PinotHintOptions.JOIN_HINT_OPTIONS,
PinotHintOptions.JoinHintOptions.IS_COLOCATED_BY_JOIN_KEYS);
- PinotLogicalExchange dynamicBroadcastExchange;
- RelNode rightInput = right.getInput();
- if (isColocatedJoin) {
- RelDistribution dist =
RelDistributions.hash(join.analyzeCondition().rightKeys);
- dynamicBroadcastExchange = PinotLogicalExchange.create(rightInput, dist,
PinotRelExchangeType.PIPELINE_BREAKER);
- } else {
- RelDistribution dist = RelDistributions.BROADCAST_DISTRIBUTED;
- dynamicBroadcastExchange = PinotLogicalExchange.create(rightInput, dist,
PinotRelExchangeType.PIPELINE_BREAKER);
- }
+ RelDistribution relDistribution = isColocatedJoin ?
RelDistributions.hash(join.analyzeCondition().rightKeys)
+ : RelDistributions.BROADCAST_DISTRIBUTED;
+ PinotLogicalExchange dynamicBroadcastExchange =
+ PinotLogicalExchange.create(right.getInput(), relDistribution,
PinotRelExchangeType.PIPELINE_BREAKER);
call.transformTo(join.copy(join.getTraitSet(), join.getCondition(),
left.getInput(), dynamicBroadcastExchange,
join.getJoinType(), join.isSemiJoinDone()));
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RelToPlanNodeConverter.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RelToPlanNodeConverter.java
index cd60856c17..5991b44ed8 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RelToPlanNodeConverter.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RelToPlanNodeConverter.java
@@ -19,6 +19,7 @@
package org.apache.pinot.query.planner.logical;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
@@ -32,9 +33,12 @@ import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.Exchange;
import org.apache.calcite.rel.core.JoinInfo;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.SetOp;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Window;
+import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalJoin;
import org.apache.calcite.rel.logical.LogicalProject;
@@ -45,13 +49,18 @@ import org.apache.calcite.rel.logical.LogicalWindow;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelRecordType;
+import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.pinot.calcite.rel.hint.PinotHintOptions;
+import org.apache.pinot.calcite.rel.hint.PinotHintStrategyTable;
import org.apache.pinot.calcite.rel.logical.PinotLogicalAggregate;
import org.apache.pinot.calcite.rel.logical.PinotLogicalExchange;
import org.apache.pinot.calcite.rel.logical.PinotLogicalSortExchange;
import org.apache.pinot.calcite.rel.logical.PinotRelExchangeType;
+import org.apache.pinot.calcite.rel.rules.PinotRuleUtils;
import org.apache.pinot.common.metrics.BrokerMeter;
import org.apache.pinot.common.metrics.BrokerMetrics;
import org.apache.pinot.common.utils.DataSchema;
@@ -264,11 +273,62 @@ public final class RelToPlanNodeConverter {
convertInputs(node.getInputs()), tableName, columns);
}
- private JoinNode convertLogicalJoin(LogicalJoin node) {
- JoinInfo joinInfo = node.analyzeCondition();
- return new JoinNode(DEFAULT_STAGE_ID, toDataSchema(node.getRowType()),
NodeHint.fromRelHints(node.getHints()),
- convertInputs(node.getInputs()), node.getJoinType(),
joinInfo.leftKeys, joinInfo.rightKeys,
- RexExpressionUtils.fromRexNodes(joinInfo.nonEquiConditions));
+ private JoinNode convertLogicalJoin(LogicalJoin join) {
+ JoinInfo joinInfo = join.analyzeCondition();
+ DataSchema dataSchema = toDataSchema(join.getRowType());
+ List<PlanNode> inputs = convertInputs(join.getInputs());
+ JoinRelType joinType = join.getJoinType();
+
+ // Run some validations for join
+ Preconditions.checkState(inputs.size() == 2, "Join should have exactly 2
inputs, got: %s", inputs.size());
+ PlanNode left = inputs.get(0);
+ PlanNode right = inputs.get(1);
+ int numLeftColumns = left.getDataSchema().size();
+ int numResultColumns = dataSchema.size();
+ if (joinType.projectsRight()) {
+ int numRightColumns = right.getDataSchema().size();
+ Preconditions.checkState(numLeftColumns + numRightColumns ==
numResultColumns,
+ "Invalid number of columns for join type: %s, left: %s, right: %s,
result: %s", joinType, numLeftColumns,
+ numRightColumns, numResultColumns);
+ } else {
+ Preconditions.checkState(numLeftColumns == numResultColumns,
+ "Invalid number of columns for join type: %s, left: %s, result: %s",
joinType, numLeftColumns,
+ numResultColumns);
+ }
+
+ // Check if the join hint specifies the join strategy
+ JoinNode.JoinStrategy joinStrategy;
+ ImmutableList<RelHint> relHints = join.getHints();
+ String joinStrategyHint = PinotHintStrategyTable.getHintOption(relHints,
PinotHintOptions.JOIN_HINT_OPTIONS,
+ PinotHintOptions.JoinHintOptions.JOIN_STRATEGY);
+ if
(PinotHintOptions.JoinHintOptions.LOOKUP_JOIN_STRATEGY.equals(joinStrategyHint))
{
+ joinStrategy = JoinNode.JoinStrategy.LOOKUP;
+
+ // Run some validations for lookup join
+ Preconditions.checkArgument(!joinInfo.leftKeys.isEmpty(), "Lookup join
requires join keys");
+ // Right table should be a dimension table, and the right input should
be an identifier only ProjectNode over
+ // TableScanNode.
+ RelNode rightInput = PinotRuleUtils.unboxRel(join.getRight());
+ Preconditions.checkState(rightInput instanceof Project, "Right input for
lookup join must be a Project, got: %s",
+ rightInput.getClass().getSimpleName());
+ Project project = (Project) rightInput;
+ for (RexNode node : project.getProjects()) {
+ Preconditions.checkState(node instanceof RexInputRef,
+ "Right input for lookup join must be an identifier (RexInputRef)
only Project, got: %s in project",
+ node.getClass().getSimpleName());
+ }
+ RelNode projectInput = PinotRuleUtils.unboxRel(project.getInput());
+ Preconditions.checkState(projectInput instanceof TableScan,
+ "Right input for lookup join must be a Project over TableScan, got
Project over: %s",
+ projectInput.getClass().getSimpleName());
+ } else {
+ // TODO: Consider adding DYNAMIC_BROADCAST as a separate join strategy
+ joinStrategy = JoinNode.JoinStrategy.HASH;
+ }
+
+ return new JoinNode(DEFAULT_STAGE_ID, dataSchema,
NodeHint.fromRelHints(relHints), inputs, joinType,
+ joinInfo.leftKeys, joinInfo.rightKeys,
RexExpressionUtils.fromRexNodes(joinInfo.nonEquiConditions),
+ joinStrategy);
}
private List<PlanNode> convertInputs(List<RelNode> inputs) {
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/JoinNode.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/JoinNode.java
index ea15b7c715..c07392c298 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/JoinNode.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/JoinNode.java
@@ -30,14 +30,17 @@ public class JoinNode extends BasePlanNode {
private final List<Integer> _leftKeys;
private final List<Integer> _rightKeys;
private final List<RexExpression> _nonEquiConditions;
+ private final JoinStrategy _joinStrategy;
public JoinNode(int stageId, DataSchema dataSchema, NodeHint nodeHint,
List<PlanNode> inputs, JoinRelType joinType,
- List<Integer> leftKeys, List<Integer> rightKeys, List<RexExpression>
nonEquiConditions) {
+ List<Integer> leftKeys, List<Integer> rightKeys, List<RexExpression>
nonEquiConditions,
+ JoinStrategy joinStrategy) {
super(stageId, dataSchema, nodeHint, inputs);
_joinType = joinType;
_leftKeys = leftKeys;
_rightKeys = rightKeys;
_nonEquiConditions = nonEquiConditions;
+ _joinStrategy = joinStrategy;
}
public JoinRelType getJoinType() {
@@ -56,6 +59,10 @@ public class JoinNode extends BasePlanNode {
return _nonEquiConditions;
}
+ public JoinStrategy getJoinStrategy() {
+ return _joinStrategy;
+ }
+
@Override
public String explain() {
return "JOIN";
@@ -68,7 +75,8 @@ public class JoinNode extends BasePlanNode {
@Override
public PlanNode withInputs(List<PlanNode> inputs) {
- return new JoinNode(_stageId, _dataSchema, _nodeHint, inputs, _joinType,
_leftKeys, _rightKeys, _nonEquiConditions);
+ return new JoinNode(_stageId, _dataSchema, _nodeHint, inputs, _joinType,
_leftKeys, _rightKeys, _nonEquiConditions,
+ _joinStrategy);
}
@Override
@@ -84,11 +92,16 @@ public class JoinNode extends BasePlanNode {
}
JoinNode joinNode = (JoinNode) o;
return _joinType == joinNode._joinType && Objects.equals(_leftKeys,
joinNode._leftKeys) && Objects.equals(
- _rightKeys, joinNode._rightKeys) && Objects.equals(_nonEquiConditions,
joinNode._nonEquiConditions);
+ _rightKeys, joinNode._rightKeys) && Objects.equals(_nonEquiConditions,
joinNode._nonEquiConditions)
+ && _joinStrategy == joinNode._joinStrategy;
}
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), _joinType, _leftKeys, _rightKeys,
_nonEquiConditions);
+ return Objects.hash(super.hashCode(), _joinType, _leftKeys, _rightKeys,
_nonEquiConditions, _joinStrategy);
+ }
+
+ public enum JoinStrategy {
+ HASH, LOOKUP
}
}
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/PlanNode.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/PlanNode.java
index cadcb899bb..3994b82c67 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/PlanNode.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/PlanNode.java
@@ -81,7 +81,8 @@ public interface PlanNode {
} else {
hintOptions = Maps.newHashMapWithExpectedSize(numHints);
for (RelHint relHint : relHints) {
- hintOptions.put(relHint.hintName, relHint.kvOptions);
+ // Put the first matching hint to match the behavior of
PinotHintStrategyTable
+ hintOptions.putIfAbsent(relHint.hintName, relHint.kvOptions);
}
}
return new NodeHint(hintOptions);
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeDeserializer.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeDeserializer.java
index 026e9737f0..d90f03c73c 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeDeserializer.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeDeserializer.java
@@ -99,7 +99,8 @@ public class PlanNodeDeserializer {
Plan.JoinNode protoJoinNode = protoNode.getJoinNode();
return new JoinNode(protoNode.getStageId(), extractDataSchema(protoNode),
extractNodeHint(protoNode),
extractInputs(protoNode),
convertJoinType(protoJoinNode.getJoinType()), protoJoinNode.getLeftKeysList(),
- protoJoinNode.getRightKeysList(),
convertExpressions(protoJoinNode.getNonEquiConditionsList()));
+ protoJoinNode.getRightKeysList(),
convertExpressions(protoJoinNode.getNonEquiConditionsList()),
+ convertJoinStrategy(protoJoinNode.getJoinStrategy()));
}
private static MailboxReceiveNode
deserializeMailboxReceiveNode(Plan.PlanNode protoNode) {
@@ -274,6 +275,17 @@ public class PlanNodeDeserializer {
}
}
+ private static JoinNode.JoinStrategy convertJoinStrategy(Plan.JoinStrategy
joinStrategy) {
+ switch (joinStrategy) {
+ case HASH:
+ return JoinNode.JoinStrategy.HASH;
+ case LOOKUP:
+ return JoinNode.JoinStrategy.LOOKUP;
+ default:
+ throw new IllegalStateException("Unsupported JoinStrategy: " +
joinStrategy);
+ }
+ }
+
private static PinotRelExchangeType convertExchangeType(Plan.ExchangeType
exchangeType) {
switch (exchangeType) {
case STREAMING:
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeSerializer.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeSerializer.java
index be631c3733..00a21c05e9 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeSerializer.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/serde/PlanNodeSerializer.java
@@ -112,7 +112,8 @@ public class PlanNodeSerializer {
Plan.JoinNode joinNode =
Plan.JoinNode.newBuilder().setJoinType(convertJoinType(node.getJoinType())).addAllLeftKeys(node.getLeftKeys())
.addAllRightKeys(node.getRightKeys())
-
.addAllNonEquiConditions(convertExpressions(node.getNonEquiConditions())).build();
+
.addAllNonEquiConditions(convertExpressions(node.getNonEquiConditions()))
+
.setJoinStrategy(convertJoinStrategy(node.getJoinStrategy())).build();
builder.setJoinNode(joinNode);
return null;
}
@@ -265,6 +266,17 @@ public class PlanNodeSerializer {
}
}
+ private static Plan.JoinStrategy convertJoinStrategy(JoinNode.JoinStrategy
joinStrategy) {
+ switch (joinStrategy) {
+ case HASH:
+ return Plan.JoinStrategy.HASH;
+ case LOOKUP:
+ return Plan.JoinStrategy.LOOKUP;
+ default:
+ throw new IllegalStateException("Unsupported JoinStrategy: " +
joinStrategy);
+ }
+ }
+
private static Plan.ExchangeType convertExchangeType(PinotRelExchangeType
exchangeType) {
switch (exchangeType) {
case STREAMING:
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/routing/WorkerManager.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/routing/WorkerManager.java
index d256a19a00..ba3a709fed 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/routing/WorkerManager.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/routing/WorkerManager.java
@@ -19,6 +19,7 @@
package org.apache.pinot.query.routing;
import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -29,6 +30,7 @@ import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.annotation.Nullable;
+import org.apache.calcite.rel.RelDistribution;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.pinot.calcite.rel.hint.PinotHintOptions;
import org.apache.pinot.core.routing.RoutingManager;
@@ -39,6 +41,8 @@ import org.apache.pinot.core.transport.ServerInstance;
import org.apache.pinot.query.planner.PlanFragment;
import org.apache.pinot.query.planner.physical.DispatchablePlanContext;
import org.apache.pinot.query.planner.physical.DispatchablePlanMetadata;
+import org.apache.pinot.query.planner.plannode.MailboxSendNode;
+import org.apache.pinot.query.planner.plannode.PlanNode;
import org.apache.pinot.query.planner.plannode.TableScanNode;
import org.apache.pinot.spi.config.table.TableType;
import
org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey;
@@ -84,16 +88,46 @@ public class WorkerManager {
}
private void assignWorkersToNonRootFragment(PlanFragment fragment,
DispatchablePlanContext context) {
- for (PlanFragment child : fragment.getChildren()) {
+ List<PlanFragment> children = fragment.getChildren();
+ for (PlanFragment child : children) {
assignWorkersToNonRootFragment(child, context);
}
- if
(isLeafPlan(context.getDispatchablePlanMetadataMap().get(fragment.getFragmentId())))
{
+ Map<Integer, DispatchablePlanMetadata> metadataMap =
context.getDispatchablePlanMetadataMap();
+ DispatchablePlanMetadata metadata =
metadataMap.get(fragment.getFragmentId());
+ boolean leafPlan = isLeafPlan(metadata);
+ if (isLocalExchange(children)) {
+ // If it is a local exchange (single child with SINGLETON distribution),
use the same worker assignment to avoid
+ // shuffling data.
+ // TODO: Support partition parallelism
+ DispatchablePlanMetadata childMetadata =
metadataMap.get(children.get(0).getFragmentId());
+
metadata.setWorkerIdToServerInstanceMap(childMetadata.getWorkerIdToServerInstanceMap());
+ metadata.setPartitionFunction(childMetadata.getPartitionFunction());
+ if (leafPlan) {
+ // Fake segments map for leaf plan
+ Set<Integer> workerIds =
metadata.getWorkerIdToServerInstanceMap().keySet();
+ Map<Integer, Map<String, List<String>>> workerIdToSegmentsMap =
+ Maps.newHashMapWithExpectedSize(workerIds.size());
+ for (Integer workerId : workerIds) {
+ workerIdToSegmentsMap.put(workerId, Map.of(TableType.OFFLINE.name(),
List.of()));
+ }
+ metadata.setWorkerIdToSegmentsMap(workerIdToSegmentsMap);
+ }
+ } else if (leafPlan) {
assignWorkersToLeafFragment(fragment, context);
} else {
assignWorkersToIntermediateFragment(fragment, context);
}
}
+ private boolean isLocalExchange(List<PlanFragment> children) {
+ if (children.size() != 1) {
+ return false;
+ }
+ PlanNode childPlanNode = children.get(0).getFragmentRoot();
+ return childPlanNode instanceof MailboxSendNode
+ && ((MailboxSendNode) childPlanNode).getDistributionType() ==
RelDistribution.Type.SINGLETON;
+ }
+
private static boolean isLeafPlan(DispatchablePlanMetadata metadata) {
return metadata.getScannedTables().size() == 1;
}
@@ -102,21 +136,22 @@ public class WorkerManager {
// Intermediate stage assign logic
// --------------------------------------------------------------------------
private void assignWorkersToIntermediateFragment(PlanFragment fragment,
DispatchablePlanContext context) {
+ List<PlanFragment> children = fragment.getChildren();
Map<Integer, DispatchablePlanMetadata> metadataMap =
context.getDispatchablePlanMetadataMap();
DispatchablePlanMetadata metadata =
metadataMap.get(fragment.getFragmentId());
- // If the first child is partitioned and can be inherent from this
intermediate stage, use the same worker
- // assignment to avoid shuffling data.
- // When partition parallelism is configured,
- // 1. create multiple intermediate stage workers on the same instance for
each worker in the first child if the
- // first child is a table scan. this is b/c we cannot pre-config
parallelism on leaf stage thus needs fan-out.
- // 2. ignore partition parallelism when first child is NOT table scan b/c
it would've done fan-out already.
- if (isPrePartitionAssignment(fragment, metadataMap)) {
- DispatchablePlanMetadata firstChildMetadata =
metadataMap.get(fragment.getChildren().get(0).getFragmentId());
+ if (isPrePartitionAssignment(children, metadataMap)) {
+ // If the first child is partitioned and can be inherent from this
intermediate stage, use the same worker
+ // assignment to avoid shuffling data.
+ // When partition parallelism is configured,
+ // 1. Create multiple intermediate stage workers on the same instance
for each worker in the first child if the
+ // first child is a table scan. this is b/c we cannot pre-config
parallelism on leaf stage thus needs fan-out.
+ // 2. Ignore partition parallelism when first child is NOT table scan
b/c it would've done fan-out already.
+ DispatchablePlanMetadata firstChildMetadata =
metadataMap.get(children.get(0).getFragmentId());
int partitionParallelism = firstChildMetadata.getPartitionParallelism();
Map<Integer, QueryServerInstance> childWorkerIdToServerInstanceMap =
firstChildMetadata.getWorkerIdToServerInstanceMap();
- if (partitionParallelism == 1 ||
firstChildMetadata.getScannedTables().size() == 0) {
+ if (partitionParallelism == 1 ||
firstChildMetadata.getScannedTables().isEmpty()) {
metadata.setWorkerIdToServerInstanceMap(childWorkerIdToServerInstanceMap);
} else {
int numChildWorkers = childWorkerIdToServerInstanceMap.size();
@@ -158,17 +193,18 @@ public class WorkerManager {
}
}
- private boolean isPrePartitionAssignment(PlanFragment fragment, Map<Integer,
DispatchablePlanMetadata> metadataMap) {
- List<PlanFragment> children = fragment.getChildren();
+ private boolean isPrePartitionAssignment(List<PlanFragment> children,
+ Map<Integer, DispatchablePlanMetadata> metadataMap) {
if (children.isEmpty()) {
return false;
}
// Now, is all children needs to be pre-partitioned by the same function
and size to allow pre-partition assignment
- // TODO1: when partition function is allowed to be configured in exchange
we can relax this condition
- // TODO2: pick the most colocate assignment instead of picking the first
children
+ // TODO:
+ // 1. When partition function is allowed to be configured in exchange we
can relax this condition
+ // 2. Pick the most colocate assignment instead of picking the first
children
String partitionFunction = null;
int partitionCount = 0;
- for (PlanFragment child : fragment.getChildren()) {
+ for (PlanFragment child : children) {
DispatchablePlanMetadata childMetadata =
metadataMap.get(child.getFragmentId());
if (!childMetadata.isPrePartitioned()) {
return false;
@@ -178,8 +214,9 @@ public class WorkerManager {
} else if
(!partitionFunction.equalsIgnoreCase(childMetadata.getPartitionFunction())) {
return false;
}
- int childComputedPartitionCount =
childMetadata.getWorkerIdToServerInstanceMap().size()
- * (isLeafPlan(childMetadata) ?
childMetadata.getPartitionParallelism() : 1);
+ int childComputedPartitionCount =
+ childMetadata.getWorkerIdToServerInstanceMap().size() *
(isLeafPlan(childMetadata)
+ ? childMetadata.getPartitionParallelism() : 1);
if (partitionCount == 0) {
partitionCount = childComputedPartitionCount;
} else if (childComputedPartitionCount != partitionCount) {
@@ -193,7 +230,7 @@ public class WorkerManager {
List<ServerInstance> serverInstances;
Set<String> tableNames = context.getTableNames();
Map<String, ServerInstance> enabledServerInstanceMap =
_routingManager.getEnabledServerInstanceMap();
- if (tableNames.size() == 0) {
+ if (tableNames.isEmpty()) {
// TODO: Short circuit it when no table needs to be scanned
// This could be the case from queries that don't actually fetch values
from the tables. In such cases the
// routing need not be tenant aware.
@@ -435,23 +472,20 @@ public class WorkerManager {
}
if (offlinePartitionInfo == null) {
partitionInfoMap[i] =
- new
PartitionInfo(realtimePartitionInfo._fullyReplicatedServers, null,
- realtimePartitionInfo._segments);
+ new
PartitionInfo(realtimePartitionInfo._fullyReplicatedServers, null,
realtimePartitionInfo._segments);
continue;
}
if (realtimePartitionInfo == null) {
partitionInfoMap[i] =
- new
PartitionInfo(offlinePartitionInfo._fullyReplicatedServers,
offlinePartitionInfo._segments,
- null);
+ new
PartitionInfo(offlinePartitionInfo._fullyReplicatedServers,
offlinePartitionInfo._segments, null);
continue;
}
Set<String> fullyReplicatedServers = new
HashSet<>(offlinePartitionInfo._fullyReplicatedServers);
fullyReplicatedServers.retainAll(realtimePartitionInfo._fullyReplicatedServers);
Preconditions.checkState(!fullyReplicatedServers.isEmpty(),
"Failed to find fully replicated server for partition: %s in
hybrid table: %s", i, tableName);
- partitionInfoMap[i] =
- new PartitionInfo(fullyReplicatedServers,
offlinePartitionInfo._segments,
- realtimePartitionInfo._segments);
+ partitionInfoMap[i] = new PartitionInfo(fullyReplicatedServers,
offlinePartitionInfo._segments,
+ realtimePartitionInfo._segments);
}
return new PartitionTableInfo(partitionInfoMap, timeBoundaryInfo);
} else if (offlineRoutingExists) {
@@ -496,8 +530,7 @@ public class WorkerManager {
for (int i = 0; i < numPartitions; i++) {
TablePartitionInfo.PartitionInfo partitionInfo =
tablePartitionInfoArr[i];
if (partitionInfo != null) {
- partitionInfoMap[i] =
- new PartitionInfo(partitionInfo._fullyReplicatedServers,
partitionInfo._segments, null);
+ partitionInfoMap[i] = new
PartitionInfo(partitionInfo._fullyReplicatedServers, partitionInfo._segments,
null);
}
}
return new PartitionTableInfo(partitionInfoMap, null);
@@ -511,8 +544,7 @@ public class WorkerManager {
for (int i = 0; i < numPartitions; i++) {
TablePartitionInfo.PartitionInfo partitionInfo =
tablePartitionInfoArr[i];
if (partitionInfo != null) {
- partitionInfoMap[i] =
- new PartitionInfo(partitionInfo._fullyReplicatedServers, null,
partitionInfo._segments);
+ partitionInfoMap[i] = new
PartitionInfo(partitionInfo._fullyReplicatedServers, null,
partitionInfo._segments);
}
}
return new PartitionTableInfo(partitionInfoMap, null);
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java
index 389e96f3e8..048af39253 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java
@@ -138,7 +138,12 @@ public class InStageStatsTreeBuilder implements
PlanNodeVisitor<ObjectNode, Void
@Override
public ObjectNode visitJoin(JoinNode node, Void context) {
- return recursiveCase(node, MultiStageOperator.Type.HASH_JOIN);
+ if (node.getJoinStrategy() == JoinNode.JoinStrategy.HASH) {
+ return recursiveCase(node, MultiStageOperator.Type.HASH_JOIN);
+ } else {
+ assert node.getJoinStrategy() == JoinNode.JoinStrategy.LOOKUP;
+ return recursiveCase(node, MultiStageOperator.Type.LOOKUP_JOIN);
+ }
}
@Override
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java
index 9d845561c8..28cebdbcd3 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java
@@ -53,16 +53,12 @@ import org.slf4j.LoggerFactory;
/**
- * This basic {@code BroadcastJoinOperator} implement a basic broadcast join
algorithm.
- * This algorithm assumes that the broadcast table has to fit in memory since
we are not supporting any spilling.
- *
- * For left join, inner join, right join and full join,
- * <p>It takes the right table as the broadcast side and materialize a hash
table. Then for each of the left table row,
- * it looks up for the corresponding row(s) from the hash table and create a
joint row.
- *
- * <p>For each of the data block received from the left table, it will
generate a joint data block.
- * We currently support left join, inner join, right join and full join.
- * The output is in the format of [left_row, right_row]
+ * This {@code HashJoinOperator} implements the hash join algorithm.
+ * <p>This algorithm assumes that the right table has to fit in memory since
we are not supporting any spilling. It
+ * reads the complete hash partitioned right table and materialize the data
into a hash table. Then for each of the left
+ * table row, it looks up for the corresponding row(s) from the hash table and
create a joint row.
+ * <p>For each of the data block received from the left table, it generates a
joint data block. The output is in the
+ * format of [left_row, right_row].
*/
// TODO: Move inequi out of hashjoin.
(https://github.com/apache/pinot/issues/9728)
// TODO: Support memory size based resource limit.
@@ -120,19 +116,17 @@ public class HashJoinOperator extends MultiStageOperator {
public HashJoinOperator(OpChainExecutionContext context, MultiStageOperator
leftInput, DataSchema leftSchema,
MultiStageOperator rightInput, JoinNode node) {
super(context);
- Preconditions.checkState(SUPPORTED_JOIN_TYPES.contains(node.getJoinType()),
- "Join type: " + node.getJoinType() + " is not supported!");
+ _leftInput = leftInput;
+ _rightInput = rightInput;
_joinType = node.getJoinType();
+ Preconditions.checkState(SUPPORTED_JOIN_TYPES.contains(_joinType), "Join
type: % is not supported for hash join",
+ _joinType);
+
_leftKeySelector = KeySelectorFactory.getKeySelector(node.getLeftKeys());
_rightKeySelector = KeySelectorFactory.getKeySelector(node.getRightKeys());
_leftColumnSize = leftSchema.size();
_resultSchema = node.getDataSchema();
_resultColumnSize = _resultSchema.size();
- Preconditions.checkState(_resultColumnSize >= _leftColumnSize,
- "Result column size: %s has to be greater than or equal to left column
size: %s", _resultColumnSize,
- _leftColumnSize);
- _leftInput = leftInput;
- _rightInput = rightInput;
List<RexExpression> nonEquiConditions = node.getNonEquiConditions();
_nonEquiEvaluators = new ArrayList<>(nonEquiConditions.size());
for (RexExpression nonEquiCondition : nonEquiConditions) {
@@ -292,7 +286,7 @@ public class HashJoinOperator extends MultiStageOperator {
return new TransferableBlock(rows, _resultSchema,
DataBlock.Type.ROW);
}
}
- return
TransferableBlockUtils.getEndOfStreamTransferableBlock(_leftSideStats);
+ return leftBlock;
}
assert leftBlock.isDataBlock();
List<Object[]> rows = buildJoinedRows(leftBlock);
@@ -377,7 +371,7 @@ public class HashJoinOperator extends MultiStageOperator {
Object key = _leftKeySelector.getKey(leftRow);
// SEMI-JOIN only checks existence of the key
if (_broadcastRightTable.containsKey(key)) {
- rows.add(joinRow(leftRow, null));
+ rows.add(leftRow);
}
}
@@ -392,7 +386,7 @@ public class HashJoinOperator extends MultiStageOperator {
Object key = _leftKeySelector.getKey(leftRow);
// ANTI-JOIN only checks non-existence of the key
if (!_broadcastRightTable.containsKey(key)) {
- rows.add(joinRow(leftRow, null));
+ rows.add(leftRow);
}
}
@@ -421,18 +415,11 @@ public class HashJoinOperator extends MultiStageOperator {
private Object[] joinRow(@Nullable Object[] leftRow, @Nullable Object[]
rightRow) {
Object[] resultRow = new Object[_resultColumnSize];
- int idx = 0;
if (leftRow != null) {
- for (Object obj : leftRow) {
- resultRow[idx++] = obj;
- }
+ System.arraycopy(leftRow, 0, resultRow, 0, leftRow.length);
}
- // This is needed since left row can be null and we need to advance the
idx to the beginning of right row.
- idx = _leftColumnSize;
if (rightRow != null) {
- for (Object obj : rightRow) {
- resultRow[idx++] = obj;
- }
+ System.arraycopy(rightRow, 0, resultRow, _leftColumnSize,
rightRow.length);
}
return resultRow;
}
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
index 3baab8f536..c185d63e56 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
@@ -120,6 +120,18 @@ public class LeafStageTransferableBlockOperator extends
MultiStageOperator {
_statMap.merge(StatKey.TABLE, tableName);
}
+ public List<ServerQueryRequest> getRequests() {
+ return _requests;
+ }
+
+ public DataSchema getDataSchema() {
+ return _dataSchema;
+ }
+
+ public MultiStageQueryStats getQueryStats() {
+ return MultiStageQueryStats.createLeaf(_context.getStageId(), _statMap);
+ }
+
@Override
public void registerExecution(long time, int numRows) {
_statMap.merge(StatKey.EXECUTION_TIME_MS, time);
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LookupJoinOperator.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LookupJoinOperator.java
new file mode 100644
index 0000000000..1f43543018
--- /dev/null
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LookupJoinOperator.java
@@ -0,0 +1,260 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.query.runtime.operator;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.pinot.common.datablock.DataBlock;
+import org.apache.pinot.common.datatable.StatMap;
+import org.apache.pinot.common.utils.DataSchema;
+import org.apache.pinot.core.data.manager.offline.DimensionTableDataManager;
+import org.apache.pinot.core.query.request.ServerQueryRequest;
+import org.apache.pinot.core.query.request.context.QueryContext;
+import org.apache.pinot.query.planner.logical.RexExpression;
+import org.apache.pinot.query.planner.plannode.JoinNode;
+import org.apache.pinot.query.runtime.blocks.TransferableBlock;
+import org.apache.pinot.query.runtime.operator.operands.TransformOperand;
+import
org.apache.pinot.query.runtime.operator.operands.TransformOperandFactory;
+import org.apache.pinot.query.runtime.plan.MultiStageQueryStats;
+import org.apache.pinot.query.runtime.plan.OpChainExecutionContext;
+import org.apache.pinot.spi.data.readers.PrimaryKey;
+import org.apache.pinot.spi.utils.BooleanUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * This {@code LookupJoinOperator} implements the lookup join algorithm.
+ * <p>This algorithm assumes that the right table is a dimension table which
is preloaded. For each of the left table
+ * row, it looks up for the corresponding row from the dimension table and
create a joint row.
+ * <p>For each of the data block received from the left table, it generates a
joint data block. The output is in the
+ * format of [left_row, right_row].
+ * <p>Since right table is a dimension table which is replicated across all
servers, RIGHT and FULL join are not
+ * supported to avoid duplication.
+ */
+public class LookupJoinOperator extends MultiStageOperator {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(LookupJoinOperator.class);
+ private static final String EXPLAIN_NAME = "LOOKUP_JOIN";
+ private static final Set<JoinRelType> SUPPORTED_JOIN_TYPES =
+ Set.of(JoinRelType.INNER, JoinRelType.LEFT, JoinRelType.SEMI,
JoinRelType.ANTI);
+
+ private final MultiStageOperator _leftInput;
+ private final LeafStageTransferableBlockOperator _rightInput;
+ private final JoinRelType _joinType;
+ private final int[] _leftKeyIds;
+ private final DimensionTableDataManager _rightTable;
+ private final String[] _rightColumns;
+ private final DataSchema _resultSchema;
+ private final int _resultColumnSize;
+ private final List<TransformOperand> _nonEquiEvaluators;
+ private final StatMap<StatKey> _statMap = new StatMap<>(StatKey.class);
+
+ public LookupJoinOperator(OpChainExecutionContext context,
MultiStageOperator leftInput,
+ MultiStageOperator rightInput, JoinNode node) {
+ super(context);
+ _leftInput = leftInput;
+ Preconditions.checkState(rightInput instanceof
LeafStageTransferableBlockOperator,
+ "Right input must be leaf stage operator");
+ _rightInput = (LeafStageTransferableBlockOperator) rightInput;
+ _joinType = node.getJoinType();
+ Preconditions.checkState(SUPPORTED_JOIN_TYPES.contains(_joinType), "Join
type: % is not supported for lookup join",
+ _joinType);
+
+ List<Integer> leftKeys = node.getLeftKeys();
+ _leftKeyIds = new int[leftKeys.size()];
+ for (int i = 0; i < leftKeys.size(); i++) {
+ _leftKeyIds[i] = leftKeys.get(i);
+ }
+ List<ServerQueryRequest> leafStageRequests = _rightInput.getRequests();
+ Preconditions.checkState(leafStageRequests.size() == 1, "Lookup join
cannot be applied to hybrid tables");
+ QueryContext queryContext = leafStageRequests.get(0).getQueryContext();
+ String rightTableName = queryContext.getTableName();
+ _rightTable =
DimensionTableDataManager.getInstanceByTableName(rightTableName);
+ Preconditions.checkState(_rightTable != null, "Failed to find dimension
table for name: %s", rightTableName);
+ _rightColumns = _rightInput.getDataSchema().getColumnNames();
+ _resultSchema = node.getDataSchema();
+ _resultColumnSize = _resultSchema.size();
+ List<RexExpression> nonEquiConditions = node.getNonEquiConditions();
+ _nonEquiEvaluators = new ArrayList<>(nonEquiConditions.size());
+ for (RexExpression nonEquiCondition : nonEquiConditions) {
+
_nonEquiEvaluators.add(TransformOperandFactory.getTransformOperand(nonEquiCondition,
_resultSchema));
+ }
+ }
+
+ @Override
+ public void registerExecution(long time, int numRows) {
+ _statMap.merge(LookupJoinOperator.StatKey.EXECUTION_TIME_MS, time);
+ _statMap.merge(LookupJoinOperator.StatKey.EMITTED_ROWS, numRows);
+ }
+
+ @Override
+ public Type getOperatorType() {
+ return Type.LOOKUP_JOIN;
+ }
+
+ @Override
+ protected Logger logger() {
+ return LOGGER;
+ }
+
+ @Override
+ public List<MultiStageOperator> getChildOperators() {
+ return List.of(_leftInput, _rightInput);
+ }
+
+ @Override
+ public String toExplainString() {
+ return EXPLAIN_NAME;
+ }
+
+ @Override
+ protected TransferableBlock getNextBlock() {
+ // Keep reading the input blocks until we find a match row or all blocks
are processed.
+ // TODO: Consider batching the rows to improve performance.
+ while (true) {
+ TransferableBlock leftBlock = _leftInput.nextBlock();
+ if (leftBlock.isErrorBlock()) {
+ return leftBlock;
+ }
+ if (leftBlock.isSuccessfulEndOfStreamBlock()) {
+ MultiStageQueryStats leftStats = leftBlock.getQueryStats();
+ assert leftStats != null;
+ leftStats.mergeInOrder(_rightInput.getQueryStats(), getOperatorType(),
_statMap);
+ return leftBlock;
+ }
+ assert leftBlock.isDataBlock();
+ List<Object[]> rows = buildJoinedRows(leftBlock);
+ sampleAndCheckInterruption();
+ if (!rows.isEmpty()) {
+ return new TransferableBlock(rows, _resultSchema, DataBlock.Type.ROW);
+ }
+ }
+ }
+
+ private List<Object[]> buildJoinedRows(TransferableBlock leftBlock) {
+ switch (_joinType) {
+ case SEMI:
+ return buildJoinedDataBlockSemi(leftBlock);
+ case ANTI:
+ return buildJoinedDataBlockAnti(leftBlock);
+ default: { // INNER, LEFT, RIGHT, FULL
+ return buildJoinedDataBlockDefault(leftBlock);
+ }
+ }
+ }
+
+ private List<Object[]> buildJoinedDataBlockDefault(TransferableBlock
leftBlock) {
+ List<Object[]> container = leftBlock.getContainer();
+ ArrayList<Object[]> rows = new ArrayList<>(container.size());
+
+ for (Object[] leftRow : container) {
+ PrimaryKey key = getKey(leftRow);
+ Object[] rightRow = _rightTable.lookupValues(key, _rightColumns);
+ if (rightRow != null) {
+ // TODO: Optimize this to avoid unnecessary object copy.
+ Object[] resultRow = joinRow(leftRow, rightRow);
+ if (_nonEquiEvaluators.isEmpty() || _nonEquiEvaluators.stream()
+ .allMatch(evaluator ->
BooleanUtils.isTrueInternalValue(evaluator.apply(resultRow)))) {
+ rows.add(resultRow);
+ continue;
+ }
+ }
+ if (needUnmatchedLeftRows()) {
+ rows.add(joinRow(leftRow, null));
+ }
+ }
+
+ return rows;
+ }
+
+ private List<Object[]> buildJoinedDataBlockSemi(TransferableBlock leftBlock)
{
+ List<Object[]> container = leftBlock.getContainer();
+ List<Object[]> rows = new ArrayList<>(container.size());
+ for (Object[] leftRow : container) {
+ if (_rightTable.containsKey(getKey(leftRow))) {
+ rows.add(leftRow);
+ }
+ }
+ return rows;
+ }
+
+ private List<Object[]> buildJoinedDataBlockAnti(TransferableBlock leftBlock)
{
+ List<Object[]> container = leftBlock.getContainer();
+ List<Object[]> rows = new ArrayList<>(container.size());
+ for (Object[] leftRow : container) {
+ if (!_rightTable.containsKey(getKey(leftRow))) {
+ rows.add(leftRow);
+ }
+ }
+ return rows;
+ }
+
+ private PrimaryKey getKey(Object[] row) {
+ Object[] values = new Object[_leftKeyIds.length];
+ for (int i = 0; i < _leftKeyIds.length; i++) {
+ values[i] = row[_leftKeyIds[i]];
+ }
+ return new PrimaryKey(values);
+ }
+
+ private Object[] joinRow(Object[] leftRow, @Nullable Object[] rightRow) {
+ Object[] resultRow = new Object[_resultColumnSize];
+ System.arraycopy(leftRow, 0, resultRow, 0, leftRow.length);
+ if (rightRow != null) {
+ System.arraycopy(rightRow, 0, resultRow, leftRow.length,
rightRow.length);
+ }
+ return resultRow;
+ }
+
+ private boolean needUnmatchedLeftRows() {
+ return _joinType == JoinRelType.LEFT;
+ }
+
+ public enum StatKey implements StatMap.Key {
+ //@formatter:off
+ EXECUTION_TIME_MS(StatMap.Type.LONG) {
+ @Override
+ public boolean includeDefaultInJson() {
+ return true;
+ }
+ },
+ EMITTED_ROWS(StatMap.Type.LONG) {
+ @Override
+ public boolean includeDefaultInJson() {
+ return true;
+ }
+ };
+ //@formatter:on
+
+ private final StatMap.Type _type;
+
+ StatKey(StatMap.Type type) {
+ _type = type;
+ }
+
+ @Override
+ public StatMap.Type getType() {
+ return _type;
+ }
+ }
+}
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MultiStageOperator.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MultiStageOperator.java
index bfb02a7006..300f474fcc 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MultiStageOperator.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MultiStageOperator.java
@@ -207,6 +207,7 @@ public abstract class MultiStageOperator
* <p>
* This is mostly used in the context of stats collection, where we use this
enum in the serialization form in order
* to identify the type of the stats in an efficient way.
+ * DO NOT change the order of the enum values, as the ordinal is used in
serialization.
*/
public enum Type {
AGGREGATE(AggregateOperator.StatKey.class) {
@@ -391,7 +392,15 @@ public abstract class MultiStageOperator
serverMetrics.addMeteredGlobalValue(ServerMeter.WINDOW_TIMES_MAX_ROWS_REACHED,
1);
}
}
- },;
+ },
+ LOOKUP_JOIN(LookupJoinOperator.StatKey.class) {
+ @Override
+ public void mergeInto(BrokerResponseNativeV2 response, StatMap<?> map) {
+ @SuppressWarnings("unchecked")
+ StatMap<LookupJoinOperator.StatKey> stats =
(StatMap<LookupJoinOperator.StatKey>) map;
+
response.mergeMaxRowsInOperator(stats.getLong(LookupJoinOperator.StatKey.EMITTED_ROWS));
+ }
+ };
private final Class _statKeyClass;
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/PlanNodeToOpChain.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/PlanNodeToOpChain.java
index 742419d696..9570d77b47 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/PlanNodeToOpChain.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/PlanNodeToOpChain.java
@@ -43,6 +43,7 @@ import
org.apache.pinot.query.runtime.operator.IntersectAllOperator;
import org.apache.pinot.query.runtime.operator.IntersectOperator;
import
org.apache.pinot.query.runtime.operator.LeafStageTransferableBlockOperator;
import org.apache.pinot.query.runtime.operator.LiteralValueOperator;
+import org.apache.pinot.query.runtime.operator.LookupJoinOperator;
import org.apache.pinot.query.runtime.operator.MailboxReceiveOperator;
import org.apache.pinot.query.runtime.operator.MailboxSendOperator;
import org.apache.pinot.query.runtime.operator.MinusAllOperator;
@@ -174,8 +175,16 @@ public class PlanNodeToOpChain {
public MultiStageOperator visitJoin(JoinNode node, OpChainExecutionContext
context) {
List<PlanNode> inputs = node.getInputs();
PlanNode left = inputs.get(0);
+ MultiStageOperator leftOperator = visit(left, context);
PlanNode right = inputs.get(1);
- return new HashJoinOperator(context, visit(left, context),
left.getDataSchema(), visit(right, context), node);
+ MultiStageOperator rightOperator = visit(right, context);
+ JoinNode.JoinStrategy joinStrategy = node.getJoinStrategy();
+ if (joinStrategy == JoinNode.JoinStrategy.HASH) {
+ return new HashJoinOperator(context, leftOperator,
left.getDataSchema(), rightOperator, node);
+ } else {
+ assert joinStrategy == JoinNode.JoinStrategy.LOOKUP;
+ return new LookupJoinOperator(context, leftOperator, rightOperator,
node);
+ }
}
@Override
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestVisitor.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestVisitor.java
index 87dbd27a71..9de79d15e2 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestVisitor.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestVisitor.java
@@ -18,9 +18,11 @@
*/
package org.apache.pinot.query.runtime.plan.server;
+import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.apache.pinot.calcite.rel.logical.PinotRelExchangeType;
import org.apache.pinot.common.datablock.DataBlock;
import org.apache.pinot.common.request.DataSource;
import org.apache.pinot.common.request.Expression;
@@ -125,28 +127,39 @@ public class ServerPlanRequestVisitor implements
PlanNodeVisitor<Void, ServerPla
@Override
public Void visitJoin(JoinNode node, ServerPlanRequestContext context) {
- // visit only the static side, turn the dynamic side into a lookup from
the pipeline breaker resultDataContainer
- PlanNode staticSide = node.getInputs().get(0);
- PlanNode dynamicSide = node.getInputs().get(1);
- if (staticSide instanceof MailboxReceiveNode) {
- dynamicSide = node.getInputs().get(0);
- staticSide = node.getInputs().get(1);
- }
- if (visit(staticSide, context)) {
- PipelineBreakerResult pipelineBreakerResult =
context.getPipelineBreakerResult();
- int resultMapId = pipelineBreakerResult.getNodeIdMap().get(dynamicSide);
- List<TransferableBlock> transferableBlocks =
- pipelineBreakerResult.getResultMap().getOrDefault(resultMapId,
Collections.emptyList());
- List<Object[]> resultDataContainer = new ArrayList<>();
- DataSchema dataSchema = dynamicSide.getDataSchema();
- for (TransferableBlock block : transferableBlocks) {
- if (block.getType() == DataBlock.Type.ROW) {
- resultDataContainer.addAll(block.getContainer());
+ // We can reach here for dynamic broadcast SEMI join and lookup join.
+ List<PlanNode> inputs = node.getInputs();
+ PlanNode left = inputs.get(0);
+ PlanNode right = inputs.get(1);
+
+ if (right instanceof MailboxReceiveNode
+ && ((MailboxReceiveNode) right).getExchangeType() ==
PinotRelExchangeType.PIPELINE_BREAKER) {
+ // For dynamic broadcast SEMI join, right child should be a
PIPELINE_BREAKER exchange. Visit the left child and
+ // attach the dynamic filter to the query.
+ if (visit(left, context)) {
+ PipelineBreakerResult pipelineBreakerResult =
context.getPipelineBreakerResult();
+ int resultMapId = pipelineBreakerResult.getNodeIdMap().get(right);
+ List<TransferableBlock> transferableBlocks =
+ pipelineBreakerResult.getResultMap().getOrDefault(resultMapId,
Collections.emptyList());
+ List<Object[]> resultDataContainer = new ArrayList<>();
+ DataSchema dataSchema = right.getDataSchema();
+ for (TransferableBlock block : transferableBlocks) {
+ if (block.getType() == DataBlock.Type.ROW) {
+ resultDataContainer.addAll(block.getContainer());
+ }
}
+ ServerPlanRequestUtils.attachDynamicFilter(context.getPinotQuery(),
node.getLeftKeys(), node.getRightKeys(),
+ resultDataContainer, dataSchema);
+ }
+ } else {
+ // For lookup join, visit the right child and set it as the leaf
boundary.
+ Preconditions.checkState(node.getJoinStrategy() ==
JoinNode.JoinStrategy.LOOKUP,
+ "Leaf stage should not visit regular JoinNode");
+ if (visit(right, context)) {
+ context.setLeafStageBoundaryNode(right);
}
- ServerPlanRequestUtils.attachDynamicFilter(context.getPinotQuery(),
node.getLeftKeys(), node.getRightKeys(),
- resultDataContainer, dataSchema);
}
+
return null;
}
diff --git
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java
index 983b2bf937..5b29817b7b 100644
---
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java
+++
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java
@@ -349,15 +349,15 @@ public class HashJoinOperatorTest {
when(_rightInput.nextBlock()).thenReturn(
OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new
Object[]{2, "BB"}, new Object[]{3, "BB"}))
.thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0));
- DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar", "foo",
"bar"}, new ColumnDataType[]{
- ColumnDataType.INT, ColumnDataType.STRING, ColumnDataType.INT,
ColumnDataType.STRING
+ DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar"}, new
ColumnDataType[]{
+ ColumnDataType.INT, ColumnDataType.STRING
});
HashJoinOperator operator =
getOperator(leftSchema, resultSchema, JoinRelType.SEMI, List.of(1),
List.of(1), List.of());
List<Object[]> resultRows = operator.nextBlock().getContainer();
assertEquals(resultRows.size(), 2);
- assertEquals(resultRows.get(0), new Object[]{1, "Aa", null, null});
- assertEquals(resultRows.get(1), new Object[]{2, "BB", null, null});
+ assertEquals(resultRows.get(0), new Object[]{1, "Aa"});
+ assertEquals(resultRows.get(1), new Object[]{2, "BB"});
assertTrue(operator.nextBlock().isSuccessfulEndOfStreamBlock());
}
@@ -408,14 +408,14 @@ public class HashJoinOperatorTest {
when(_rightInput.nextBlock()).thenReturn(
OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new
Object[]{2, "BB"}, new Object[]{3, "BB"}))
.thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0));
- DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar", "foo",
"bar"}, new ColumnDataType[]{
- ColumnDataType.INT, ColumnDataType.STRING, ColumnDataType.INT,
ColumnDataType.STRING
+ DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar"}, new
ColumnDataType[]{
+ ColumnDataType.INT, ColumnDataType.STRING
});
HashJoinOperator operator =
getOperator(leftSchema, resultSchema, JoinRelType.ANTI, List.of(1),
List.of(1), List.of());
List<Object[]> resultRows = operator.nextBlock().getContainer();
assertEquals(resultRows.size(), 1);
- assertEquals(resultRows.get(0), new Object[]{4, "CC", null, null});
+ assertEquals(resultRows.get(0), new Object[]{4, "CC"});
assertTrue(operator.nextBlock().isSuccessfulEndOfStreamBlock());
}
@@ -574,8 +574,7 @@ public class HashJoinOperatorTest {
OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new
Object[]{2, "Aa"}, new Object[]{3, "Aa"}))
.thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{4, "Aa"},
new Object[]{5, "Aa"}))
.thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0));
- when(_rightInput.nextBlock()).thenReturn(
- OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}))
+
when(_rightInput.nextBlock()).thenReturn(OperatorTestUtil.block(rightSchema,
new Object[]{2, "Aa"}))
.thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0));
DataSchema resultSchema =
new DataSchema(new String[]{"int_col1", "string_col1", "int_co2",
"string_col2"}, new ColumnDataType[]{
@@ -600,7 +599,8 @@ public class HashJoinOperatorTest {
List<Integer> leftKeys, List<Integer> rightKeys, List<RexExpression>
nonEquiConditions,
PlanNode.NodeHint nodeHint) {
return new HashJoinOperator(OperatorTestUtil.getTracingContext(),
_leftInput, leftSchema, _rightInput,
- new JoinNode(-1, resultSchema, nodeHint, List.of(), joinType,
leftKeys, rightKeys, nonEquiConditions));
+ new JoinNode(-1, resultSchema, nodeHint, List.of(), joinType,
leftKeys, rightKeys, nonEquiConditions,
+ JoinNode.JoinStrategy.HASH));
}
private HashJoinOperator getOperator(DataSchema leftSchema, DataSchema
resultSchema, JoinRelType joinType,
diff --git
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MultiStageAccountingTest.java
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MultiStageAccountingTest.java
index 2821de5e73..5b38dcdab5 100644
---
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MultiStageAccountingTest.java
+++
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MultiStageAccountingTest.java
@@ -178,7 +178,7 @@ public class MultiStageAccountingTest implements ITest {
});
return new HashJoinOperator(OperatorTestUtil.getTracingContext(),
leftInput, leftSchema, rightInput,
new JoinNode(-1, resultSchema, PlanNode.NodeHint.EMPTY, List.of(),
JoinRelType.INNER, List.of(0), List.of(0),
- List.of()));
+ List.of(), JoinNode.JoinStrategy.HASH));
}
private static MultiStageOperator getSortOperator() {
diff --git
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java
index 8c71bd00a8..26635cb8f8 100644
---
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java
+++
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java
@@ -142,7 +142,7 @@ public class PipelineBreakerExecutorTest {
MailboxReceiveNode mailboxReceiveNode2 = getPBReceiveNode(2);
JoinNode joinNode =
new JoinNode(0, DATA_SCHEMA, PlanNode.NodeHint.EMPTY,
List.of(mailboxReceiveNode1, mailboxReceiveNode2),
- JoinRelType.INNER, List.of(0), List.of(0), List.of());
+ JoinRelType.INNER, List.of(0), List.of(0), List.of(),
JoinNode.JoinStrategy.HASH);
StagePlan stagePlan = new StagePlan(joinNode, _stageMetadata);
// when
@@ -231,7 +231,7 @@ public class PipelineBreakerExecutorTest {
MailboxReceiveNode incorrectlyConfiguredMailboxNode = getPBReceiveNode(3);
JoinNode joinNode = new JoinNode(0, DATA_SCHEMA, PlanNode.NodeHint.EMPTY,
List.of(mailboxReceiveNode1, incorrectlyConfiguredMailboxNode),
JoinRelType.INNER, List.of(0), List.of(0),
- List.of());
+ List.of(), JoinNode.JoinStrategy.HASH);
StagePlan stagePlan = new StagePlan(joinNode, _stageMetadata);
// when
@@ -264,7 +264,7 @@ public class PipelineBreakerExecutorTest {
MailboxReceiveNode incorrectlyConfiguredMailboxNode = getPBReceiveNode(2);
JoinNode joinNode = new JoinNode(0, DATA_SCHEMA, PlanNode.NodeHint.EMPTY,
List.of(mailboxReceiveNode1, incorrectlyConfiguredMailboxNode),
JoinRelType.INNER, List.of(0), List.of(0),
- List.of());
+ List.of(), JoinNode.JoinStrategy.HASH);
StagePlan stagePlan = new StagePlan(joinNode, _stageMetadata);
// when
diff --git
a/pinot-tools/src/main/java/org/apache/pinot/tools/LookupJoinEngineQuickStart.java
b/pinot-tools/src/main/java/org/apache/pinot/tools/LookupJoinEngineQuickStart.java
new file mode 100644
index 0000000000..a6fb1a417d
--- /dev/null
+++
b/pinot-tools/src/main/java/org/apache/pinot/tools/LookupJoinEngineQuickStart.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.tools;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.tools.admin.PinotAdministrator;
+
+
+public class LookupJoinEngineQuickStart extends MultistageEngineQuickStart {
+ private static final String QUICKSTART_IDENTIFIER = "LOOKUP_JOIN";
+ // Reuse userAttributes from ColocatedJoinEngineQuickStart
+ private static final String[] LOOKUP_JOIN_DIRECTORIES = new String[]{
+ "examples/batch/colocated/userAttributes",
"examples/batch/lookup/userGroupsDim"
+ };
+
+ @Override
+ public List<String> types() {
+ return Collections.singletonList(QUICKSTART_IDENTIFIER);
+ }
+
+ @Override
+ public Map<String, Object> getConfigOverrides() {
+ Map<String, Object> overrides = new HashMap<>(super.getConfigOverrides());
+
overrides.put(CommonConstants.Broker.CONFIG_OF_ENABLE_PARTITION_METADATA_MANAGER,
"true");
+ return overrides;
+ }
+
+ @Override
+ public String[] getDefaultBatchTableDirectories() {
+ return LOOKUP_JOIN_DIRECTORIES;
+ }
+
+ @Override
+ protected int getNumQuickstartRunnerServers() {
+ return 4;
+ }
+
+ public static void main(String[] args)
+ throws Exception {
+ List<String> arguments = new ArrayList<>();
+ arguments.addAll(Arrays.asList("QuickStart", "-type",
QUICKSTART_IDENTIFIER));
+ arguments.addAll(Arrays.asList(args));
+ PinotAdministrator.main(arguments.toArray(new String[0]));
+ }
+}
diff --git
a/pinot-tools/src/main/resources/examples/batch/colocated/userGroups/userGroups_schema.json
b/pinot-tools/src/main/resources/examples/batch/colocated/userGroups/userGroups_schema.json
index c4772df7a2..7dad5360b2 100644
---
a/pinot-tools/src/main/resources/examples/batch/colocated/userGroups/userGroups_schema.json
+++
b/pinot-tools/src/main/resources/examples/batch/colocated/userGroups/userGroups_schema.json
@@ -1,6 +1,5 @@
{
- "metricFieldSpecs": [
- ],
+ "schemaName": "userGroups",
"dimensionFieldSpecs": [
{
"dataType": "STRING",
@@ -10,6 +9,5 @@
"dataType": "STRING",
"name": "userUUID"
}
- ],
- "schemaName": "userGroups"
+ ]
}
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/ingestionJobSpec.yaml
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/ingestionJobSpec.yaml
new file mode 100644
index 0000000000..93712178be
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/ingestionJobSpec.yaml
@@ -0,0 +1,140 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# executionFrameworkSpec: Defines ingestion jobs to be running.
+executionFrameworkSpec:
+
+ # name: execution framework name
+ name: 'standalone'
+
+ # Class to use for segment generation and different push types.
+ segmentGenerationJobRunnerClassName:
'org.apache.pinot.plugin.ingestion.batch.standalone.SegmentGenerationJobRunner'
+ segmentTarPushJobRunnerClassName:
'org.apache.pinot.plugin.ingestion.batch.standalone.SegmentTarPushJobRunner'
+ segmentUriPushJobRunnerClassName:
'org.apache.pinot.plugin.ingestion.batch.standalone.SegmentUriPushJobRunner'
+ segmentMetadataPushJobRunnerClassName:
'org.apache.pinot.plugin.ingestion.batch.standalone.SegmentMetadataPushJobRunner'
+
+
+# jobType: Pinot ingestion job type.
+# Supported job types are defined in PinotIngestionJobType class.
+# 'SegmentCreation'
+# 'SegmentTarPush'
+# 'SegmentUriPush'
+# 'SegmentMetadataPush'
+# 'SegmentCreationAndTarPush'
+# 'SegmentCreationAndUriPush'
+# 'SegmentCreationAndMetadataPush'
+jobType: SegmentCreationAndTarPush
+
+# inputDirURI: Root directory of input data, expected to have scheme
configured in PinotFS.
+inputDirURI: 'examples/batch/lookup/userGroupsDim/rawdata'
+
+# includeFileNamePattern: include file name pattern, supported glob pattern.
+# Sample usage:
+# 'glob:*.avro' will include all avro files just under the inputDirURI, not
sub directories;
+# 'glob:**/*.avro' will include all the avro files under inputDirURI
recursively.
+includeFileNamePattern: 'glob:**/*.csv'
+
+# excludeFileNamePattern: exclude file name pattern, supported glob pattern.
+# Sample usage:
+# 'glob:*.avro' will exclude all avro files just under the inputDirURI, not
sub directories;
+# 'glob:**/*.avro' will exclude all the avro files under inputDirURI
recursively.
+# _excludeFileNamePattern: ''
+
+# outputDirURI: Root directory of output segments, expected to have scheme
configured in PinotFS.
+outputDirURI: 'examples/batch/lookup/userGroupsDim/segments'
+
+# overwriteOutput: Overwrite output segments if existed.
+overwriteOutput: true
+
+# pinotFSSpecs: defines all related Pinot file systems.
+pinotFSSpecs:
+
+ - # scheme: used to identify a PinotFS.
+ # E.g. local, hdfs, dbfs, etc
+ scheme: file
+
+ # className: Class name used to create the PinotFS instance.
+ # E.g.
+ # org.apache.pinot.spi.filesystem.LocalPinotFS is used for local
filesystem
+ # org.apache.pinot.plugin.filesystem.AzurePinotFS is used for Azure Data
Lake
+ # org.apache.pinot.plugin.filesystem.HadoopPinotFS is used for HDFS
+ className: org.apache.pinot.spi.filesystem.LocalPinotFS
+
+# recordReaderSpec: defines all record reader
+recordReaderSpec:
+
+ # dataFormat: Record data format, e.g. 'avro', 'parquet', 'orc', 'csv',
'json', 'thrift' etc.
+ dataFormat: 'csv'
+
+ # className: Corresponding RecordReader class name.
+ # E.g.
+ # org.apache.pinot.plugin.inputformat.avro.AvroRecordReader
+ # org.apache.pinot.plugin.inputformat.csv.CSVRecordReader
+ # org.apache.pinot.plugin.inputformat.parquet.ParquetRecordReader
+ # org.apache.pinot.plugin.inputformat.parquet.ParquetNativeRecordReader
+ # org.apache.pinot.plugin.inputformat.json.JSONRecordReader
+ # org.apache.pinot.plugin.inputformat.orc.ORCRecordReader
+ # org.apache.pinot.plugin.inputformat.thrift.ThriftRecordReader
+ className: 'org.apache.pinot.plugin.inputformat.csv.CSVRecordReader'
+
+ # configClassName: Corresponding RecordReaderConfig class name, it's
mandatory for CSV and Thrift file format.
+ # E.g.
+ # org.apache.pinot.plugin.inputformat.csv.CSVRecordReaderConfig
+ # org.apache.pinot.plugin.inputformat.thrift.ThriftRecordReaderConfig
+ configClassName:
'org.apache.pinot.plugin.inputformat.csv.CSVRecordReaderConfig'
+
+ # configs: Used to init RecordReaderConfig class name, this config is
required for CSV and Thrift data format.
+ configs:
+
+
+# tableSpec: defines table name and where to fetch corresponding table config
and table schema.
+tableSpec:
+
+ # tableName: Table name
+ tableName: 'userGroupsDim'
+
+ # schemaURI: defines where to read the table schema, supports PinotFS or
HTTP.
+ # E.g.
+ # hdfs://path/to/table_schema.json
+ # http://localhost:9000/tables/myTable/schema
+ schemaURI: 'http://localhost:9000/tables/userGroupsDim/schema'
+
+ # tableConfigURI: defines where to reade the table config.
+ # Supports using PinotFS or HTTP.
+ # E.g.
+ # hdfs://path/to/table_config.json
+ # http://localhost:9000/tables/myTable
+ # Note that the API to read Pinot table config directly from pinot
controller contains a JSON wrapper.
+ # The real table config is the object under the field 'OFFLINE'.
+ tableConfigURI: 'http://localhost:9000/tables/userGroupsDim'
+
+# pinotClusterSpecs: defines the Pinot Cluster Access Point.
+pinotClusterSpecs:
+ - # controllerURI: used to fetch table/schema information and data push.
+ # E.g. http://localhost:9000
+ controllerURI: 'http://localhost:9000'
+
+# pushJobSpec: defines segment push job related configuration.
+pushJobSpec:
+
+ # pushAttempts: number of attempts for push job, default is 1, which means
no retry.
+ pushAttempts: 2
+
+ # pushRetryIntervalMillis: retry wait Ms, default to 1 second.
+ pushRetryIntervalMillis: 1000
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p0.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p0.csv
new file mode 100644
index 0000000000..7708a2c3a6
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p0.csv
@@ -0,0 +1,8 @@
+userUUID,groupUUID
+user-1,group-0
+user-1,group-1
+user-2,group-0
+user-2,group-1
+user-7,group-1
+user-7,group-2
+user-15,group-1
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p1.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p1.csv
new file mode 100644
index 0000000000..675175d70c
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p1.csv
@@ -0,0 +1,7 @@
+userUUID,groupUUID
+user-6,group-0
+user-6,group-1
+user-6,group-2
+user-8,group-0
+user-14,group-0
+user-24,group-0
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p2.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p2.csv
new file mode 100644
index 0000000000..4afa948eaf
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p2.csv
@@ -0,0 +1,9 @@
+userUUID,groupUUID
+user-5,group-0
+user-10,group-0
+user-11,group-0
+user-12,group-0
+user-16,group-0
+user-18,group-0
+user-19,group-0
+user-25,group-0
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p3.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p3.csv
new file mode 100644
index 0000000000..3a9e76c651
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p3.csv
@@ -0,0 +1,2455 @@
+userUUID,groupUUID
+user-0,group-0
+user-3,group-0
+user-4,group-0
+user-9,group-0
+user-13,group-0
+user-20,group-0
+user-22,group-0
+user-29,group-0
+user-30,group-0
+user-36,group-0
+user-37,group-0
+user-40,group-0
+user-51,group-0
+user-55,group-0
+user-63,group-0
+user-64,group-0
+user-76,group-0
+user-77,group-0
+user-81,group-0
+user-82,group-0
+user-84,group-0
+user-86,group-0
+user-102,group-1
+user-104,group-1
+user-109,group-1
+user-111,group-1
+user-112,group-1
+user-114,group-1
+user-116,group-1
+user-122,group-1
+user-133,group-1
+user-136,group-1
+user-141,group-1
+user-142,group-1
+user-147,group-1
+user-152,group-1
+user-154,group-1
+user-155,group-1
+user-156,group-1
+user-158,group-1
+user-167,group-1
+user-168,group-1
+user-171,group-1
+user-176,group-1
+user-180,group-1
+user-187,group-1
+user-191,group-1
+user-194,group-1
+user-195,group-1
+user-204,group-2
+user-205,group-2
+user-207,group-2
+user-213,group-2
+user-220,group-2
+user-221,group-2
+user-223,group-2
+user-230,group-2
+user-238,group-2
+user-239,group-2
+user-246,group-2
+user-247,group-2
+user-252,group-2
+user-253,group-2
+user-256,group-2
+user-259,group-2
+user-262,group-2
+user-264,group-2
+user-268,group-2
+user-269,group-2
+user-277,group-2
+user-287,group-2
+user-293,group-2
+user-296,group-2
+user-299,group-2
+user-302,group-3
+user-304,group-3
+user-309,group-3
+user-310,group-3
+user-320,group-3
+user-321,group-3
+user-322,group-3
+user-326,group-3
+user-332,group-3
+user-334,group-3
+user-337,group-3
+user-338,group-3
+user-352,group-3
+user-357,group-3
+user-359,group-3
+user-361,group-3
+user-364,group-3
+user-367,group-3
+user-369,group-3
+user-370,group-3
+user-371,group-3
+user-376,group-3
+user-379,group-3
+user-383,group-3
+user-387,group-3
+user-390,group-3
+user-391,group-3
+user-392,group-3
+user-394,group-3
+user-399,group-3
+user-417,group-4
+user-420,group-4
+user-423,group-4
+user-424,group-4
+user-428,group-4
+user-430,group-4
+user-432,group-4
+user-434,group-4
+user-440,group-4
+user-443,group-4
+user-444,group-4
+user-448,group-4
+user-449,group-4
+user-450,group-4
+user-457,group-4
+user-458,group-4
+user-471,group-4
+user-472,group-4
+user-489,group-4
+user-504,group-5
+user-505,group-5
+user-515,group-5
+user-520,group-5
+user-523,group-5
+user-524,group-5
+user-531,group-5
+user-533,group-5
+user-535,group-5
+user-537,group-5
+user-542,group-5
+user-543,group-5
+user-557,group-5
+user-574,group-5
+user-576,group-5
+user-582,group-5
+user-589,group-5
+user-591,group-5
+user-592,group-5
+user-593,group-5
+user-594,group-5
+user-596,group-5
+user-597,group-5
+user-600,group-6
+user-602,group-6
+user-604,group-6
+user-612,group-6
+user-615,group-6
+user-621,group-6
+user-622,group-6
+user-623,group-6
+user-629,group-6
+user-638,group-6
+user-640,group-6
+user-652,group-6
+user-655,group-6
+user-656,group-6
+user-659,group-6
+user-661,group-6
+user-668,group-6
+user-669,group-6
+user-686,group-6
+user-690,group-6
+user-695,group-6
+user-698,group-6
+user-702,group-7
+user-703,group-7
+user-704,group-7
+user-706,group-7
+user-710,group-7
+user-716,group-7
+user-718,group-7
+user-725,group-7
+user-726,group-7
+user-728,group-7
+user-730,group-7
+user-731,group-7
+user-737,group-7
+user-738,group-7
+user-747,group-7
+user-748,group-7
+user-753,group-7
+user-755,group-7
+user-761,group-7
+user-766,group-7
+user-769,group-7
+user-773,group-7
+user-774,group-7
+user-775,group-7
+user-776,group-7
+user-777,group-7
+user-778,group-7
+user-782,group-7
+user-784,group-7
+user-803,group-8
+user-804,group-8
+user-805,group-8
+user-812,group-8
+user-818,group-8
+user-825,group-8
+user-826,group-8
+user-827,group-8
+user-829,group-8
+user-830,group-8
+user-831,group-8
+user-842,group-8
+user-844,group-8
+user-845,group-8
+user-849,group-8
+user-855,group-8
+user-863,group-8
+user-866,group-8
+user-871,group-8
+user-877,group-8
+user-880,group-8
+user-882,group-8
+user-884,group-8
+user-885,group-8
+user-897,group-8
+user-909,group-9
+user-916,group-9
+user-919,group-9
+user-921,group-9
+user-922,group-9
+user-923,group-9
+user-927,group-9
+user-928,group-9
+user-931,group-9
+user-932,group-9
+user-937,group-9
+user-939,group-9
+user-947,group-9
+user-949,group-9
+user-951,group-9
+user-954,group-9
+user-963,group-9
+user-965,group-9
+user-972,group-9
+user-974,group-9
+user-982,group-9
+user-984,group-9
+user-991,group-9
+user-992,group-9
+user-993,group-9
+user-1000,group-10
+user-1012,group-10
+user-1013,group-10
+user-1019,group-10
+user-1022,group-10
+user-1025,group-10
+user-1026,group-10
+user-1027,group-10
+user-1032,group-10
+user-1033,group-10
+user-1035,group-10
+user-1036,group-10
+user-1038,group-10
+user-1040,group-10
+user-1048,group-10
+user-1061,group-10
+user-1063,group-10
+user-1066,group-10
+user-1068,group-10
+user-1070,group-10
+user-1071,group-10
+user-1072,group-10
+user-1073,group-10
+user-1078,group-10
+user-1079,group-10
+user-1080,group-10
+user-1081,group-10
+user-1082,group-10
+user-1090,group-10
+user-1094,group-10
+user-1096,group-10
+user-1099,group-10
+user-1101,group-11
+user-1103,group-11
+user-1110,group-11
+user-1113,group-11
+user-1117,group-11
+user-1118,group-11
+user-1119,group-11
+user-1122,group-11
+user-1131,group-11
+user-1133,group-11
+user-1142,group-11
+user-1146,group-11
+user-1147,group-11
+user-1148,group-11
+user-1153,group-11
+user-1154,group-11
+user-1157,group-11
+user-1159,group-11
+user-1160,group-11
+user-1161,group-11
+user-1166,group-11
+user-1170,group-11
+user-1174,group-11
+user-1175,group-11
+user-1182,group-11
+user-1188,group-11
+user-1192,group-11
+user-1196,group-11
+user-1201,group-12
+user-1202,group-12
+user-1204,group-12
+user-1211,group-12
+user-1214,group-12
+user-1219,group-12
+user-1220,group-12
+user-1223,group-12
+user-1224,group-12
+user-1228,group-12
+user-1230,group-12
+user-1233,group-12
+user-1238,group-12
+user-1240,group-12
+user-1242,group-12
+user-1247,group-12
+user-1259,group-12
+user-1261,group-12
+user-1262,group-12
+user-1264,group-12
+user-1268,group-12
+user-1272,group-12
+user-1281,group-12
+user-1284,group-12
+user-1285,group-12
+user-1298,group-12
+user-1299,group-12
+user-1301,group-13
+user-1305,group-13
+user-1306,group-13
+user-1307,group-13
+user-1308,group-13
+user-1313,group-13
+user-1314,group-13
+user-1318,group-13
+user-1322,group-13
+user-1327,group-13
+user-1331,group-13
+user-1337,group-13
+user-1339,group-13
+user-1350,group-13
+user-1354,group-13
+user-1356,group-13
+user-1360,group-13
+user-1363,group-13
+user-1365,group-13
+user-1367,group-13
+user-1371,group-13
+user-1373,group-13
+user-1375,group-13
+user-1381,group-13
+user-1383,group-13
+user-1392,group-13
+user-1393,group-13
+user-1401,group-14
+user-1403,group-14
+user-1406,group-14
+user-1409,group-14
+user-1413,group-14
+user-1414,group-14
+user-1415,group-14
+user-1419,group-14
+user-1421,group-14
+user-1423,group-14
+user-1425,group-14
+user-1426,group-14
+user-1427,group-14
+user-1428,group-14
+user-1430,group-14
+user-1433,group-14
+user-1434,group-14
+user-1447,group-14
+user-1451,group-14
+user-1454,group-14
+user-1458,group-14
+user-1464,group-14
+user-1468,group-14
+user-1469,group-14
+user-1470,group-14
+user-1478,group-14
+user-1485,group-14
+user-1487,group-14
+user-1490,group-14
+user-1491,group-14
+user-1492,group-14
+user-1493,group-14
+user-1494,group-14
+user-1496,group-14
+user-1498,group-14
+user-1503,group-15
+user-1504,group-15
+user-1509,group-15
+user-1516,group-15
+user-1518,group-15
+user-1519,group-15
+user-1522,group-15
+user-1525,group-15
+user-1526,group-15
+user-1529,group-15
+user-1532,group-15
+user-1533,group-15
+user-1536,group-15
+user-1538,group-15
+user-1545,group-15
+user-1548,group-15
+user-1553,group-15
+user-1557,group-15
+user-1560,group-15
+user-1562,group-15
+user-1566,group-15
+user-1568,group-15
+user-1574,group-15
+user-1575,group-15
+user-1578,group-15
+user-1582,group-15
+user-1587,group-15
+user-1590,group-15
+user-1591,group-15
+user-1595,group-15
+user-1596,group-15
+user-1609,group-16
+user-1614,group-16
+user-1617,group-16
+user-1622,group-16
+user-1623,group-16
+user-1628,group-16
+user-1632,group-16
+user-1633,group-16
+user-1636,group-16
+user-1642,group-16
+user-1643,group-16
+user-1649,group-16
+user-1652,group-16
+user-1656,group-16
+user-1663,group-16
+user-1666,group-16
+user-1673,group-16
+user-1675,group-16
+user-1676,group-16
+user-1678,group-16
+user-1684,group-16
+user-1687,group-16
+user-1697,group-16
+user-1698,group-16
+user-1699,group-16
+user-1704,group-17
+user-1707,group-17
+user-1710,group-17
+user-1711,group-17
+user-1718,group-17
+user-1723,group-17
+user-1727,group-17
+user-1729,group-17
+user-1733,group-17
+user-1734,group-17
+user-1759,group-17
+user-1765,group-17
+user-1775,group-17
+user-1779,group-17
+user-1782,group-17
+user-1789,group-17
+user-1795,group-17
+user-1801,group-18
+user-1806,group-18
+user-1819,group-18
+user-1821,group-18
+user-1822,group-18
+user-1823,group-18
+user-1827,group-18
+user-1832,group-18
+user-1836,group-18
+user-1838,group-18
+user-1839,group-18
+user-1850,group-18
+user-1854,group-18
+user-1862,group-18
+user-1866,group-18
+user-1868,group-18
+user-1872,group-18
+user-1874,group-18
+user-1879,group-18
+user-1880,group-18
+user-1882,group-18
+user-1888,group-18
+user-1889,group-18
+user-1896,group-18
+user-1898,group-18
+user-1908,group-19
+user-1920,group-19
+user-1921,group-19
+user-1922,group-19
+user-1928,group-19
+user-1937,group-19
+user-1940,group-19
+user-1942,group-19
+user-1943,group-19
+user-1949,group-19
+user-1951,group-19
+user-1952,group-19
+user-1954,group-19
+user-1966,group-19
+user-1967,group-19
+user-1972,group-19
+user-1979,group-19
+user-1984,group-19
+user-1985,group-19
+user-1988,group-19
+user-1990,group-19
+user-2002,group-20
+user-2010,group-20
+user-2014,group-20
+user-2021,group-20
+user-2024,group-20
+user-2027,group-20
+user-2028,group-20
+user-2030,group-20
+user-2039,group-20
+user-2044,group-20
+user-2058,group-20
+user-2061,group-20
+user-2064,group-20
+user-2069,group-20
+user-2078,group-20
+user-2084,group-20
+user-2087,group-20
+user-2092,group-20
+user-2096,group-20
+user-2110,group-21
+user-2113,group-21
+user-2117,group-21
+user-2124,group-21
+user-2129,group-21
+user-2130,group-21
+user-2139,group-21
+user-2148,group-21
+user-2150,group-21
+user-2151,group-21
+user-2159,group-21
+user-2160,group-21
+user-2168,group-21
+user-2177,group-21
+user-2182,group-21
+user-2189,group-21
+user-2194,group-21
+user-2202,group-22
+user-2207,group-22
+user-2217,group-22
+user-2221,group-22
+user-2223,group-22
+user-2224,group-22
+user-2226,group-22
+user-2237,group-22
+user-2241,group-22
+user-2242,group-22
+user-2244,group-22
+user-2255,group-22
+user-2264,group-22
+user-2265,group-22
+user-2268,group-22
+user-2269,group-22
+user-2273,group-22
+user-2286,group-22
+user-2288,group-22
+user-2302,group-23
+user-2310,group-23
+user-2312,group-23
+user-2315,group-23
+user-2320,group-23
+user-2325,group-23
+user-2327,group-23
+user-2334,group-23
+user-2340,group-23
+user-2355,group-23
+user-2356,group-23
+user-2364,group-23
+user-2365,group-23
+user-2368,group-23
+user-2386,group-23
+user-2390,group-23
+user-2394,group-23
+user-2399,group-23
+user-2400,group-24
+user-2402,group-24
+user-2407,group-24
+user-2409,group-24
+user-2414,group-24
+user-2415,group-24
+user-2416,group-24
+user-2417,group-24
+user-2418,group-24
+user-2420,group-24
+user-2423,group-24
+user-2426,group-24
+user-2432,group-24
+user-2435,group-24
+user-2436,group-24
+user-2437,group-24
+user-2442,group-24
+user-2443,group-24
+user-2444,group-24
+user-2446,group-24
+user-2448,group-24
+user-2449,group-24
+user-2454,group-24
+user-2458,group-24
+user-2467,group-24
+user-2472,group-24
+user-2474,group-24
+user-2475,group-24
+user-2478,group-24
+user-2482,group-24
+user-2489,group-24
+user-2494,group-24
+user-2496,group-24
+user-2498,group-24
+user-2499,group-24
+user-2500,group-25
+user-2501,group-25
+user-2512,group-25
+user-2513,group-25
+user-2514,group-25
+user-2517,group-25
+user-2522,group-25
+user-2524,group-25
+user-2526,group-25
+user-2529,group-25
+user-2530,group-25
+user-2532,group-25
+user-2546,group-25
+user-2551,group-25
+user-2552,group-25
+user-2564,group-25
+user-2570,group-25
+user-2580,group-25
+user-2583,group-25
+user-2584,group-25
+user-2588,group-25
+user-2589,group-25
+user-2590,group-25
+user-2591,group-25
+user-2594,group-25
+user-2596,group-25
+user-2597,group-25
+user-2601,group-26
+user-2606,group-26
+user-2607,group-26
+user-2608,group-26
+user-2610,group-26
+user-2616,group-26
+user-2619,group-26
+user-2620,group-26
+user-2625,group-26
+user-2634,group-26
+user-2648,group-26
+user-2656,group-26
+user-2664,group-26
+user-2665,group-26
+user-2677,group-26
+user-2681,group-26
+user-2682,group-26
+user-2684,group-26
+user-2686,group-26
+user-2690,group-26
+user-2693,group-26
+user-2694,group-26
+user-2700,group-27
+user-2707,group-27
+user-2709,group-27
+user-2713,group-27
+user-2716,group-27
+user-2718,group-27
+user-2736,group-27
+user-2740,group-27
+user-2745,group-27
+user-2750,group-27
+user-2756,group-27
+user-2759,group-27
+user-2760,group-27
+user-2763,group-27
+user-2764,group-27
+user-2768,group-27
+user-2769,group-27
+user-2777,group-27
+user-2778,group-27
+user-2782,group-27
+user-2783,group-27
+user-2785,group-27
+user-2789,group-27
+user-2790,group-27
+user-2798,group-27
+user-2807,group-28
+user-2816,group-28
+user-2817,group-28
+user-2830,group-28
+user-2832,group-28
+user-2833,group-28
+user-2834,group-28
+user-2836,group-28
+user-2838,group-28
+user-2845,group-28
+user-2849,group-28
+user-2850,group-28
+user-2865,group-28
+user-2878,group-28
+user-2879,group-28
+user-2880,group-28
+user-2890,group-28
+user-2894,group-28
+user-2901,group-29
+user-2908,group-29
+user-2909,group-29
+user-2910,group-29
+user-2912,group-29
+user-2913,group-29
+user-2914,group-29
+user-2922,group-29
+user-2924,group-29
+user-2928,group-29
+user-2932,group-29
+user-2940,group-29
+user-2942,group-29
+user-2944,group-29
+user-2947,group-29
+user-2948,group-29
+user-2949,group-29
+user-2953,group-29
+user-2958,group-29
+user-2961,group-29
+user-2967,group-29
+user-2977,group-29
+user-2983,group-29
+user-2988,group-29
+user-3004,group-30
+user-3007,group-30
+user-3010,group-30
+user-3011,group-30
+user-3013,group-30
+user-3016,group-30
+user-3018,group-30
+user-3019,group-30
+user-3021,group-30
+user-3025,group-30
+user-3026,group-30
+user-3029,group-30
+user-3037,group-30
+user-3049,group-30
+user-3052,group-30
+user-3057,group-30
+user-3059,group-30
+user-3060,group-30
+user-3062,group-30
+user-3091,group-30
+user-3094,group-30
+user-3100,group-31
+user-3101,group-31
+user-3107,group-31
+user-3119,group-31
+user-3120,group-31
+user-3125,group-31
+user-3144,group-31
+user-3148,group-31
+user-3149,group-31
+user-3159,group-31
+user-3163,group-31
+user-3167,group-31
+user-3181,group-31
+user-3183,group-31
+user-3189,group-31
+user-3190,group-31
+user-3198,group-31
+user-3199,group-31
+user-3205,group-32
+user-3208,group-32
+user-3212,group-32
+user-3213,group-32
+user-3218,group-32
+user-3219,group-32
+user-3221,group-32
+user-3223,group-32
+user-3226,group-32
+user-3228,group-32
+user-3229,group-32
+user-3231,group-32
+user-3243,group-32
+user-3245,group-32
+user-3246,group-32
+user-3249,group-32
+user-3264,group-32
+user-3268,group-32
+user-3270,group-32
+user-3272,group-32
+user-3274,group-32
+user-3275,group-32
+user-3277,group-32
+user-3279,group-32
+user-3286,group-32
+user-3288,group-32
+user-3290,group-32
+user-3291,group-32
+user-3294,group-32
+user-3301,group-33
+user-3302,group-33
+user-3319,group-33
+user-3324,group-33
+user-3325,group-33
+user-3329,group-33
+user-3331,group-33
+user-3336,group-33
+user-3337,group-33
+user-3344,group-33
+user-3346,group-33
+user-3359,group-33
+user-3373,group-33
+user-3376,group-33
+user-3380,group-33
+user-3381,group-33
+user-3382,group-33
+user-3386,group-33
+user-3394,group-33
+user-3396,group-33
+user-3398,group-33
+user-3401,group-34
+user-3403,group-34
+user-3404,group-34
+user-3408,group-34
+user-3410,group-34
+user-3422,group-34
+user-3426,group-34
+user-3428,group-34
+user-3434,group-34
+user-3439,group-34
+user-3448,group-34
+user-3449,group-34
+user-3450,group-34
+user-3451,group-34
+user-3455,group-34
+user-3456,group-34
+user-3459,group-34
+user-3460,group-34
+user-3461,group-34
+user-3463,group-34
+user-3466,group-34
+user-3470,group-34
+user-3480,group-34
+user-3481,group-34
+user-3484,group-34
+user-3495,group-34
+user-3497,group-34
+user-3498,group-34
+user-3499,group-34
+user-3500,group-35
+user-3501,group-35
+user-3512,group-35
+user-3515,group-35
+user-3523,group-35
+user-3527,group-35
+user-3529,group-35
+user-3532,group-35
+user-3534,group-35
+user-3540,group-35
+user-3545,group-35
+user-3546,group-35
+user-3549,group-35
+user-3550,group-35
+user-3554,group-35
+user-3557,group-35
+user-3562,group-35
+user-3565,group-35
+user-3571,group-35
+user-3573,group-35
+user-3580,group-35
+user-3584,group-35
+user-3585,group-35
+user-3586,group-35
+user-3588,group-35
+user-3589,group-35
+user-3593,group-35
+user-3600,group-36
+user-3603,group-36
+user-3616,group-36
+user-3618,group-36
+user-3625,group-36
+user-3637,group-36
+user-3641,group-36
+user-3642,group-36
+user-3647,group-36
+user-3649,group-36
+user-3650,group-36
+user-3657,group-36
+user-3658,group-36
+user-3667,group-36
+user-3674,group-36
+user-3675,group-36
+user-3678,group-36
+user-3686,group-36
+user-3694,group-36
+user-3695,group-36
+user-3705,group-37
+user-3712,group-37
+user-3716,group-37
+user-3717,group-37
+user-3722,group-37
+user-3723,group-37
+user-3727,group-37
+user-3745,group-37
+user-3746,group-37
+user-3756,group-37
+user-3758,group-37
+user-3759,group-37
+user-3762,group-37
+user-3767,group-37
+user-3772,group-37
+user-3777,group-37
+user-3778,group-37
+user-3781,group-37
+user-3782,group-37
+user-3787,group-37
+user-3794,group-37
+user-3807,group-38
+user-3812,group-38
+user-3814,group-38
+user-3815,group-38
+user-3821,group-38
+user-3823,group-38
+user-3829,group-38
+user-3831,group-38
+user-3832,group-38
+user-3835,group-38
+user-3838,group-38
+user-3839,group-38
+user-3840,group-38
+user-3841,group-38
+user-3843,group-38
+user-3844,group-38
+user-3845,group-38
+user-3849,group-38
+user-3854,group-38
+user-3859,group-38
+user-3865,group-38
+user-3867,group-38
+user-3872,group-38
+user-3884,group-38
+user-3885,group-38
+user-3889,group-38
+user-3891,group-38
+user-3897,group-38
+user-3902,group-39
+user-3906,group-39
+user-3907,group-39
+user-3915,group-39
+user-3920,group-39
+user-3921,group-39
+user-3929,group-39
+user-3933,group-39
+user-3935,group-39
+user-3943,group-39
+user-3957,group-39
+user-3959,group-39
+user-3960,group-39
+user-3974,group-39
+user-3979,group-39
+user-3988,group-39
+user-4001,group-40
+user-4003,group-40
+user-4009,group-40
+user-4013,group-40
+user-4017,group-40
+user-4022,group-40
+user-4023,group-40
+user-4024,group-40
+user-4029,group-40
+user-4030,group-40
+user-4031,group-40
+user-4032,group-40
+user-4035,group-40
+user-4042,group-40
+user-4058,group-40
+user-4060,group-40
+user-4061,group-40
+user-4064,group-40
+user-4079,group-40
+user-4080,group-40
+user-4083,group-40
+user-4086,group-40
+user-4091,group-40
+user-4093,group-40
+user-4095,group-40
+user-4098,group-40
+user-4103,group-41
+user-4107,group-41
+user-4119,group-41
+user-4123,group-41
+user-4124,group-41
+user-4126,group-41
+user-4127,group-41
+user-4133,group-41
+user-4135,group-41
+user-4144,group-41
+user-4150,group-41
+user-4153,group-41
+user-4155,group-41
+user-4156,group-41
+user-4167,group-41
+user-4173,group-41
+user-4176,group-41
+user-4177,group-41
+user-4181,group-41
+user-4186,group-41
+user-4197,group-41
+user-4199,group-41
+user-4204,group-42
+user-4207,group-42
+user-4215,group-42
+user-4217,group-42
+user-4218,group-42
+user-4219,group-42
+user-4221,group-42
+user-4222,group-42
+user-4230,group-42
+user-4234,group-42
+user-4239,group-42
+user-4243,group-42
+user-4246,group-42
+user-4247,group-42
+user-4250,group-42
+user-4251,group-42
+user-4258,group-42
+user-4260,group-42
+user-4261,group-42
+user-4268,group-42
+user-4278,group-42
+user-4279,group-42
+user-4283,group-42
+user-4284,group-42
+user-4287,group-42
+user-4296,group-42
+user-4297,group-42
+user-4304,group-43
+user-4316,group-43
+user-4321,group-43
+user-4323,group-43
+user-4331,group-43
+user-4335,group-43
+user-4337,group-43
+user-4344,group-43
+user-4346,group-43
+user-4347,group-43
+user-4349,group-43
+user-4351,group-43
+user-4362,group-43
+user-4369,group-43
+user-4374,group-43
+user-4378,group-43
+user-4381,group-43
+user-4385,group-43
+user-4387,group-43
+user-4396,group-43
+user-4400,group-44
+user-4403,group-44
+user-4405,group-44
+user-4408,group-44
+user-4420,group-44
+user-4422,group-44
+user-4428,group-44
+user-4430,group-44
+user-4432,group-44
+user-4438,group-44
+user-4439,group-44
+user-4440,group-44
+user-4441,group-44
+user-4445,group-44
+user-4446,group-44
+user-4449,group-44
+user-4451,group-44
+user-4461,group-44
+user-4462,group-44
+user-4464,group-44
+user-4466,group-44
+user-4468,group-44
+user-4474,group-44
+user-4476,group-44
+user-4481,group-44
+user-4487,group-44
+user-4488,group-44
+user-4489,group-44
+user-4494,group-44
+user-4501,group-45
+user-4506,group-45
+user-4509,group-45
+user-4516,group-45
+user-4527,group-45
+user-4529,group-45
+user-4532,group-45
+user-4540,group-45
+user-4547,group-45
+user-4549,group-45
+user-4554,group-45
+user-4556,group-45
+user-4572,group-45
+user-4575,group-45
+user-4576,group-45
+user-4581,group-45
+user-4586,group-45
+user-4597,group-45
+user-4600,group-46
+user-4601,group-46
+user-4602,group-46
+user-4604,group-46
+user-4607,group-46
+user-4611,group-46
+user-4615,group-46
+user-4629,group-46
+user-4636,group-46
+user-4642,group-46
+user-4644,group-46
+user-4645,group-46
+user-4647,group-46
+user-4650,group-46
+user-4653,group-46
+user-4655,group-46
+user-4667,group-46
+user-4668,group-46
+user-4673,group-46
+user-4676,group-46
+user-4679,group-46
+user-4683,group-46
+user-4696,group-46
+user-4700,group-47
+user-4701,group-47
+user-4705,group-47
+user-4708,group-47
+user-4711,group-47
+user-4716,group-47
+user-4722,group-47
+user-4725,group-47
+user-4726,group-47
+user-4735,group-47
+user-4737,group-47
+user-4748,group-47
+user-4753,group-47
+user-4755,group-47
+user-4757,group-47
+user-4761,group-47
+user-4762,group-47
+user-4768,group-47
+user-4770,group-47
+user-4771,group-47
+user-4773,group-47
+user-4774,group-47
+user-4778,group-47
+user-4779,group-47
+user-4786,group-47
+user-4787,group-47
+user-4791,group-47
+user-4792,group-47
+user-4794,group-47
+user-4797,group-47
+user-4801,group-48
+user-4803,group-48
+user-4812,group-48
+user-4814,group-48
+user-4821,group-48
+user-4832,group-48
+user-4834,group-48
+user-4836,group-48
+user-4847,group-48
+user-4854,group-48
+user-4856,group-48
+user-4862,group-48
+user-4863,group-48
+user-4864,group-48
+user-4877,group-48
+user-4882,group-48
+user-4883,group-48
+user-4886,group-48
+user-4889,group-48
+user-4890,group-48
+user-4893,group-48
+user-4895,group-48
+user-4902,group-49
+user-4908,group-49
+user-4923,group-49
+user-4929,group-49
+user-4934,group-49
+user-4940,group-49
+user-4946,group-49
+user-4952,group-49
+user-4953,group-49
+user-4954,group-49
+user-4957,group-49
+user-4961,group-49
+user-4965,group-49
+user-4970,group-49
+user-4977,group-49
+user-4980,group-49
+user-5000,group-50
+user-5001,group-50
+user-5004,group-50
+user-5006,group-50
+user-5009,group-50
+user-5017,group-50
+user-5023,group-50
+user-5024,group-50
+user-5025,group-50
+user-5028,group-50
+user-5033,group-50
+user-5034,group-50
+user-5040,group-50
+user-5046,group-50
+user-5052,group-50
+user-5057,group-50
+user-5060,group-50
+user-5062,group-50
+user-5067,group-50
+user-5071,group-50
+user-5077,group-50
+user-5078,group-50
+user-5079,group-50
+user-5084,group-50
+user-5087,group-50
+user-5097,group-50
+user-5100,group-51
+user-5103,group-51
+user-5104,group-51
+user-5107,group-51
+user-5112,group-51
+user-5114,group-51
+user-5118,group-51
+user-5121,group-51
+user-5122,group-51
+user-5123,group-51
+user-5124,group-51
+user-5128,group-51
+user-5132,group-51
+user-5135,group-51
+user-5136,group-51
+user-5137,group-51
+user-5139,group-51
+user-5143,group-51
+user-5144,group-51
+user-5147,group-51
+user-5156,group-51
+user-5157,group-51
+user-5163,group-51
+user-5164,group-51
+user-5166,group-51
+user-5169,group-51
+user-5171,group-51
+user-5172,group-51
+user-5180,group-51
+user-5181,group-51
+user-5186,group-51
+user-5190,group-51
+user-5191,group-51
+user-5192,group-51
+user-5201,group-52
+user-5209,group-52
+user-5218,group-52
+user-5220,group-52
+user-5228,group-52
+user-5230,group-52
+user-5231,group-52
+user-5236,group-52
+user-5241,group-52
+user-5251,group-52
+user-5258,group-52
+user-5264,group-52
+user-5270,group-52
+user-5274,group-52
+user-5278,group-52
+user-5279,group-52
+user-5282,group-52
+user-5286,group-52
+user-5290,group-52
+user-5293,group-52
+user-5299,group-52
+user-5304,group-53
+user-5315,group-53
+user-5316,group-53
+user-5318,group-53
+user-5319,group-53
+user-5320,group-53
+user-5322,group-53
+user-5324,group-53
+user-5325,group-53
+user-5329,group-53
+user-5333,group-53
+user-5345,group-53
+user-5351,group-53
+user-5354,group-53
+user-5357,group-53
+user-5360,group-53
+user-5361,group-53
+user-5363,group-53
+user-5367,group-53
+user-5368,group-53
+user-5370,group-53
+user-5371,group-53
+user-5376,group-53
+user-5377,group-53
+user-5388,group-53
+user-5389,group-53
+user-5396,group-53
+user-5407,group-54
+user-5409,group-54
+user-5422,group-54
+user-5423,group-54
+user-5424,group-54
+user-5434,group-54
+user-5438,group-54
+user-5439,group-54
+user-5441,group-54
+user-5446,group-54
+user-5454,group-54
+user-5457,group-54
+user-5471,group-54
+user-5481,group-54
+user-5482,group-54
+user-5485,group-54
+user-5489,group-54
+user-5501,group-55
+user-5503,group-55
+user-5504,group-55
+user-5505,group-55
+user-5508,group-55
+user-5509,group-55
+user-5514,group-55
+user-5516,group-55
+user-5517,group-55
+user-5518,group-55
+user-5521,group-55
+user-5522,group-55
+user-5523,group-55
+user-5524,group-55
+user-5528,group-55
+user-5532,group-55
+user-5535,group-55
+user-5536,group-55
+user-5539,group-55
+user-5540,group-55
+user-5542,group-55
+user-5549,group-55
+user-5550,group-55
+user-5554,group-55
+user-5555,group-55
+user-5559,group-55
+user-5561,group-55
+user-5562,group-55
+user-5566,group-55
+user-5569,group-55
+user-5574,group-55
+user-5579,group-55
+user-5580,group-55
+user-5582,group-55
+user-5592,group-55
+user-5594,group-55
+user-5595,group-55
+user-5599,group-55
+user-5606,group-56
+user-5607,group-56
+user-5608,group-56
+user-5614,group-56
+user-5622,group-56
+user-5629,group-56
+user-5638,group-56
+user-5639,group-56
+user-5644,group-56
+user-5661,group-56
+user-5663,group-56
+user-5670,group-56
+user-5674,group-56
+user-5675,group-56
+user-5677,group-56
+user-5684,group-56
+user-5685,group-56
+user-5689,group-56
+user-5694,group-56
+user-5698,group-56
+user-5703,group-57
+user-5723,group-57
+user-5731,group-57
+user-5733,group-57
+user-5736,group-57
+user-5738,group-57
+user-5747,group-57
+user-5753,group-57
+user-5757,group-57
+user-5759,group-57
+user-5769,group-57
+user-5774,group-57
+user-5777,group-57
+user-5782,group-57
+user-5788,group-57
+user-5793,group-57
+user-5797,group-57
+user-5799,group-57
+user-5804,group-58
+user-5805,group-58
+user-5811,group-58
+user-5812,group-58
+user-5813,group-58
+user-5818,group-58
+user-5826,group-58
+user-5828,group-58
+user-5830,group-58
+user-5831,group-58
+user-5832,group-58
+user-5834,group-58
+user-5840,group-58
+user-5841,group-58
+user-5847,group-58
+user-5853,group-58
+user-5854,group-58
+user-5856,group-58
+user-5858,group-58
+user-5860,group-58
+user-5862,group-58
+user-5866,group-58
+user-5870,group-58
+user-5875,group-58
+user-5876,group-58
+user-5882,group-58
+user-5885,group-58
+user-5886,group-58
+user-5893,group-58
+user-5895,group-58
+user-5897,group-58
+user-5908,group-59
+user-5909,group-59
+user-5914,group-59
+user-5917,group-59
+user-5919,group-59
+user-5924,group-59
+user-5926,group-59
+user-5933,group-59
+user-5939,group-59
+user-5940,group-59
+user-5948,group-59
+user-5955,group-59
+user-5956,group-59
+user-5958,group-59
+user-5959,group-59
+user-5961,group-59
+user-5987,group-59
+user-5991,group-59
+user-5993,group-59
+user-5994,group-59
+user-6000,group-60
+user-6002,group-60
+user-6004,group-60
+user-6013,group-60
+user-6017,group-60
+user-6024,group-60
+user-6033,group-60
+user-6042,group-60
+user-6043,group-60
+user-6055,group-60
+user-6062,group-60
+user-6064,group-60
+user-6071,group-60
+user-6072,group-60
+user-6076,group-60
+user-6080,group-60
+user-6085,group-60
+user-6088,group-60
+user-6096,group-60
+user-6104,group-61
+user-6105,group-61
+user-6109,group-61
+user-6113,group-61
+user-6114,group-61
+user-6124,group-61
+user-6127,group-61
+user-6129,group-61
+user-6130,group-61
+user-6136,group-61
+user-6143,group-61
+user-6144,group-61
+user-6147,group-61
+user-6153,group-61
+user-6158,group-61
+user-6162,group-61
+user-6167,group-61
+user-6170,group-61
+user-6172,group-61
+user-6175,group-61
+user-6176,group-61
+user-6177,group-61
+user-6194,group-61
+user-6196,group-61
+user-6209,group-62
+user-6211,group-62
+user-6222,group-62
+user-6223,group-62
+user-6225,group-62
+user-6226,group-62
+user-6230,group-62
+user-6233,group-62
+user-6234,group-62
+user-6241,group-62
+user-6249,group-62
+user-6250,group-62
+user-6251,group-62
+user-6255,group-62
+user-6258,group-62
+user-6259,group-62
+user-6261,group-62
+user-6268,group-62
+user-6283,group-62
+user-6284,group-62
+user-6290,group-62
+user-6299,group-62
+user-6301,group-63
+user-6302,group-63
+user-6304,group-63
+user-6305,group-63
+user-6311,group-63
+user-6316,group-63
+user-6317,group-63
+user-6318,group-63
+user-6320,group-63
+user-6321,group-63
+user-6328,group-63
+user-6331,group-63
+user-6334,group-63
+user-6335,group-63
+user-6337,group-63
+user-6343,group-63
+user-6347,group-63
+user-6351,group-63
+user-6359,group-63
+user-6360,group-63
+user-6361,group-63
+user-6365,group-63
+user-6368,group-63
+user-6371,group-63
+user-6372,group-63
+user-6380,group-63
+user-6385,group-63
+user-6386,group-63
+user-6390,group-63
+user-6398,group-63
+user-6399,group-63
+user-6403,group-64
+user-6404,group-64
+user-6405,group-64
+user-6407,group-64
+user-6411,group-64
+user-6412,group-64
+user-6415,group-64
+user-6418,group-64
+user-6419,group-64
+user-6422,group-64
+user-6428,group-64
+user-6436,group-64
+user-6441,group-64
+user-6443,group-64
+user-6449,group-64
+user-6450,group-64
+user-6460,group-64
+user-6471,group-64
+user-6476,group-64
+user-6482,group-64
+user-6483,group-64
+user-6485,group-64
+user-6486,group-64
+user-6490,group-64
+user-6513,group-65
+user-6515,group-65
+user-6518,group-65
+user-6522,group-65
+user-6530,group-65
+user-6531,group-65
+user-6544,group-65
+user-6545,group-65
+user-6556,group-65
+user-6562,group-65
+user-6563,group-65
+user-6566,group-65
+user-6567,group-65
+user-6578,group-65
+user-6580,group-65
+user-6582,group-65
+user-6583,group-65
+user-6585,group-65
+user-6588,group-65
+user-6590,group-65
+user-6599,group-65
+user-6601,group-66
+user-6604,group-66
+user-6610,group-66
+user-6612,group-66
+user-6613,group-66
+user-6614,group-66
+user-6616,group-66
+user-6618,group-66
+user-6619,group-66
+user-6621,group-66
+user-6625,group-66
+user-6626,group-66
+user-6627,group-66
+user-6631,group-66
+user-6639,group-66
+user-6642,group-66
+user-6647,group-66
+user-6649,group-66
+user-6652,group-66
+user-6656,group-66
+user-6659,group-66
+user-6666,group-66
+user-6678,group-66
+user-6681,group-66
+user-6692,group-66
+user-6694,group-66
+user-6696,group-66
+user-6704,group-67
+user-6709,group-67
+user-6710,group-67
+user-6713,group-67
+user-6715,group-67
+user-6717,group-67
+user-6727,group-67
+user-6731,group-67
+user-6734,group-67
+user-6738,group-67
+user-6740,group-67
+user-6742,group-67
+user-6743,group-67
+user-6755,group-67
+user-6756,group-67
+user-6757,group-67
+user-6768,group-67
+user-6771,group-67
+user-6772,group-67
+user-6774,group-67
+user-6776,group-67
+user-6779,group-67
+user-6792,group-67
+user-6794,group-67
+user-6797,group-67
+user-6800,group-68
+user-6803,group-68
+user-6804,group-68
+user-6805,group-68
+user-6808,group-68
+user-6811,group-68
+user-6819,group-68
+user-6823,group-68
+user-6827,group-68
+user-6834,group-68
+user-6836,group-68
+user-6837,group-68
+user-6839,group-68
+user-6849,group-68
+user-6851,group-68
+user-6859,group-68
+user-6861,group-68
+user-6863,group-68
+user-6868,group-68
+user-6877,group-68
+user-6885,group-68
+user-6887,group-68
+user-6894,group-68
+user-6899,group-68
+user-6902,group-69
+user-6903,group-69
+user-6905,group-69
+user-6909,group-69
+user-6912,group-69
+user-6913,group-69
+user-6914,group-69
+user-6916,group-69
+user-6919,group-69
+user-6921,group-69
+user-6923,group-69
+user-6924,group-69
+user-6925,group-69
+user-6927,group-69
+user-6935,group-69
+user-6936,group-69
+user-6937,group-69
+user-6940,group-69
+user-6941,group-69
+user-6944,group-69
+user-6952,group-69
+user-6957,group-69
+user-6958,group-69
+user-6959,group-69
+user-6965,group-69
+user-6966,group-69
+user-6970,group-69
+user-6972,group-69
+user-6984,group-69
+user-6985,group-69
+user-6990,group-69
+user-6996,group-69
+user-6999,group-69
+user-7003,group-70
+user-7010,group-70
+user-7015,group-70
+user-7016,group-70
+user-7018,group-70
+user-7020,group-70
+user-7021,group-70
+user-7022,group-70
+user-7023,group-70
+user-7028,group-70
+user-7031,group-70
+user-7037,group-70
+user-7038,group-70
+user-7039,group-70
+user-7041,group-70
+user-7045,group-70
+user-7046,group-70
+user-7050,group-70
+user-7052,group-70
+user-7058,group-70
+user-7060,group-70
+user-7068,group-70
+user-7073,group-70
+user-7074,group-70
+user-7076,group-70
+user-7078,group-70
+user-7082,group-70
+user-7085,group-70
+user-7086,group-70
+user-7087,group-70
+user-7089,group-70
+user-7095,group-70
+user-7097,group-70
+user-7102,group-71
+user-7111,group-71
+user-7114,group-71
+user-7118,group-71
+user-7119,group-71
+user-7121,group-71
+user-7124,group-71
+user-7129,group-71
+user-7137,group-71
+user-7140,group-71
+user-7150,group-71
+user-7164,group-71
+user-7165,group-71
+user-7167,group-71
+user-7172,group-71
+user-7183,group-71
+user-7185,group-71
+user-7191,group-71
+user-7192,group-71
+user-7195,group-71
+user-7201,group-72
+user-7212,group-72
+user-7228,group-72
+user-7234,group-72
+user-7236,group-72
+user-7246,group-72
+user-7253,group-72
+user-7256,group-72
+user-7260,group-72
+user-7267,group-72
+user-7270,group-72
+user-7272,group-72
+user-7273,group-72
+user-7282,group-72
+user-7285,group-72
+user-7288,group-72
+user-7289,group-72
+user-7290,group-72
+user-7299,group-72
+user-7306,group-73
+user-7309,group-73
+user-7311,group-73
+user-7312,group-73
+user-7314,group-73
+user-7316,group-73
+user-7320,group-73
+user-7327,group-73
+user-7329,group-73
+user-7338,group-73
+user-7340,group-73
+user-7351,group-73
+user-7364,group-73
+user-7365,group-73
+user-7367,group-73
+user-7381,group-73
+user-7388,group-73
+user-7390,group-73
+user-7391,group-73
+user-7396,group-73
+user-7403,group-74
+user-7407,group-74
+user-7408,group-74
+user-7410,group-74
+user-7415,group-74
+user-7417,group-74
+user-7420,group-74
+user-7426,group-74
+user-7427,group-74
+user-7430,group-74
+user-7431,group-74
+user-7433,group-74
+user-7447,group-74
+user-7449,group-74
+user-7450,group-74
+user-7454,group-74
+user-7461,group-74
+user-7472,group-74
+user-7477,group-74
+user-7479,group-74
+user-7486,group-74
+user-7494,group-74
+user-7498,group-74
+user-7506,group-75
+user-7510,group-75
+user-7513,group-75
+user-7515,group-75
+user-7516,group-75
+user-7518,group-75
+user-7519,group-75
+user-7522,group-75
+user-7532,group-75
+user-7536,group-75
+user-7543,group-75
+user-7544,group-75
+user-7546,group-75
+user-7553,group-75
+user-7554,group-75
+user-7555,group-75
+user-7558,group-75
+user-7560,group-75
+user-7567,group-75
+user-7568,group-75
+user-7569,group-75
+user-7571,group-75
+user-7572,group-75
+user-7576,group-75
+user-7583,group-75
+user-7586,group-75
+user-7591,group-75
+user-7592,group-75
+user-7593,group-75
+user-7595,group-75
+user-7600,group-76
+user-7605,group-76
+user-7606,group-76
+user-7607,group-76
+user-7609,group-76
+user-7611,group-76
+user-7629,group-76
+user-7630,group-76
+user-7633,group-76
+user-7639,group-76
+user-7642,group-76
+user-7644,group-76
+user-7651,group-76
+user-7652,group-76
+user-7657,group-76
+user-7659,group-76
+user-7665,group-76
+user-7676,group-76
+user-7678,group-76
+user-7685,group-76
+user-7686,group-76
+user-7687,group-76
+user-7688,group-76
+user-7689,group-76
+user-7695,group-76
+user-7696,group-76
+user-7698,group-76
+user-7700,group-77
+user-7703,group-77
+user-7705,group-77
+user-7707,group-77
+user-7713,group-77
+user-7716,group-77
+user-7718,group-77
+user-7719,group-77
+user-7725,group-77
+user-7731,group-77
+user-7732,group-77
+user-7734,group-77
+user-7737,group-77
+user-7739,group-77
+user-7761,group-77
+user-7766,group-77
+user-7772,group-77
+user-7773,group-77
+user-7785,group-77
+user-7786,group-77
+user-7795,group-77
+user-7798,group-77
+user-7803,group-78
+user-7805,group-78
+user-7813,group-78
+user-7815,group-78
+user-7822,group-78
+user-7833,group-78
+user-7834,group-78
+user-7838,group-78
+user-7839,group-78
+user-7855,group-78
+user-7861,group-78
+user-7865,group-78
+user-7868,group-78
+user-7870,group-78
+user-7872,group-78
+user-7873,group-78
+user-7876,group-78
+user-7878,group-78
+user-7879,group-78
+user-7882,group-78
+user-7888,group-78
+user-7891,group-78
+user-7894,group-78
+user-7895,group-78
+user-7898,group-78
+user-7901,group-79
+user-7902,group-79
+user-7907,group-79
+user-7908,group-79
+user-7913,group-79
+user-7914,group-79
+user-7923,group-79
+user-7927,group-79
+user-7930,group-79
+user-7936,group-79
+user-7938,group-79
+user-7941,group-79
+user-7944,group-79
+user-7954,group-79
+user-7959,group-79
+user-7965,group-79
+user-7968,group-79
+user-7970,group-79
+user-7972,group-79
+user-7975,group-79
+user-7998,group-79
+user-8002,group-80
+user-8004,group-80
+user-8006,group-80
+user-8009,group-80
+user-8011,group-80
+user-8013,group-80
+user-8014,group-80
+user-8017,group-80
+user-8020,group-80
+user-8021,group-80
+user-8024,group-80
+user-8027,group-80
+user-8029,group-80
+user-8036,group-80
+user-8038,group-80
+user-8039,group-80
+user-8045,group-80
+user-8050,group-80
+user-8053,group-80
+user-8054,group-80
+user-8057,group-80
+user-8064,group-80
+user-8074,group-80
+user-8081,group-80
+user-8084,group-80
+user-8096,group-80
+user-8110,group-81
+user-8111,group-81
+user-8116,group-81
+user-8117,group-81
+user-8118,group-81
+user-8119,group-81
+user-8123,group-81
+user-8129,group-81
+user-8131,group-81
+user-8136,group-81
+user-8144,group-81
+user-8145,group-81
+user-8147,group-81
+user-8162,group-81
+user-8164,group-81
+user-8166,group-81
+user-8174,group-81
+user-8181,group-81
+user-8184,group-81
+user-8187,group-81
+user-8195,group-81
+user-8200,group-82
+user-8203,group-82
+user-8207,group-82
+user-8208,group-82
+user-8209,group-82
+user-8213,group-82
+user-8233,group-82
+user-8234,group-82
+user-8245,group-82
+user-8248,group-82
+user-8256,group-82
+user-8260,group-82
+user-8262,group-82
+user-8268,group-82
+user-8272,group-82
+user-8276,group-82
+user-8277,group-82
+user-8281,group-82
+user-8282,group-82
+user-8284,group-82
+user-8288,group-82
+user-8290,group-82
+user-8294,group-82
+user-8295,group-82
+user-8296,group-82
+user-8297,group-82
+user-8300,group-83
+user-8302,group-83
+user-8305,group-83
+user-8308,group-83
+user-8309,group-83
+user-8310,group-83
+user-8313,group-83
+user-8319,group-83
+user-8322,group-83
+user-8323,group-83
+user-8324,group-83
+user-8329,group-83
+user-8337,group-83
+user-8345,group-83
+user-8359,group-83
+user-8365,group-83
+user-8373,group-83
+user-8374,group-83
+user-8375,group-83
+user-8377,group-83
+user-8383,group-83
+user-8385,group-83
+user-8391,group-83
+user-8392,group-83
+user-8393,group-83
+user-8395,group-83
+user-8403,group-84
+user-8405,group-84
+user-8411,group-84
+user-8413,group-84
+user-8414,group-84
+user-8417,group-84
+user-8421,group-84
+user-8428,group-84
+user-8431,group-84
+user-8432,group-84
+user-8436,group-84
+user-8439,group-84
+user-8443,group-84
+user-8444,group-84
+user-8446,group-84
+user-8467,group-84
+user-8468,group-84
+user-8474,group-84
+user-8477,group-84
+user-8488,group-84
+user-8492,group-84
+user-8500,group-85
+user-8503,group-85
+user-8505,group-85
+user-8507,group-85
+user-8509,group-85
+user-8511,group-85
+user-8516,group-85
+user-8523,group-85
+user-8529,group-85
+user-8537,group-85
+user-8545,group-85
+user-8549,group-85
+user-8554,group-85
+user-8560,group-85
+user-8570,group-85
+user-8574,group-85
+user-8575,group-85
+user-8576,group-85
+user-8578,group-85
+user-8581,group-85
+user-8583,group-85
+user-8585,group-85
+user-8596,group-85
+user-8597,group-85
+user-8610,group-86
+user-8612,group-86
+user-8627,group-86
+user-8635,group-86
+user-8637,group-86
+user-8642,group-86
+user-8644,group-86
+user-8647,group-86
+user-8649,group-86
+user-8650,group-86
+user-8651,group-86
+user-8657,group-86
+user-8660,group-86
+user-8675,group-86
+user-8678,group-86
+user-8680,group-86
+user-8683,group-86
+user-8685,group-86
+user-8687,group-86
+user-8695,group-86
+user-8699,group-86
+user-8713,group-87
+user-8714,group-87
+user-8723,group-87
+user-8726,group-87
+user-8730,group-87
+user-8731,group-87
+user-8734,group-87
+user-8738,group-87
+user-8739,group-87
+user-8743,group-87
+user-8746,group-87
+user-8751,group-87
+user-8752,group-87
+user-8754,group-87
+user-8759,group-87
+user-8766,group-87
+user-8767,group-87
+user-8775,group-87
+user-8779,group-87
+user-8781,group-87
+user-8787,group-87
+user-8789,group-87
+user-8795,group-87
+user-8802,group-88
+user-8803,group-88
+user-8805,group-88
+user-8811,group-88
+user-8814,group-88
+user-8815,group-88
+user-8816,group-88
+user-8819,group-88
+user-8822,group-88
+user-8824,group-88
+user-8831,group-88
+user-8840,group-88
+user-8848,group-88
+user-8855,group-88
+user-8858,group-88
+user-8860,group-88
+user-8868,group-88
+user-8882,group-88
+user-8886,group-88
+user-8887,group-88
+user-8889,group-88
+user-8890,group-88
+user-8893,group-88
+user-8898,group-88
+user-8903,group-89
+user-8906,group-89
+user-8913,group-89
+user-8914,group-89
+user-8916,group-89
+user-8922,group-89
+user-8932,group-89
+user-8936,group-89
+user-8938,group-89
+user-8939,group-89
+user-8940,group-89
+user-8941,group-89
+user-8942,group-89
+user-8947,group-89
+user-8950,group-89
+user-8956,group-89
+user-8957,group-89
+user-8959,group-89
+user-8960,group-89
+user-8963,group-89
+user-8967,group-89
+user-8970,group-89
+user-8971,group-89
+user-8977,group-89
+user-8980,group-89
+user-8981,group-89
+user-8982,group-89
+user-8985,group-89
+user-8987,group-89
+user-8988,group-89
+user-8994,group-89
+user-9004,group-90
+user-9008,group-90
+user-9009,group-90
+user-9014,group-90
+user-9016,group-90
+user-9031,group-90
+user-9032,group-90
+user-9038,group-90
+user-9045,group-90
+user-9047,group-90
+user-9051,group-90
+user-9058,group-90
+user-9064,group-90
+user-9065,group-90
+user-9069,group-90
+user-9070,group-90
+user-9073,group-90
+user-9075,group-90
+user-9077,group-90
+user-9078,group-90
+user-9082,group-90
+user-9086,group-90
+user-9090,group-90
+user-9094,group-90
+user-9098,group-90
+user-9102,group-91
+user-9108,group-91
+user-9119,group-91
+user-9121,group-91
+user-9126,group-91
+user-9131,group-91
+user-9135,group-91
+user-9137,group-91
+user-9144,group-91
+user-9150,group-91
+user-9159,group-91
+user-9163,group-91
+user-9166,group-91
+user-9168,group-91
+user-9169,group-91
+user-9170,group-91
+user-9173,group-91
+user-9175,group-91
+user-9176,group-91
+user-9177,group-91
+user-9182,group-91
+user-9187,group-91
+user-9188,group-91
+user-9193,group-91
+user-9194,group-91
+user-9197,group-91
+user-9206,group-92
+user-9208,group-92
+user-9215,group-92
+user-9223,group-92
+user-9224,group-92
+user-9225,group-92
+user-9227,group-92
+user-9228,group-92
+user-9230,group-92
+user-9232,group-92
+user-9234,group-92
+user-9238,group-92
+user-9239,group-92
+user-9245,group-92
+user-9251,group-92
+user-9253,group-92
+user-9255,group-92
+user-9262,group-92
+user-9263,group-92
+user-9266,group-92
+user-9267,group-92
+user-9274,group-92
+user-9278,group-92
+user-9284,group-92
+user-9288,group-92
+user-9291,group-92
+user-9292,group-92
+user-9293,group-92
+user-9297,group-92
+user-9298,group-92
+user-9303,group-93
+user-9308,group-93
+user-9309,group-93
+user-9310,group-93
+user-9311,group-93
+user-9315,group-93
+user-9319,group-93
+user-9321,group-93
+user-9324,group-93
+user-9327,group-93
+user-9329,group-93
+user-9330,group-93
+user-9332,group-93
+user-9333,group-93
+user-9334,group-93
+user-9337,group-93
+user-9339,group-93
+user-9342,group-93
+user-9347,group-93
+user-9354,group-93
+user-9358,group-93
+user-9365,group-93
+user-9369,group-93
+user-9374,group-93
+user-9375,group-93
+user-9378,group-93
+user-9379,group-93
+user-9382,group-93
+user-9383,group-93
+user-9386,group-93
+user-9387,group-93
+user-9388,group-93
+user-9397,group-93
+user-9400,group-94
+user-9401,group-94
+user-9403,group-94
+user-9404,group-94
+user-9407,group-94
+user-9409,group-94
+user-9412,group-94
+user-9413,group-94
+user-9425,group-94
+user-9432,group-94
+user-9434,group-94
+user-9435,group-94
+user-9447,group-94
+user-9456,group-94
+user-9463,group-94
+user-9468,group-94
+user-9474,group-94
+user-9477,group-94
+user-9479,group-94
+user-9489,group-94
+user-9490,group-94
+user-9493,group-94
+user-9500,group-95
+user-9508,group-95
+user-9516,group-95
+user-9518,group-95
+user-9520,group-95
+user-9523,group-95
+user-9526,group-95
+user-9531,group-95
+user-9532,group-95
+user-9535,group-95
+user-9541,group-95
+user-9542,group-95
+user-9543,group-95
+user-9544,group-95
+user-9549,group-95
+user-9555,group-95
+user-9564,group-95
+user-9572,group-95
+user-9573,group-95
+user-9596,group-95
+user-9598,group-95
+user-9603,group-96
+user-9607,group-96
+user-9608,group-96
+user-9612,group-96
+user-9615,group-96
+user-9617,group-96
+user-9620,group-96
+user-9626,group-96
+user-9627,group-96
+user-9629,group-96
+user-9632,group-96
+user-9634,group-96
+user-9636,group-96
+user-9637,group-96
+user-9639,group-96
+user-9645,group-96
+user-9649,group-96
+user-9650,group-96
+user-9652,group-96
+user-9654,group-96
+user-9656,group-96
+user-9657,group-96
+user-9662,group-96
+user-9664,group-96
+user-9671,group-96
+user-9672,group-96
+user-9682,group-96
+user-9698,group-96
+user-9701,group-97
+user-9703,group-97
+user-9710,group-97
+user-9717,group-97
+user-9723,group-97
+user-9728,group-97
+user-9734,group-97
+user-9736,group-97
+user-9737,group-97
+user-9740,group-97
+user-9751,group-97
+user-9752,group-97
+user-9754,group-97
+user-9758,group-97
+user-9762,group-97
+user-9765,group-97
+user-9767,group-97
+user-9775,group-97
+user-9780,group-97
+user-9784,group-97
+user-9787,group-97
+user-9788,group-97
+user-9789,group-97
+user-9790,group-97
+user-9805,group-98
+user-9806,group-98
+user-9815,group-98
+user-9816,group-98
+user-9821,group-98
+user-9822,group-98
+user-9827,group-98
+user-9829,group-98
+user-9830,group-98
+user-9831,group-98
+user-9838,group-98
+user-9839,group-98
+user-9841,group-98
+user-9845,group-98
+user-9857,group-98
+user-9858,group-98
+user-9859,group-98
+user-9860,group-98
+user-9862,group-98
+user-9863,group-98
+user-9867,group-98
+user-9873,group-98
+user-9878,group-98
+user-9880,group-98
+user-9882,group-98
+user-9885,group-98
+user-9896,group-98
+user-9897,group-98
+user-9912,group-99
+user-9917,group-99
+user-9922,group-99
+user-9926,group-99
+user-9929,group-99
+user-9933,group-99
+user-9937,group-99
+user-9939,group-99
+user-9942,group-99
+user-9945,group-99
+user-9947,group-99
+user-9951,group-99
+user-9952,group-99
+user-9953,group-99
+user-9957,group-99
+user-9959,group-99
+user-9960,group-99
+user-9963,group-99
+user-9966,group-99
+user-9971,group-99
+user-9974,group-99
+user-9975,group-99
+user-9981,group-99
+user-9984,group-99
+user-9987,group-99
+user-9991,group-99
+user-9992,group-99
+user-9996,group-99
+user-9997,group-99
+user-9998,group-99
\ No newline at end of file
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p4.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p4.csv
new file mode 100644
index 0000000000..6f4f597ef0
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p4.csv
@@ -0,0 +1,5 @@
+userUUID,groupUUID
+user-1,group-2
+user-2,group-2
+user-7,group-0
+user-15,group-2
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p5.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p5.csv
new file mode 100644
index 0000000000..e135aeea68
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p5.csv
@@ -0,0 +1,5 @@
+userUUID,groupUUID
+user-6,group-3
+user-8,group-1
+user-14,group-1
+user-24,group-1
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p6.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p6.csv
new file mode 100644
index 0000000000..889b1be1d3
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p6.csv
@@ -0,0 +1,5 @@
+userUUID,groupUUID
+user-5,group-1
+user-10,group-1
+user-19,group-1
+user-25,group-1
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p7.csv
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p7.csv
new file mode 100644
index 0000000000..47afb26791
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/rawdata/p7.csv
@@ -0,0 +1,8 @@
+userUUID,groupUUID
+user-0,group-1
+user-3,group-1
+user-4,group-2
+user-9,group-2
+user-13,group-2
+user-20,group-1
+user-22,group-1
diff --git
a/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/userGroupsDim_offline_table_config.json
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/userGroupsDim_offline_table_config.json
new file mode 100644
index 0000000000..004f1cfe05
--- /dev/null
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/userGroupsDim_offline_table_config.json
@@ -0,0 +1,18 @@
+{
+ "tableName": "userGroupsDim",
+ "tableType": "OFFLINE",
+ "isDimTable": true,
+ "segmentsConfig": {
+ "replication": "2"
+ },
+ "tenants": {
+ "broker": "DefaultTenant",
+ "server": "DefaultTenant"
+ },
+ "tableIndexConfig": {
+ },
+ "metadata": {
+ "customConfigs": {
+ }
+ }
+}
diff --git
a/pinot-tools/src/main/resources/examples/batch/colocated/userGroups/userGroups_schema.json
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/userGroupsDim_schema.json
similarity index 68%
copy from
pinot-tools/src/main/resources/examples/batch/colocated/userGroups/userGroups_schema.json
copy to
pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/userGroupsDim_schema.json
index c4772df7a2..98aa39ac06 100644
---
a/pinot-tools/src/main/resources/examples/batch/colocated/userGroups/userGroups_schema.json
+++
b/pinot-tools/src/main/resources/examples/batch/lookup/userGroupsDim/userGroupsDim_schema.json
@@ -1,6 +1,5 @@
{
- "metricFieldSpecs": [
- ],
+ "schemaName": "userGroupsDim",
"dimensionFieldSpecs": [
{
"dataType": "STRING",
@@ -11,5 +10,7 @@
"name": "userUUID"
}
],
- "schemaName": "userGroups"
+ "primaryKeyColumns": [
+ "userUUID"
+ ]
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]