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

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


The following commit(s) were added to refs/heads/master by this push:
     new c1017953ac3 Adds support for reading at a given Delta Lake version or 
timestamp (#39758)
c1017953ac3 is described below

commit c1017953ac3b213a951fe9534a33afcc1c1190ee
Author: Chamikara Jayalath <[email protected]>
AuthorDate: Fri Aug 21 16:54:16 2026 -0700

    Adds support for reading at a given Delta Lake version or timestamp (#39758)
---
 .../beam_PostCommit_Java_Delta_IO_Dataflow.json    |   2 +-
 .../beam/sdk/io/delta/CreateReadTasksDoFn.java     |  23 +++-
 .../java/org/apache/beam/sdk/io/delta/DeltaIO.java |  26 ++--
 .../io/delta/DeltaReadSchemaTransformProvider.java |   6 +-
 .../org/apache/beam/sdk/io/delta/DeltaIOIT.java    | 141 ++++++++++++++-------
 .../org/apache/beam/sdk/io/delta/DeltaIOTest.java  | 102 +++++++++++++++
 .../DeltaReadSchemaTransformProviderTest.java      |  51 ++++++++
 .../beam/sdk/io/delta/DeltaWriteTestUtils.java     |  30 +++++
 .../site/content/en/documentation/io/managed-io.md |   4 +-
 9 files changed, 326 insertions(+), 59 deletions(-)

diff --git a/.github/trigger_files/beam_PostCommit_Java_Delta_IO_Dataflow.json 
b/.github/trigger_files/beam_PostCommit_Java_Delta_IO_Dataflow.json
index 12481ae0dbc..ab4daeae234 100644
--- a/.github/trigger_files/beam_PostCommit_Java_Delta_IO_Dataflow.json
+++ b/.github/trigger_files/beam_PostCommit_Java_Delta_IO_Dataflow.json
@@ -1,4 +1,4 @@
 {
   "comment": "Modify this file in a trivial way to cause this test suite to 
run.",
-  "modification": 4
+  "modification": 3
 }
diff --git 
a/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/CreateReadTasksDoFn.java
 
b/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/CreateReadTasksDoFn.java
index 36c9a1a47f8..9d4da4708e8 100644
--- 
a/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/CreateReadTasksDoFn.java
+++ 
b/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/CreateReadTasksDoFn.java
@@ -38,9 +38,20 @@ import org.checkerframework.checker.nullness.qual.Nullable;
 class CreateReadTasksDoFn extends DoFn<String, DeltaReadTask> {
   private static final long MAX_TASK_SIZE_BYTES = 1024L * 1024L * 1024L; // 1 
GB
   private final @Nullable Map<String, String> hadoopConfig;
+  private final @Nullable Long version;
+  private final @Nullable String timestamp;
 
   public CreateReadTasksDoFn(@Nullable Map<String, String> hadoopConfig) {
+    this(hadoopConfig, null, null);
+  }
+
+  public CreateReadTasksDoFn(
+      @Nullable Map<String, String> hadoopConfig,
+      @Nullable Long version,
+      @Nullable String timestamp) {
     this.hadoopConfig = hadoopConfig;
+    this.version = version;
+    this.timestamp = timestamp;
   }
 
   @ProcessElement
@@ -54,7 +65,17 @@ class CreateReadTasksDoFn extends DoFn<String, 
DeltaReadTask> {
     }
     Engine engine = DefaultEngine.create(conf);
     Table table = Table.forPath(engine, tablePath);
-    Snapshot snapshot = table.getLatestSnapshot(engine);
+    Snapshot snapshot;
+    Long versionVal = version;
+    String timestampVal = timestamp;
+    if (versionVal != null) {
+      snapshot = table.getSnapshotAsOfVersion(engine, versionVal);
+    } else if (timestampVal != null) {
+      long timestampMillis = 
java.time.Instant.parse(timestampVal).toEpochMilli();
+      snapshot = table.getSnapshotAsOfTimestamp(engine, timestampMillis);
+    } else {
+      snapshot = table.getLatestSnapshot(engine);
+    }
     Scan scan = snapshot.getScanBuilder().build();
     Row scanState = scan.getScanState(engine);
     SerializableRow serializableScanState = new SerializableRow(scanState);
diff --git 
a/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaIO.java 
b/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaIO.java
index 3a53b5c7620..4701ff59c4a 100644
--- a/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaIO.java
+++ b/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaIO.java
@@ -132,14 +132,8 @@ public class DeltaIO {
       if (path == null) {
         throw new IllegalArgumentException("Table path must be set.");
       }
-      if (getTimestamp() != null) {
-        throw new UnsupportedOperationException(
-            "Reading from a specific timestamp is not supported yet");
-      }
-
-      if (getVersion() != null) {
-        throw new UnsupportedOperationException(
-            "Reading from a specific version is not supported yet");
+      if (getVersion() != null && getTimestamp() != null) {
+        throw new IllegalArgumentException("Cannot set both version and 
timestamp.");
       }
 
       Configuration conf = new Configuration();
@@ -151,7 +145,17 @@ public class DeltaIO {
       }
       Engine engine = DefaultEngine.create(conf);
       Table table = Table.forPath(engine, path);
-      io.delta.kernel.Snapshot snapshot = table.getLatestSnapshot(engine);
+      Snapshot snapshot;
+      Long versionVal = getVersion();
+      String timestampVal = getTimestamp();
+      if (versionVal != null) {
+        snapshot = table.getSnapshotAsOfVersion(engine, versionVal);
+      } else if (timestampVal != null) {
+        long timestampMillis = 
java.time.Instant.parse(timestampVal).toEpochMilli();
+        snapshot = table.getSnapshotAsOfTimestamp(engine, timestampMillis);
+      } else {
+        snapshot = table.getLatestSnapshot(engine);
+      }
       StructType deltaSchema = snapshot.getSchema();
       if (deltaSchema == null) {
         throw new IllegalStateException("Table schema is null.");
@@ -160,7 +164,9 @@ public class DeltaIO {
 
       return input
           .apply("Create Path", Create.of(path))
-          .apply("Plan Files", ParDo.of(new CreateReadTasksDoFn(hadoopConfig)))
+          .apply(
+              "Plan Files",
+              ParDo.of(new CreateReadTasksDoFn(hadoopConfig, getVersion(), 
getTimestamp())))
           .apply("Read Logical Data", ParDo.of(new 
DeltaSourceDoFn(hadoopConfig)))
           .setRowSchema(beamSchema);
     }
diff --git 
a/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProvider.java
 
b/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProvider.java
index 48dc3a2c748..3121a36d4c3 100644
--- 
a/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProvider.java
+++ 
b/sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProvider.java
@@ -114,11 +114,13 @@ public class DeltaReadSchemaTransformProvider extends 
TypedSchemaTransformProvid
     @SchemaFieldDescription("Identifier of the Delta Lake table.")
     abstract String getTable();
 
-    @SchemaFieldDescription("Version of the Delta Lake table to read.")
+    @SchemaFieldDescription(
+        "Version of the Delta Lake table to read. Cannot be set if timestamp 
is set.")
     @Nullable
     abstract Long getVersion();
 
-    @SchemaFieldDescription("Timestamp of the Delta Lake table to read.")
+    @SchemaFieldDescription(
+        "Timestamp of the Delta Lake table to read (in UTC ISO 8601 format, 
e.g. 2026-05-20T15:43:26Z). Cannot be set if version is set.")
     @Nullable
     abstract String getTimestamp();
 
diff --git 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOIT.java 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOIT.java
index ad526008b20..ab7fd8e24f4 100644
--- 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOIT.java
+++ 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOIT.java
@@ -49,6 +49,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import org.apache.beam.sdk.extensions.gcp.options.GcpOptions;
 import org.apache.beam.sdk.managed.Managed;
 import org.apache.beam.sdk.options.ExperimentalOptions;
 import org.apache.beam.sdk.schemas.Schema;
@@ -114,19 +115,7 @@ public class DeltaIOIT {
     LOG.info("Generating Delta Lake repository at {}", repoPath);
 
     Configuration configuration = new Configuration();
-    configuration.set("fs.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem");
-    configuration.set(
-        "fs.AbstractFileSystem.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS");
-    configuration.set("fs.gs.auth.type", "APPLICATION_DEFAULT");
-    String project =
-        readPipeline
-            .getOptions()
-            .as(org.apache.beam.sdk.extensions.gcp.options.GcpOptions.class)
-            .getProject();
-    if (project != null) {
-      configuration.set("fs.gs.project.id", project);
-    }
-
+    getHadoopConfig().forEach(configuration::set);
     Engine engine = DefaultEngine.create(configuration);
     Table table = Table.forPath(engine, repoPath);
 
@@ -278,18 +267,7 @@ public class DeltaIOIT {
     ExperimentalOptions options = 
readPipeline.getOptions().as(ExperimentalOptions.class);
     ExperimentalOptions.addExperiment(options, "use_runner_v2");
 
-    Map<String, String> hadoopConfig = new HashMap<>();
-    hadoopConfig.put("fs.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem");
-    hadoopConfig.put(
-        "fs.AbstractFileSystem.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS");
-    String project =
-        readPipeline
-            .getOptions()
-            .as(org.apache.beam.sdk.extensions.gcp.options.GcpOptions.class)
-            .getProject();
-    if (project != null) {
-      hadoopConfig.put("fs.gs.project.id", project);
-    }
+    Map<String, String> hadoopConfig = getHadoopConfig();
 
     PCollection<Row> output =
         readPipeline
@@ -302,6 +280,85 @@ public class DeltaIOIT {
     readPipeline.run().waitUntilFinish();
   }
 
+  @Test
+  public void testReadDeltaLakeTableAtTimestamp() throws Exception {
+    ExperimentalOptions options = 
readPipeline.getOptions().as(ExperimentalOptions.class);
+    ExperimentalOptions.addExperiment(options, "use_runner_v2");
+
+    Map<String, String> hadoopConfig = getHadoopConfig();
+    Configuration conf = new Configuration();
+    hadoopConfig.forEach(conf::set);
+    Engine engine = DefaultEngine.create(conf);
+
+    Table table = Table.forPath(engine, repoPath);
+    long commitTimestampV0 = table.getSnapshotAsOfVersion(engine, 
0L).getTimestamp(engine);
+    String timestampV0 = 
java.time.Instant.ofEpochMilli(commitTimestampV0).toString();
+
+    // Write version 1 with additional rows
+    List<Row> additionalRows =
+        IntStream.range(100, 150)
+            .mapToObj(i -> Row.withSchema(ROW_SCHEMA).addValues(i, "name_" + 
i).build())
+            .collect(Collectors.toList());
+
+    StructType deltaSchema =
+        new StructType().add("id", IntegerType.INTEGER).add("name", 
StringType.STRING);
+
+    DeltaWriteTestUtils.writeAppendCommit(
+        engine, repoPath, 1L, System.currentTimeMillis(), deltaSchema, 
additionalRows);
+
+    PCollection<Row> output =
+        readPipeline
+            .apply(
+                Managed.read(Managed.DELTA_LAKE)
+                    .withConfig(
+                        ImmutableMap.of(
+                            "table",
+                            repoPath,
+                            "timestamp",
+                            timestampV0,
+                            "hadoop_config",
+                            hadoopConfig)))
+            .getSinglePCollection();
+
+    PAssert.that(output).containsInAnyOrder(TEST_ROWS);
+    readPipeline.run().waitUntilFinish();
+  }
+
+  @Test
+  public void testReadDeltaLakeTableAtVersion() throws Exception {
+    ExperimentalOptions options = 
readPipeline.getOptions().as(ExperimentalOptions.class);
+    ExperimentalOptions.addExperiment(options, "use_runner_v2");
+
+    Map<String, String> hadoopConfig = getHadoopConfig();
+    Configuration conf = new Configuration();
+    hadoopConfig.forEach(conf::set);
+    Engine engine = DefaultEngine.create(conf);
+
+    // Write version 1 with additional rows
+    List<Row> additionalRows =
+        IntStream.range(100, 150)
+            .mapToObj(i -> Row.withSchema(ROW_SCHEMA).addValues(i, "name_" + 
i).build())
+            .collect(Collectors.toList());
+
+    StructType deltaSchema =
+        new StructType().add("id", IntegerType.INTEGER).add("name", 
StringType.STRING);
+
+    DeltaWriteTestUtils.writeAppendCommit(
+        engine, repoPath, 1L, System.currentTimeMillis(), deltaSchema, 
additionalRows);
+
+    PCollection<Row> output =
+        readPipeline
+            .apply(
+                Managed.read(Managed.DELTA_LAKE)
+                    .withConfig(
+                        ImmutableMap.of(
+                            "table", repoPath, "version", 0L, "hadoop_config", 
hadoopConfig)))
+            .getSinglePCollection();
+
+    PAssert.that(output).containsInAnyOrder(TEST_ROWS);
+    readPipeline.run().waitUntilFinish();
+  }
+
   @Test
   public void testReadChangesDeltaLake() throws Exception {
     ExperimentalOptions options = 
readPipeline.getOptions().as(ExperimentalOptions.class);
@@ -314,24 +371,9 @@ public class DeltaIOIT {
       options.setExperiments(modifiableExperiments);
     }
 
-    Map<String, String> hadoopConfig = new HashMap<>();
-    hadoopConfig.put("fs.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem");
-    hadoopConfig.put(
-        "fs.AbstractFileSystem.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS");
-    hadoopConfig.put("fs.gs.auth.type", "APPLICATION_DEFAULT");
-    String project =
-        readPipeline
-            .getOptions()
-            .as(org.apache.beam.sdk.extensions.gcp.options.GcpOptions.class)
-            .getProject();
-    if (project != null) {
-      hadoopConfig.put("fs.gs.project.id", project);
-    }
-
-    org.apache.hadoop.conf.Configuration conf = new 
org.apache.hadoop.conf.Configuration();
-    for (Map.Entry<String, String> entry : hadoopConfig.entrySet()) {
-      conf.set(entry.getKey(), entry.getValue());
-    }
+    Map<String, String> hadoopConfig = getHadoopConfig();
+    Configuration conf = new Configuration();
+    hadoopConfig.forEach(conf::set);
     Engine engine = DefaultEngine.create(conf);
 
     StructType deltaSchema =
@@ -413,6 +455,19 @@ public class DeltaIOIT {
     readPipeline.run().waitUntilFinish();
   }
 
+  private Map<String, String> getHadoopConfig() {
+    Map<String, String> hadoopConfig = new HashMap<>();
+    hadoopConfig.put("fs.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem");
+    hadoopConfig.put(
+        "fs.AbstractFileSystem.gs.impl", 
"com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS");
+    hadoopConfig.put("fs.gs.auth.type", "APPLICATION_DEFAULT");
+    String project = 
readPipeline.getOptions().as(GcpOptions.class).getProject();
+    if (project != null) {
+      hadoopConfig.put("fs.gs.project.id", project);
+    }
+    return hadoopConfig;
+  }
+
   private static final class FormatITRowWithMetadata extends DoFn<Row, String> 
{
     @ProcessElement
     public void process(@Element Row row, OutputReceiver<String> out) {
diff --git 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOTest.java
 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOTest.java
index 0db0aef9e08..1b7f73566c1 100644
--- 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOTest.java
+++ 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaIOTest.java
@@ -38,6 +38,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.apache.avro.generic.GenericRecord;
 import org.apache.beam.sdk.extensions.avro.coders.AvroCoder;
@@ -109,6 +110,107 @@ public class DeltaIOTest {
     Assert.assertNull(readRows.getHadoopConfig());
   }
 
+  @Test
+  public void testReadRowsBothVersionAndTimestampThrows() {
+    org.apache.beam.sdk.Pipeline p = org.apache.beam.sdk.Pipeline.create();
+    IllegalArgumentException exception =
+        Assert.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                p.apply(
+                    DeltaIO.readRows()
+                        .from("/path/to/table")
+                        .withVersion(0L)
+                        .withTimestamp("2026-05-20T15:43:26Z")));
+    Assert.assertTrue(exception.getMessage().contains("Cannot set both version 
and timestamp."));
+  }
+
+  @Test
+  public void testReadRowsAtVersion() throws Exception {
+    File tableDir = tempFolder.newFolder("delta-table-read-version");
+    Engine engine = DefaultEngine.create(new 
org.apache.hadoop.conf.Configuration());
+
+    List<Row> rows = DeltaWriteTestUtils.setupTwoVersionTable(engine, 
tableDir.getAbsolutePath());
+    Row row1 = rows.get(0);
+    Row row2 = rows.get(1);
+
+    // Read at version 0
+    PCollection<Row> outputV0 =
+        
readPipeline.apply(DeltaIO.readRows().from(tableDir.getAbsolutePath()).withVersion(0L));
+
+    PAssert.that(outputV0).containsInAnyOrder(row1, row2);
+
+    readPipeline.run().waitUntilFinish();
+  }
+
+  @Test
+  public void testReadRowsAtTimestamp() throws Exception {
+    File tableDir = tempFolder.newFolder("delta-table-read-timestamp");
+    Engine engine = DefaultEngine.create(new 
org.apache.hadoop.conf.Configuration());
+
+    List<Row> rows = DeltaWriteTestUtils.setupTwoVersionTable(engine, 
tableDir.getAbsolutePath());
+    Row row1 = rows.get(0);
+    Row row2 = rows.get(1);
+
+    // Read at timestamp between version 0 and version 1
+    String timestampV0 = 
java.time.Instant.ofEpochMilli(150000000000L).toString();
+    PCollection<Row> outputV0 =
+        readPipeline.apply(
+            
DeltaIO.readRows().from(tableDir.getAbsolutePath()).withTimestamp(timestampV0));
+
+    PAssert.that(outputV0).containsInAnyOrder(row1, row2);
+
+    readPipeline.run().waitUntilFinish();
+  }
+
+  @Test
+  public void testManagedDeltaReadWithVersion() throws Exception {
+    File tableDir = tempFolder.newFolder("managed-delta-table-version");
+    Engine engine = DefaultEngine.create(new 
org.apache.hadoop.conf.Configuration());
+
+    List<Row> rows = DeltaWriteTestUtils.setupTwoVersionTable(engine, 
tableDir.getAbsolutePath());
+    Row row1 = rows.get(0);
+    Row row2 = rows.get(1);
+
+    // Read version 0 using Managed
+    PCollection<Row> output =
+        readPipeline
+            .apply(
+                Managed.read(Managed.DELTA_LAKE)
+                    .withConfig(
+                        ImmutableMap.of("table", tableDir.getAbsolutePath(), 
"version", 0L)))
+            .getSinglePCollection();
+
+    PAssert.that(output).containsInAnyOrder(row1, row2);
+
+    readPipeline.run().waitUntilFinish();
+  }
+
+  @Test
+  public void testManagedDeltaReadWithTimestamp() throws Exception {
+    File tableDir = tempFolder.newFolder("managed-delta-table-timestamp");
+    Engine engine = DefaultEngine.create(new 
org.apache.hadoop.conf.Configuration());
+
+    List<Row> rows = DeltaWriteTestUtils.setupTwoVersionTable(engine, 
tableDir.getAbsolutePath());
+    Row row1 = rows.get(0);
+    Row row2 = rows.get(1);
+
+    // Read timestamp after version 0 using Managed
+    String timestampV0 = 
java.time.Instant.ofEpochMilli(150000000000L).toString();
+    PCollection<Row> output =
+        readPipeline
+            .apply(
+                Managed.read(Managed.DELTA_LAKE)
+                    .withConfig(
+                        ImmutableMap.of(
+                            "table", tableDir.getAbsolutePath(), "timestamp", 
timestampV0)))
+            .getSinglePCollection();
+
+    PAssert.that(output).containsInAnyOrder(row1, row2);
+
+    readPipeline.run().waitUntilFinish();
+  }
+
   @Test
   public void testPrintScanStateSchema() throws Exception {
     File tableDir = tempFolder.newFolder("delta-table-schema");
diff --git 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProviderTest.java
 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProviderTest.java
index 77aef7bce49..2e2060b81ea 100644
--- 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProviderTest.java
+++ 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaReadSchemaTransformProviderTest.java
@@ -20,9 +20,12 @@ package org.apache.beam.sdk.io.delta;
 import static 
org.apache.beam.sdk.io.delta.DeltaReadSchemaTransformProvider.Configuration;
 import static 
org.apache.beam.sdk.io.delta.DeltaReadSchemaTransformProvider.OUTPUT_TAG;
 
+import io.delta.kernel.defaults.engine.DefaultEngine;
+import io.delta.kernel.engine.Engine;
 import java.io.File;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.util.List;
 import org.apache.avro.generic.GenericRecord;
 import org.apache.beam.sdk.extensions.avro.coders.AvroCoder;
 import org.apache.beam.sdk.extensions.avro.schemas.utils.AvroUtils;
@@ -124,4 +127,52 @@ public class DeltaReadSchemaTransformProviderTest {
 
     readPipeline.run().waitUntilFinish();
   }
+
+  @Test
+  public void testReadWithVersion() throws Exception {
+    File tableDir = tempFolder.newFolder("delta-table-provider-version");
+    Engine engine = DefaultEngine.create(new 
org.apache.hadoop.conf.Configuration());
+
+    List<Row> rows = DeltaWriteTestUtils.setupTwoVersionTable(engine, 
tableDir.getAbsolutePath());
+    Row row1 = rows.get(0);
+    Row row2 = rows.get(1);
+
+    Configuration readConfig =
+        
Configuration.builder().setTable(tableDir.getAbsolutePath()).setVersion(0L).build();
+
+    PCollection<Row> output =
+        PCollectionRowTuple.empty(readPipeline)
+            .apply(new DeltaReadSchemaTransformProvider().from(readConfig))
+            .get(OUTPUT_TAG);
+
+    PAssert.that(output).containsInAnyOrder(row1, row2);
+
+    readPipeline.run().waitUntilFinish();
+  }
+
+  @Test
+  public void testReadWithTimestamp() throws Exception {
+    File tableDir = tempFolder.newFolder("delta-table-provider-timestamp");
+    Engine engine = DefaultEngine.create(new 
org.apache.hadoop.conf.Configuration());
+
+    List<Row> rows = DeltaWriteTestUtils.setupTwoVersionTable(engine, 
tableDir.getAbsolutePath());
+    Row row1 = rows.get(0);
+    Row row2 = rows.get(1);
+
+    String timestampV0 = 
java.time.Instant.ofEpochMilli(150000000000L).toString();
+    Configuration readConfig =
+        Configuration.builder()
+            .setTable(tableDir.getAbsolutePath())
+            .setTimestamp(timestampV0)
+            .build();
+
+    PCollection<Row> output =
+        PCollectionRowTuple.empty(readPipeline)
+            .apply(new DeltaReadSchemaTransformProvider().from(readConfig))
+            .get(OUTPUT_TAG);
+
+    PAssert.that(output).containsInAnyOrder(row1, row2);
+
+    readPipeline.run().waitUntilFinish();
+  }
 }
diff --git 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaWriteTestUtils.java
 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaWriteTestUtils.java
index 4ae75bcd47c..55646de749f 100644
--- 
a/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaWriteTestUtils.java
+++ 
b/sdks/java/io/delta/src/test/java/org/apache/beam/sdk/io/delta/DeltaWriteTestUtils.java
@@ -48,6 +48,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import javax.annotation.Nullable;
+import org.apache.beam.sdk.schemas.Schema;
 import org.apache.beam.sdk.values.Row;
 import org.joda.time.Instant;
 
@@ -368,4 +369,33 @@ final class DeltaWriteTestUtils {
       commitFile.setLastModified(timestamp);
     }
   }
+
+  /**
+   * Sets up a Delta table with two commit versions containing test rows.
+   *
+   * <p>Version 0 is committed at timestamp 100000000000L with rows ["row-1", 
"row-2"]. Version 1 is
+   * committed at timestamp 200000000000L with row ["row-3"].
+   *
+   * @param engine the Delta Lake {@link Engine} instance to use
+   * @param tablePath the path of the Delta table to create
+   * @return the list of {@link Row} objects written [row1, row2, row3]
+   * @throws Exception if any error occurs during write or commit
+   */
+  static List<Row> setupTwoVersionTable(Engine engine, String tablePath) 
throws Exception {
+    Schema schema = Schema.builder().addField("name", 
Schema.FieldType.STRING).build();
+    Row row1 = Row.withSchema(schema).addValues("row-1").build();
+    Row row2 = Row.withSchema(schema).addValues("row-2").build();
+    Row row3 = Row.withSchema(schema).addValues("row-3").build();
+    StructType deltaSchema = new StructType().add("name", StringType.STRING);
+
+    // Commit version 0
+    writeAppendCommit(
+        engine, tablePath, 0L, 100000000000L, deltaSchema, 
java.util.Arrays.asList(row1, row2));
+
+    // Commit version 1
+    writeAppendCommit(
+        engine, tablePath, 1L, 200000000000L, deltaSchema, 
java.util.Arrays.asList(row3));
+
+    return java.util.Arrays.asList(row1, row2, row3);
+  }
 }
diff --git a/website/www/site/content/en/documentation/io/managed-io.md 
b/website/www/site/content/en/documentation/io/managed-io.md
index 70b5efe9ed1..5eb6f04ab80 100644
--- a/website/www/site/content/en/documentation/io/managed-io.md
+++ b/website/www/site/content/en/documentation/io/managed-io.md
@@ -304,7 +304,7 @@ and Beam SQL is invoked via the Managed API under the hood.
         <code style="color: green">str</code>
       </td>
       <td>
-        Timestamp of the Delta Lake table to read.
+        Timestamp of the Delta Lake table to read (in UTC ISO 8601 format, 
e.g. <code>2026-05-20T15:43:26Z</code>). Cannot be set if version is set.
       </td>
     </tr>
     <tr>
@@ -315,7 +315,7 @@ and Beam SQL is invoked via the Managed API under the hood.
         <code style="color: #f54251">int64</code>
       </td>
       <td>
-        Version of the Delta Lake table to read.
+        Version of the Delta Lake table to read. Cannot be set if timestamp is 
set.
       </td>
     </tr>
   </table>

Reply via email to