Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions api/src/main/java/org/apache/iceberg/view/View.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,15 @@ default UpdateLocation updateLocation() {
default UUID uuid() {
throw new UnsupportedOperationException("Retrieving a view's uuid is not supported");
}

/**
* Returns the view representation for the given SQL dialect
*
* @return the view representation for the given SQL dialect, or null if no representation could
* be resolved
*/
default SQLViewRepresentation sqlFor(String dialect) {
throw new UnsupportedOperationException(
"Resolving a sql with a given dialect is not supported");
}
}
25 changes: 25 additions & 0 deletions core/src/main/java/org/apache/iceberg/view/BaseView.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.UUID;
import org.apache.iceberg.Schema;
import org.apache.iceberg.UpdateLocation;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;

public class BaseView implements View, Serializable {

Expand Down Expand Up @@ -103,4 +104,28 @@ public UpdateLocation updateLocation() {
public UUID uuid() {
return UUID.fromString(ops.current().uuid());
}

/**
* This implementation of sqlFor will resolve what is considered the "closest" dialect. If an
* exact match is found, then that is returned. Otherwise, the first representation would be
* returned. If no SQL representation is found, null is returned.
*/
@Override
public SQLViewRepresentation sqlFor(String dialect) {
Preconditions.checkArgument(dialect != null, "Invalid dialect: null");
Preconditions.checkArgument(!dialect.isEmpty(), "Invalid dialect: (empty string)");
SQLViewRepresentation closest = null;
for (ViewRepresentation representation : currentVersion().representations()) {
if (representation instanceof SQLViewRepresentation) {
SQLViewRepresentation sqlViewRepresentation = (SQLViewRepresentation) representation;
if (sqlViewRepresentation.dialect().equals(dialect)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious whether equalsIgnoreCase would make sense here?

Copy link
Contributor Author

@amogh-jahagirdar amogh-jahagirdar Dec 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this was equalsIgnoreCase before but when I refactored back into a loop, it mistakenly became equals. Let me fix this.

Copy link
Contributor Author

@amogh-jahagirdar amogh-jahagirdar Dec 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But yes I think the API should be case insensitive (in practice it won't really matter I think because engines would just read and write the view through a constant but it's helpful from an API perspective if someone wants to create the view from say a Python script which doesn't necessarily know the exact constant). The API should still return the same representation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should add a test for this as well

return sqlViewRepresentation;
} else if (closest == null) {
closest = sqlViewRepresentation;
}
}
}

return closest;
}
}
48 changes: 48 additions & 0 deletions core/src/test/java/org/apache/iceberg/view/ViewCatalogTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1669,4 +1669,52 @@ public void concurrentReplaceViewVersion() {
.build());
}
}

@Test
public void testSqlForMultipleDialects() {
TableIdentifier identifier = TableIdentifier.of("ns", "view");

if (requiresNamespaceCreate()) {
catalog().createNamespace(identifier.namespace());
}

View view =
catalog()
.buildView(identifier)
.withSchema(SCHEMA)
.withDefaultNamespace(identifier.namespace())
.withDefaultCatalog(catalog().name())
.withQuery("spark", "select * from ns.tbl")
.withQuery("trino", "select * from ns.tbl using X")
.create();

assertThat(view.sqlFor("spark").sql()).isEqualTo("select * from ns.tbl");
assertThat(view.sqlFor("trino").sql()).isEqualTo("select * from ns.tbl using X");
assertThat(view.sqlFor("unknown-dialect").sql()).isEqualTo("select * from ns.tbl");
}

@Test
public void testSqlForInvalidArguments() {
TableIdentifier identifier = TableIdentifier.of("ns", "view");

if (requiresNamespaceCreate()) {
catalog().createNamespace(identifier.namespace());
}

View view =
catalog()
.buildView(identifier)
.withSchema(SCHEMA)
.withDefaultNamespace(identifier.namespace())
.withDefaultCatalog(catalog().name())
.withQuery("spark", "select * from ns.tbl")
.create();

assertThatThrownBy(() -> view.sqlFor(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid dialect: null");
assertThatThrownBy(() -> view.sqlFor(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid dialect: (empty string)");
}
}