Skip to content

Commit

Permalink
Support renaming a field with RENAME COLUMN in Iceberg
Browse files Browse the repository at this point in the history
  • Loading branch information
ebyhr committed Jul 24, 2023
1 parent 3004e83 commit 7df00e9
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior)

case SUPPORTS_ADD_FIELD:
case SUPPORTS_DROP_FIELD:
case SUPPORTS_RENAME_FIELD:
case SUPPORTS_SET_COLUMN_TYPE:
return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior)

case SUPPORTS_ADD_FIELD:
case SUPPORTS_DROP_FIELD:
case SUPPORTS_RENAME_FIELD:
case SUPPORTS_SET_COLUMN_TYPE:
return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Maps.transformValues;
import static com.google.common.collect.Sets.difference;
import static io.trino.plugin.base.projection.ApplyProjectionUtil.extractSupportedProjectedColumns;
Expand Down Expand Up @@ -1742,6 +1743,27 @@ public void renameColumn(ConnectorSession session, ConnectorTableHandle tableHan
}
}

@Override
public void renameField(ConnectorSession session, ConnectorTableHandle tableHandle, List<String> fieldPath, String target)
{
Table icebergTable = catalog.loadTable(session, ((IcebergTableHandle) tableHandle).getSchemaTableName());
String parentPath = String.join(".", fieldPath.subList(0, fieldPath.size() - 1));
NestedField parent = icebergTable.schema().caseInsensitiveFindField(parentPath);

String caseSensitiveParentName = icebergTable.schema().findColumnName(parent.fieldId());
NestedField source = parent.type().asStructType().caseInsensitiveField(getLast(fieldPath));

String sourcePath = caseSensitiveParentName + "." + source.name();
try {
icebergTable.updateSchema()
.renameColumn(sourcePath, target)
.commit();
}
catch (RuntimeException e) {
throw new TrinoException(ICEBERG_COMMIT_ERROR, "Failed to rename field: " + firstNonNull(e.getMessage(), e), e);
}
}

@Override
public void setColumnType(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle, io.trino.spi.type.Type type)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior)
return false;

case SUPPORTS_ADD_FIELD:
case SUPPORTS_RENAME_FIELD:
case SUPPORTS_DROP_FIELD:
return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,26 @@ public void testAddNestedField()
onTrino().executeQuery("DROP TABLE " + trinoTableName);
}

@Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS, ICEBERG_REST, ICEBERG_JDBC})
public void testRenameNestedField()
{
String baseTableName = "test_rename_nested_field_" + randomNameSuffix();
String trinoTableName = trinoTableName(baseTableName);
String sparkTableName = sparkTableName(baseTableName);

onTrino().executeQuery("CREATE TABLE " + trinoTableName + " AS SELECT CAST(row(1, row(10)) AS row(a integer, b row(x integer))) AS col");

onTrino().executeQuery("ALTER TABLE " + trinoTableName + " ADD COLUMN col.c integer");
assertThat(onTrino().executeQuery("SELECT col.a, col.b.x, col.c FROM " + trinoTableName)).containsOnly(row(1, 10, null));
assertThat(onSpark().executeQuery("SELECT col.a, col.b.x, col.c FROM " + sparkTableName)).containsOnly(row(1, 10, null));

onTrino().executeQuery("ALTER TABLE " + trinoTableName + " ADD COLUMN col.b.y integer");
assertThat(onTrino().executeQuery("SELECT col.a, col.b.x, col.b.y, col.c FROM " + trinoTableName)).containsOnly(row(1, 10, null, null));
assertThat(onSpark().executeQuery("SELECT col.a, col.b.x, col.b.y, col.c FROM " + sparkTableName)).containsOnly(row(1, 10, null, null));

onTrino().executeQuery("DROP TABLE " + trinoTableName);
}

@Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS, ICEBERG_REST, ICEBERG_JDBC})
public void testDropNestedField()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_NEGATIVE_DATE;
import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_NOT_NULL_CONSTRAINT;
import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_COLUMN;
import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_FIELD;
import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_MATERIALIZED_VIEW;
import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_MATERIALIZED_VIEW_ACROSS_SCHEMAS;
import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_SCHEMA;
Expand Down Expand Up @@ -2779,6 +2780,68 @@ public void testRenameColumn()
assertFalse(getQueryRunner().tableExists(getSession(), tableName));
}

