diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java index c50341811c98..231669cc6337 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java @@ -766,7 +766,7 @@ private Type handleResolvedField(Expression node, ResolvedField resolvedField, C } if (field.getOriginTable().isPresent() && field.getOriginColumnName().isPresent()) { - tableColumnReferences.put(field.getOriginTable().get(), field.getOriginColumnName().get()); + tableColumnReferences.put(field.getOriginTable().get(), field.getOriginColumnName().get().toLowerCase(ENGLISH)); } sourceFields.add(field); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java index cdfbd32a46c2..02dc963fef69 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java @@ -212,6 +212,7 @@ import static io.trino.sql.tree.PatternSearchMode.Mode.INITIAL; import static io.trino.sql.tree.SkipTo.Position.PAST_LAST; import static io.trino.type.Json2016Type.JSON_2016; +import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; class RelationPlanner @@ -440,7 +441,7 @@ private RelationPlan addColumnMasks(Table table, RelationPlan plan) for (int i = 0; i < plan.getDescriptor().getAllFieldCount(); i++) { Field field = plan.getDescriptor().getFieldByIndex(i); - io.trino.sql.tree.Expression mask = columnMasks.get(field.getName().orElseThrow()); + io.trino.sql.tree.Expression mask = columnMasks.get(field.getName().orElseThrow().toLowerCase(ENGLISH)); Symbol symbol = plan.getFieldMappings().get(i); Expression projection = symbol.toSymbolReference(); if (mask != null) { diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java b/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java index 94eb4684502c..2dbdb74bf9ef 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java @@ -89,6 +89,17 @@ public TestColumnMask() Optional.of(VIEW_OWNER), false, ImmutableList.of()); + ConnectorViewDefinition viewWithDifferentCase = new ConnectorViewDefinition( + "SELECT NATIONKEY, NAME FROM local.tiny.nation", + Optional.empty(), + Optional.empty(), + ImmutableList.of( + new ConnectorViewDefinition.ViewColumn("NATIONKEY", BigintType.BIGINT.getTypeId(), Optional.empty()), + new ConnectorViewDefinition.ViewColumn("NAME", VarcharType.createVarcharType(25).getTypeId(), Optional.empty())), + Optional.empty(), + Optional.of(VIEW_OWNER), + false, + ImmutableList.of()); ConnectorViewDefinition viewWithNested = new ConnectorViewDefinition( """ @@ -171,6 +182,7 @@ public TestColumnMask() }) .withGetViews((s, prefix) -> ImmutableMap.of( new SchemaTableName("default", "nation_view"), view, + new SchemaTableName("default", "nation_view_uppercase"), viewWithDifferentCase, new SchemaTableName("default", "view_with_nested"), viewWithNested)) .withGetMaterializedViews((s, prefix) -> ImmutableMap.of( new SchemaTableName("default", "nation_materialized_view"), materializedView, @@ -456,6 +468,23 @@ public void testView() assertThat(assertions.query("SELECT name FROM mock.default.nation_view WHERE nationkey = 1")).matches("VALUES CAST('ANITNEGRA' AS VARCHAR(25))"); } + @Test + public void testViewWithUppercaseColumnName() + { + accessControl.reset(); + accessControl.columnMask( + new QualifiedObjectName(MOCK_CATALOG, "default", "nation_view_uppercase"), + "name", + USER, + ViewExpression.builder() + .identity(USER) + .catalog(LOCAL_CATALOG) + .schema("tiny") + .expression("reverse(name)") + .build()); + assertThat(assertions.query("SELECT name FROM mock.default.nation_view_uppercase WHERE nationkey = 1")).matches("VALUES CAST('ANITNEGRA' AS VARCHAR(25))"); + } + @Test public void testTableReferenceInWithClause() { diff --git a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java index 5d556c6f41ed..244305ac3e3c 100644 --- a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java +++ b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java @@ -406,6 +406,16 @@ public void testViewColumnAccessControl() "SELECT * FROM " + columnAccessViewName, privilege(getSession().getUser(), "orders", SELECT_COLUMN)); + // a view which exposes underlying columns with different case + assertAccessAllowed( + viewOwnerSession, + "CREATE VIEW " + columnAccessViewName + "_uppercase AS SELECT orderkey AS ORDERKEY FROM orders"); + // access control should "see" lowercase name + assertAccessDenied( + "SELECT * FROM " + columnAccessViewName + "_uppercase", + "Cannot select from columns \\[orderkey] in table or view .*_uppercase", + privilege(getSession().getUser(), columnAccessViewName + "_uppercase.orderkey", SELECT_COLUMN)); + Session nestedViewOwnerSession = TestingSession.testSessionBuilder() .setIdentity(Identity.ofUser("test_nested_view_access_owner")) .setCatalog(getSession().getCatalog())