This is an automated email from the ASF dual-hosted git repository.

capistrant pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new cdb50b65f06 fix: bugfix in MSQ code path for extern ingest (#19656)
cdb50b65f06 is described below

commit cdb50b65f06dc3af4b41226e4afc0ba461899309
Author: Lucas Capistrant <[email protected]>
AuthorDate: Mon Jul 6 17:52:57 2026 -0500

    fix: bugfix in MSQ code path for extern ingest (#19656)
    
    * bugfix in MSQ code path for extern ingest
    
    * remove empty line
    
    * spelling fix
---
 .../msq/EmbeddedMSQParquetIngestionTest.java       | 162 +++++++++++++++++++++
 .../input/external/ExternalInputSliceReader.java   |  11 ++
 2 files changed, 173 insertions(+)

diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedMSQParquetIngestionTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedMSQParquetIngestionTest.java
new file mode 100644
index 00000000000..79e2fbf0acd
--- /dev/null
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedMSQParquetIngestionTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.druid.testing.embedded.msq;
+
+import com.sun.net.httpserver.HttpServer;
+import org.apache.druid.data.input.parquet.ParquetExtensionsModule;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.query.http.SqlTaskStatus;
+import org.apache.druid.testing.embedded.EmbeddedBroker;
+import org.apache.druid.testing.embedded.EmbeddedCoordinator;
+import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
+import org.apache.druid.testing.embedded.EmbeddedHistorical;
+import org.apache.druid.testing.embedded.EmbeddedIndexer;
+import org.apache.druid.testing.embedded.EmbeddedOverlord;
+import org.apache.druid.testing.embedded.indexing.Resources;
+import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * Ingests a Parquet file served over HTTP through an MSQ task, confirming end 
to end success.
+ *
+ * <p>Reading from a remote source such as HTTP or S3 exercises this 
fetch-to-disk path; a {@code local} input source
+ * does not, because its file is already on disk and is read in place.
+ */
+public class EmbeddedMSQParquetIngestionTest extends EmbeddedClusterTestBase
+{
+  private static final String PARQUET_RESOURCE = 
"data/parquet/wikipedia_index_data1.parquet";
+  private static final String PARQUET_PATH = "/wikipedia_index_data1.parquet";
+
+  private final EmbeddedOverlord overlord = new EmbeddedOverlord();
+  private final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
+  private final EmbeddedIndexer indexer = new EmbeddedIndexer()
+      .setServerMemory(300_000_000L)
+      .addProperty("druid.worker.capacity", "2");
+  private final EmbeddedBroker broker = new 
EmbeddedBroker().setServerMemory(200_000_000);
+
+  private EmbeddedMSQApis msqApis;
+  private HttpServer parquetServer;
+  private String parquetUri;
+
+  @Override
+  protected EmbeddedDruidCluster createCluster()
+  {
+    return EmbeddedDruidCluster
+        .withEmbeddedDerbyAndZookeeper()
+        .useLatchableEmitter()
+        .addExtension(ParquetExtensionsModule.class)
+        .addServer(overlord)
+        .addServer(coordinator)
+        .addServer(indexer)
+        .addServer(broker)
+        .addServer(new EmbeddedHistorical());
+  }
+
+  @BeforeAll
+  public void initTestClient() throws IOException
+  {
+    msqApis = new EmbeddedMSQApis(cluster, overlord);
+
+    final Path parquet = 
Resources.getFileForResource(PARQUET_RESOURCE).toPath();
+    final byte[] parquetBytes = Files.readAllBytes(parquet);
+
+    final int port;
+    try (ServerSocket socket = new ServerSocket(0)) {
+      port = socket.getLocalPort();
+    }
+    parquetServer = HttpServer.create(new InetSocketAddress("localhost", 
port), 0);
+    parquetServer.createContext(
+        PARQUET_PATH,
+        httpExchange -> {
+          httpExchange.sendResponseHeaders(200, parquetBytes.length);
+          try (OutputStream os = httpExchange.getResponseBody()) {
+            os.write(parquetBytes);
+          }
+        }
+    );
+    parquetServer.start();
+    parquetUri = StringUtils.format(
+        "http://%s:%d%s";,
+        parquetServer.getAddress().getHostName(),
+        parquetServer.getAddress().getPort(),
+        PARQUET_PATH
+    );
+  }
+
+  @AfterAll
+  public void tearDownServer()
+  {
+    if (parquetServer != null) {
+      parquetServer.stop(0);
+    }
+  }
+
+  @Test
+  public void testReplaceFromExternalParquet()
+  {
+    final String sql = StringUtils.format(
+        "REPLACE INTO %s OVERWRITE ALL\n"
+        + "SELECT\n"
+        + "  TIME_PARSE(\"timestamp\") AS __time,\n"
+        + "  \"page\",\n"
+        + "  \"added\",\n"
+        + "  \"delta\",\n"
+        + "  \"deleted\",\n"
+        + "  \"namespace\"\n"
+        + "FROM TABLE(\n"
+        + "  EXTERN(\n"
+        + "    '{\"type\":\"http\",\"uris\":[\"%s\"]}',\n"
+        + "    '{\"type\":\"parquet\"}'\n"
+        + "  )\n"
+        + ") EXTEND (\n"
+        + "  \"timestamp\" VARCHAR,\n"
+        + "  \"page\" VARCHAR,\n"
+        + "  \"added\" BIGINT,\n"
+        + "  \"delta\" BIGINT,\n"
+        + "  \"deleted\" BIGINT,\n"
+        + "  \"namespace\" VARCHAR\n"
+        + ")\n"
+        + "PARTITIONED BY DAY",
+        dataSource,
+        parquetUri
+    );
+
+    final SqlTaskStatus taskStatus = msqApis.submitTaskSql(sql);
+    cluster.callApi().waitForTaskToSucceed(taskStatus.getTaskId(), 
overlord.latchableEmitter());
+    cluster.callApi().waitForAllSegmentsToBeAvailable(dataSource, coordinator, 
broker);
+
+    cluster.callApi().verifySqlQuery(
+        "SELECT __time, page, added, delta, deleted, namespace FROM %s ORDER 
BY __time",
+        dataSource,
+        "2013-08-31T01:02:33.000Z,Gypsy Danger,57,-143,200,article\n"
+        + "2013-08-31T03:32:45.000Z,Striker Eureka,459,330,129,wikipedia\n"
+        + "2013-08-31T07:11:21.000Z,Cherno Alpha,123,111,12,article"
+    );
+  }
+}
diff --git 
a/multi-stage-query/src/main/java/org/apache/druid/msq/input/external/ExternalInputSliceReader.java
 
b/multi-stage-query/src/main/java/org/apache/druid/msq/input/external/ExternalInputSliceReader.java
index 5a95c53f4c4..e34ad6b4fc1 100644
--- 
a/multi-stage-query/src/main/java/org/apache/druid/msq/input/external/ExternalInputSliceReader.java
+++ 
b/multi-stage-query/src/main/java/org/apache/druid/msq/input/external/ExternalInputSliceReader.java
@@ -32,6 +32,7 @@ import org.apache.druid.data.input.impl.InlineInputSource;
 import org.apache.druid.data.input.impl.LocalInputSource;
 import org.apache.druid.data.input.impl.TimestampSpec;
 import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.FileUtils;
 import org.apache.druid.java.util.common.Intervals;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.java.util.common.logger.Logger;
@@ -59,6 +60,7 @@ import 
org.apache.druid.segment.loading.external.VirtualStorageManager;
 import org.apache.druid.utils.CloseableUtils;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -146,6 +148,15 @@ public class ExternalInputSliceReader implements 
InputSliceReader
         ColumnsFilter.all()
     );
 
+    // Some extern sources will need to download the source files to the 
specified temporaryDirectory.
+    // The fetch runs lazily when the cursor is created, and the directory 
must exist to avoid task failure.
+    try {
+      FileUtils.mkdirp(temporaryDirectory);
+    }
+    catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+
     final String description = StringUtils.format("external[%s]", 
inputSource.toString());
     final SegmentDescriptor descriptor = new 
SegmentDescriptor(Intervals.ETERNITY, "0", 0);
     final List<InputFilePointer> filePointers = inputSource.asFilePointers();


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

Reply via email to