@Test
public void testRenameRowField()
{
skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ROW_TYPE));

if (!hasBehavior(SUPPORTS_RENAME_FIELD)) {
try (TestTable table = new TestTable(getQueryRunner()::execute, "test_rename_field_", "AS SELECT CAST(row(1) AS row(x integer)) AS col")) {
assertQueryFails(
"ALTER TABLE " + table.getName() + " RENAME COLUMN col.x TO x_renamed",
"This connector does not support renaming fields");
}
return;
}

try (TestTable table = new TestTable(getQueryRunner()::execute,
"test_add_field_",
"AS SELECT CAST(row(1, row(10)) AS row(a integer, b row(x integer))) AS col")) {
assertEquals(getColumnType(table.getName(), "col"), "row(a integer, b row(x integer))");

assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.a TO a_renamed");
assertEquals(getColumnType(table.getName(), "col"), "row(a_renamed integer, b row(x integer))");
assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10)) AS row(a_renamed integer, b row(x integer)))");

// Rename a nested field
assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.b.x TO x_renamed");
assertEquals(getColumnType(table.getName(), "col"), "row(a_renamed integer, b row(x_renamed integer))");
assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10)) AS row(a_renamed integer, b row(x_renamed integer)))");

// Specify not existing fields with IF EXISTS option
assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN IF EXISTS col.a_missing TO a_missing_renamed");
assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN IF EXISTS col.b.x_missing TO x_missing_renamed");
assertEquals(getColumnType(table.getName(), "col"), "row(a_renamed integer, b row(x_renamed integer))");

// Specify existing fields without IF EXISTS option
assertQueryFails("ALTER TABLE " + table.getName() + " RENAME COLUMN col.a_renamed TO a_renamed", ".* Field 'a_renamed' already exists");
}
}

@Test
public void testRenameRowFieldCaseSensitivity()
{
skipTestUnless(hasBehavior(SUPPORTS_RENAME_FIELD));

try (TestTable table = new TestTable(getQueryRunner()::execute,
"test_add_row_field_case_sensitivity_",
"AS SELECT CAST(row(1, 2) AS row(lower integer, \"UPPER\" integer)) AS col")) {
assertEquals(getColumnType(table.getName(), "col"), "row(lower integer, UPPER integer)");

assertQueryFails("ALTER TABLE " + table.getName() + " RENAME COLUMN col.lower TO UPPER", ".* Field 'upper' already exists");
assertQueryFails("ALTER TABLE " + table.getName() + " RENAME COLUMN col.lower TO upper", ".* Field 'upper' already exists");

assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.lower TO LOWER_RENAMED");
assertEquals(getColumnType(table.getName(), "col"), "row(lower_renamed integer, UPPER integer)");

assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.\"UPPER\" TO upper_renamed");
assertEquals(getColumnType(table.getName(), "col"), "row(lower_renamed integer, upper_renamed integer)");

assertThat(query("SELECT * FROM " + table.getName()))
.matches("SELECT CAST(row(1, 2) AS row(lower_renamed integer, upper_renamed integer))");
}
}

@Test
public void testSetColumnType()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public enum TestingConnectorBehavior
SUPPORTS_DROP_COLUMN(SUPPORTS_ADD_COLUMN),
SUPPORTS_DROP_FIELD(and(SUPPORTS_DROP_COLUMN, SUPPORTS_ROW_TYPE)),
SUPPORTS_RENAME_COLUMN,
SUPPORTS_RENAME_FIELD(fallback -> fallback.test(SUPPORTS_RENAME_COLUMN) && fallback.test(SUPPORTS_ROW_TYPE)),
SUPPORTS_SET_COLUMN_TYPE,

SUPPORTS_COMMENT_ON_TABLE,
Expand Down

0 comments on commit 7df00e9

Please sign in to comment.