Copilot commented on code in PR #13236:
URL: https://github.com/apache/gluten/pull/13236#discussion_r4326562174


##########
cpp/velox/substrait/SubstraitToVeloxPlan.cc:
##########
@@ -1511,6 +1521,58 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::constructCudfValueStreamNode(
 }
 #endif
 
+#ifdef ENABLE_KAFKA
+core::PlanNodePtr 
SubstraitToVeloxPlanConverter::constructKafkaStreamNode(const 
::substrait::ReadRel& readRel) {
+  VELOX_USER_CHECK(!readRel.has_filter(), "Filter pushdown is not supported 
for Kafka reads.");
+  // Like other table scans, a Kafka scan consumes the next split info. The 
JVM sends it as a serialized
+  // ReadRel.ExtensionTable containing a gluten.StreamKafka rather than 
LocalFiles.
+  auto splitInfo = std::make_shared<KafkaSplitInfo>();
+  splitInfo->leafType = SplitInfo::LeafType::TABLE_SCAN;
+  if (!validationMode_) {
+    VELOX_CHECK_LT(splitInfoIdx_, rawSplitInfos_.size(), "Plan must have 
readRel and related split info.");
+    ::substrait::ReadRel_ExtensionTable extensionTable;
+    VELOX_CHECK(
+        extensionTable.ParseFromString(rawSplitInfos_[splitInfoIdx_++]),
+        "Failed to parse Kafka split info as ReadRel.ExtensionTable");
+    VELOX_CHECK(
+        extensionTable.detail().UnpackTo(&splitInfo->streamKafka),
+        "Kafka split info must contain a gluten.StreamKafka payload");
+  }
+
+  std::vector<std::string> colNameList;
+  std::vector<TypePtr> veloxTypeList;
+  bool asLowerCase = !veloxCfg_->get<bool>(kCaseSensitive, false);
+  if (readRel.has_base_schema()) {
+    const auto& baseSchema = readRel.base_schema();
+    colNameList.reserve(baseSchema.names().size());
+    for (const auto& name : baseSchema.names()) {
+      std::string fieldName = name;
+      if (asLowerCase) {
+        folly::toLowerAscii(fieldName);
+      }
+      colNameList.emplace_back(fieldName);
+    }
+    veloxTypeList = SubstraitParser::parseNamedStruct(baseSchema, asLowerCase);
+  }

Review Comment:
   `colNameList.size()` is used to index `veloxTypeList[idx]` without verifying 
that `parseNamedStruct()` returned the same number of types as there are names. 
If the Substrait `base_schema` is malformed (or evolves), this can cause 
out-of-bounds access. Add an explicit size check (and ideally throw a 
user-facing error) before the loop; also consider using `size_t` for the loop 
index to avoid signed/unsigned issues.



##########
cpp/velox/compute/VeloxPlanConverter.cc:
##########
@@ -270,9 +273,13 @@ void parseLocalFileNodes(
 
 std::shared_ptr<const facebook::velox::core::PlanNode> 
VeloxPlanConverter::toVeloxPlan(
     const ::substrait::Plan& substraitPlan,
-    std::vector<::substrait::ReadRel_LocalFiles> localFiles) {
+    std::vector<::substrait::ReadRel_LocalFiles> localFiles,
+    std::vector<std::string> rawSplitInfos) {
   if (!validationMode_) {
-    parseLocalFileNodes(&substraitVeloxPlanConverter_, veloxCfg_, localFiles);
+    if (rawSplitInfos.empty()) {
+      parseLocalFileNodes(&substraitVeloxPlanConverter_, veloxCfg_, 
localFiles);
+    }
+    substraitVeloxPlanConverter_.setRawSplitInfos(std::move(rawSplitInfos));
   }

Review Comment:
   `toVeloxPlan()` now accepts both `localFiles` and `rawSplitInfos`, but if 
`rawSplitInfos` is non-empty, `localFiles` is silently ignored. This can lead 
to accidental misuse and hard-to-debug behavior if both are provided. Consider 
enforcing mutual exclusivity (e.g., throw if both are non-empty) or refactoring 
to a clearer API (separate overloads / `std::variant` / `std::optional`) so 
callers cannot accidentally provide conflicting split sources.



##########
cpp/velox/compute/WholeStageResultIterator.cc:
##########
@@ -144,6 +147,14 @@ WholeStageResultIterator::WholeStageResultIterator(
 
   for (size_t scanInfoIdx = 0; scanInfoIdx < scanInfos.size(); ++scanInfoIdx) {
     const auto& scanInfo = scanInfos[scanInfoIdx];
+#ifdef ENABLE_KAFKA
+    if (auto kafkaSplitInfo = 
std::dynamic_pointer_cast<KafkaSplitInfo>(scanInfo)) {
+      std::vector<velox::exec::Split> kafkaSplits;
+      
kafkaSplits.emplace_back(kafkaSplitInfo->toConnectorSplit(connectorIds_.kafka));
+      splits_.emplace_back(std::move(kafkaSplits));
+      continue;
+    }

Review Comment:
   This assumes `connectorIds_.kafka` is non-empty whenever a `KafkaSplitInfo` 
is present. If it’s empty, the created split will carry an empty connector ID 
and will likely fail at execution time (or route to the wrong config entry). 
Add an explicit check here (user-facing) that `connectorIds_.kafka` is set when 
encountering `KafkaSplitInfo`, and fail fast with a clear message if it isn’t.



##########
cpp/velox/substrait/SubstraitToVeloxPlan.cc:
##########
@@ -1511,6 +1521,58 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::constructCudfValueStreamNode(
 }
 #endif
 
+#ifdef ENABLE_KAFKA
+core::PlanNodePtr 
SubstraitToVeloxPlanConverter::constructKafkaStreamNode(const 
::substrait::ReadRel& readRel) {
+  VELOX_USER_CHECK(!readRel.has_filter(), "Filter pushdown is not supported 
for Kafka reads.");
+  // Like other table scans, a Kafka scan consumes the next split info. The 
JVM sends it as a serialized
+  // ReadRel.ExtensionTable containing a gluten.StreamKafka rather than 
LocalFiles.
+  auto splitInfo = std::make_shared<KafkaSplitInfo>();
+  splitInfo->leafType = SplitInfo::LeafType::TABLE_SCAN;
+  if (!validationMode_) {
+    VELOX_CHECK_LT(splitInfoIdx_, rawSplitInfos_.size(), "Plan must have 
readRel and related split info.");
+    ::substrait::ReadRel_ExtensionTable extensionTable;
+    VELOX_CHECK(
+        extensionTable.ParseFromString(rawSplitInfos_[splitInfoIdx_++]),
+        "Failed to parse Kafka split info as ReadRel.ExtensionTable");
+    VELOX_CHECK(
+        extensionTable.detail().UnpackTo(&splitInfo->streamKafka),
+        "Kafka split info must contain a gluten.StreamKafka payload");
+  }
+
+  std::vector<std::string> colNameList;
+  std::vector<TypePtr> veloxTypeList;
+  bool asLowerCase = !veloxCfg_->get<bool>(kCaseSensitive, false);
+  if (readRel.has_base_schema()) {
+    const auto& baseSchema = readRel.base_schema();
+    colNameList.reserve(baseSchema.names().size());
+    for (const auto& name : baseSchema.names()) {
+      std::string fieldName = name;
+      if (asLowerCase) {
+        folly::toLowerAscii(fieldName);
+      }
+      colNameList.emplace_back(fieldName);
+    }
+    veloxTypeList = SubstraitParser::parseNamedStruct(baseSchema, asLowerCase);
+  }
+
+  std::vector<std::string> outNames;
+  outNames.reserve(colNameList.size());
+  connector::ColumnHandleMap assignments;
+  for (int idx = 0; idx < colNameList.size(); idx++) {
+    auto outName = SubstraitParser::makeNodeName(planNodeId_, idx);
+    assignments[outName] = 
std::make_shared<KafkaColumnHandle>(colNameList[idx], veloxTypeList[idx]);
+    outNames.emplace_back(outName);
+  }

Review Comment:
   `colNameList.size()` is used to index `veloxTypeList[idx]` without verifying 
that `parseNamedStruct()` returned the same number of types as there are names. 
If the Substrait `base_schema` is malformed (or evolves), this can cause 
out-of-bounds access. Add an explicit size check (and ideally throw a 
user-facing error) before the loop; also consider using `size_t` for the loop 
index to avoid signed/unsigned issues.



##########
cpp/velox/substrait/SubstraitToVeloxPlan.cc:
##########
@@ -1511,6 +1521,58 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::constructCudfValueStreamNode(
 }
 #endif
 
+#ifdef ENABLE_KAFKA
+core::PlanNodePtr 
SubstraitToVeloxPlanConverter::constructKafkaStreamNode(const 
::substrait::ReadRel& readRel) {
+  VELOX_USER_CHECK(!readRel.has_filter(), "Filter pushdown is not supported 
for Kafka reads.");
+  // Like other table scans, a Kafka scan consumes the next split info. The 
JVM sends it as a serialized
+  // ReadRel.ExtensionTable containing a gluten.StreamKafka rather than 
LocalFiles.
+  auto splitInfo = std::make_shared<KafkaSplitInfo>();
+  splitInfo->leafType = SplitInfo::LeafType::TABLE_SCAN;
+  if (!validationMode_) {
+    VELOX_CHECK_LT(splitInfoIdx_, rawSplitInfos_.size(), "Plan must have 
readRel and related split info.");
+    ::substrait::ReadRel_ExtensionTable extensionTable;
+    VELOX_CHECK(
+        extensionTable.ParseFromString(rawSplitInfos_[splitInfoIdx_++]),
+        "Failed to parse Kafka split info as ReadRel.ExtensionTable");
+    VELOX_CHECK(
+        extensionTable.detail().UnpackTo(&splitInfo->streamKafka),
+        "Kafka split info must contain a gluten.StreamKafka payload");
+  }

Review Comment:
   These failures are caused by invalid/missing split payloads coming from the 
caller, but they use `VELOX_CHECK*` (internal check) rather than user-facing 
checks. Using `VELOX_USER_CHECK*`/`VELOX_USER_FAIL` here would produce a 
clearer, non-internal error classification for bad inputs and avoid treating 
malformed split payloads like invariant violations.



##########
cpp/velox/compute/WholeStageResultIterator.cc:
##########
@@ -268,6 +279,9 @@ std::shared_ptr<velox::core::QueryCtx> 
WholeStageResultIterator::createNewVeloxQ
   connectorConfigs[connectorIds_.iceberg] = hiveSessionConfig;
   connectorConfigs[connectorIds_.delta] = hiveSessionConfig;
   connectorConfigs[connectorIds_.iterator] = hiveSessionConfig;
+#ifdef ENABLE_KAFKA
+  connectorConfigs[connectorIds_.kafka] = hiveSessionConfig;
+#endif

Review Comment:
   If `connectorIds_.kafka` is empty, this inserts a connector config entry 
under an empty string key, which can cause confusing behavior (and may 
override/overlap with other config resolution). Guard this with `if 
(!connectorIds_.kafka.empty())` (or assert when Kafka is actually used) so the 
connector config map remains well-defined.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to