diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/Table.java b/kernel/kernel-api/src/main/java/io/delta/kernel/Table.java
index a9c6d625bb..fb3d7eca50 100644
--- a/kernel/kernel-api/src/main/java/io/delta/kernel/Table.java
+++ b/kernel/kernel-api/src/main/java/io/delta/kernel/Table.java
@@ -22,6 +22,7 @@
import io.delta.kernel.exceptions.TableNotFoundException;
import io.delta.kernel.internal.TableImpl;
import java.io.IOException;
+import java.util.Optional;
/**
* Represents the Delta Lake table for a given path.
@@ -57,6 +58,24 @@ static Table forPath(Engine engine, String path) {
return TableImpl.forPath(engine, path);
}
+ /**
+ * Instantiate a table object for the Delta Lake table at the given path and associate it with the
+ * given {@link TableIdentifier}.
+ *
+ *
See {@link #forPath(Engine, String)} for more details on behavior when the table path does
+ * or does not exist.
+ *
+ * @param engine the {@link Engine} instance to use in Delta Kernel.
+ * @param path location of the table. Path is resolved to fully qualified path using the given
+ * {@code engine}.
+ * @param tableId the {@link TableIdentifier} to associate with the {@link Table}
+ * @return an instance of {@link Table} representing the Delta table at the given path and
+ * associated with the given {@link TableIdentifier}
+ */
+ static Table forPathWithTableId(Engine engine, String path, TableIdentifier tableId) {
+ return TableImpl.forPathWithTableId(engine, path, tableId);
+ }
+
/**
* The fully qualified path of this {@link Table} instance.
*
@@ -66,6 +85,14 @@ static Table forPath(Engine engine, String path) {
*/
String getPath(Engine engine);
+ /**
+ * The table identifier of this {@link Table} instance.
+ *
+ * @return the table identifier, or {@link Optional#empty()} if none is set.
+ * @since 3.4.0
+ */
+ Optional getTableId();
+
/**
* Get the latest snapshot of the table.
*
diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/internal/TableImpl.java b/kernel/kernel-api/src/main/java/io/delta/kernel/internal/TableImpl.java
index 0691c24ab8..acdee81825 100644
--- a/kernel/kernel-api/src/main/java/io/delta/kernel/internal/TableImpl.java
+++ b/kernel/kernel-api/src/main/java/io/delta/kernel/internal/TableImpl.java
@@ -47,39 +47,58 @@
public class TableImpl implements Table {
+ //////////////////////////////////
+ // Static variables and methods //
+ //////////////////////////////////
+
private static final Logger logger = LoggerFactory.getLogger(TableImpl.class);
public static Table forPath(Engine engine, String path) {
return forPath(engine, path, System::currentTimeMillis);
}
+ public static Table forPathWithTableId(Engine engine, String path, TableIdentifier tableId) {
+ return create(engine, path, Optional.of(tableId), System::currentTimeMillis);
+ }
+
+ public static Table forPath(Engine engine, String path, Clock clock) {
+ return create(engine, path, Optional.empty() /* tableId */, clock);
+ }
+
/**
* Instantiate a table object for the Delta Lake table at the given path. It takes an additional
* parameter called {@link Clock} which helps in testing.
*
* @param engine {@link Engine} instance to use in Delta Kernel.
* @param path location of the table.
+ * @param tableId the {@link TableIdentifier} to associate with the table.
* @param clock {@link Clock} instance to use for time-related operations.
* @return an instance of {@link Table} representing the Delta table at the given path
*/
- public static Table forPath(Engine engine, String path, Clock clock) {
- String resolvedPath;
+ private static Table create(
+ Engine engine, String path, Optional tableId, Clock clock) {
try {
- resolvedPath =
+ final String resolvedPath =
wrapEngineExceptionThrowsIO(
() -> engine.getFileSystemClient().resolvePath(path), "Resolving path %s", path);
+ return new TableImpl(resolvedPath, tableId, clock);
} catch (IOException io) {
throw new UncheckedIOException(io);
}
- return new TableImpl(resolvedPath, clock);
}
+ //////////////////////////////////
+ // Member variables and methods //
+ //////////////////////////////////
+
private final SnapshotManager snapshotManager;
private final String tablePath;
+ private final Optional tableId;
private final Clock clock;
- public TableImpl(String tablePath, Clock clock) {
+ public TableImpl(String tablePath, Optional tableId, Clock clock) {
this.tablePath = tablePath;
+ this.tableId = tableId;
final Path dataPath = new Path(tablePath);
final Path logPath = new Path(dataPath, "_delta_log");
this.snapshotManager = new SnapshotManager(logPath, dataPath);
@@ -91,6 +110,11 @@ public String getPath(Engine engine) {
return tablePath;
}
+ @Override
+ public Optional getTableId() {
+ return tableId;
+ }
+
@Override
public Snapshot getLatestSnapshot(Engine engine) throws TableNotFoundException {
SnapshotQueryContext snapshotContext = SnapshotQueryContext.forLatestSnapshot(tablePath);
diff --git a/kernel/kernel-api/src/test/scala/io/delta/kernel/TableIdentifierSuite.scala b/kernel/kernel-api/src/test/scala/io/delta/kernel/TableIdentifierSuite.scala
new file mode 100644
index 0000000000..b1b4c51aab
--- /dev/null
+++ b/kernel/kernel-api/src/test/scala/io/delta/kernel/TableIdentifierSuite.scala
@@ -0,0 +1,75 @@
+/*
+ * Copyright (2024) The Delta Lake Project Authors.
+ *
+ * Licensed 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 io.delta.kernel
+
+import org.scalatest.funsuite.AnyFunSuite
+
+class TableIdentifierSuite extends AnyFunSuite {
+
+ test("TableIdentifier should throw IllegalArgumentException for null or empty namespace") {
+ assertThrows[IllegalArgumentException] {
+ new TableIdentifier(null, "table")
+ }
+ assertThrows[IllegalArgumentException] {
+ new TableIdentifier(Array(), "table")
+ }
+ }
+
+ test("TableIdentifier should throw NullPointerException for null table name") {
+ assertThrows[NullPointerException] {
+ new TableIdentifier(Array("catalog", "schema"), null)
+ }
+ }
+
+ test("TableIdentifier should return the correct namespace and name") {
+ val namespace = Array("catalog", "schema")
+ val name = "testTable"
+ val tid = new TableIdentifier(namespace, name)
+
+ assert(tid.getNamespace.sameElements(namespace))
+ assert(tid.getName == name)
+ }
+
+ test("TableIdentifiers with same namespace and name should be equal") {
+ val tid1 = new TableIdentifier(Array("catalog", "schema"), "table")
+ val tid2 = new TableIdentifier(Array("catalog", "schema"), "table")
+
+ assert(tid1 == tid2)
+ assert(tid1.hashCode == tid2.hashCode)
+ }
+
+ test("TableIdentifiers with different namespace or name should not be equal") {
+ val tid1 = new TableIdentifier(Array("catalog", "schema1"), "table1")
+ val tid2 = new TableIdentifier(Array("catalog", "schema2"), "table1")
+ val tid3 = new TableIdentifier(Array("catalog", "schema1"), "table2")
+
+ assert(tid1 != tid2)
+ assert(tid1 != tid3)
+ }
+
+ test("TableIdentifier toString") {
+ // Normal case
+ val tidNormal = new TableIdentifier(Array("catalog", "schema"), "table")
+ val expectedNormal = "TableIdentifier{catalog.schema.table}"
+ assert(tidNormal.toString == expectedNormal)
+
+ // Special case: should escape backticks
+ val tidSpecial = new TableIdentifier(Array("catalog", "sche`ma"), "tab`le")
+ val expectedSpecial = "TableIdentifier{catalog.sche``ma.tab``le}"
+ assert(tidSpecial.toString == expectedSpecial)
+ }
+}