This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new f1fa61d345 [#9744] feat(authz): Add new view privileges (#9778)
f1fa61d345 is described below
commit f1fa61d345e67de7f1f2023a7332a48c49447130
Author: roryqi <[email protected]>
AuthorDate: Fri Jan 23 10:17:16 2026 +0800
[#9744] feat(authz): Add new view privileges (#9778)
### What changes were proposed in this pull request?
Add new view privileges.
### Why are the changes needed?
Fix: #9744
### Does this PR introduce _any_ user-facing change?
Added the documents.
### How was this patch tested?
Add uts.
---
.../java/org/apache/gravitino/MetadataObject.java | 2 +
.../java/org/apache/gravitino/MetadataObjects.java | 2 +
.../apache/gravitino/authorization/Privilege.java | 6 +-
.../apache/gravitino/authorization/Privileges.java | 82 ++++++++++++++++++++++
.../gravitino/authorization/SecurableObjects.java | 16 +++++
.../org/apache/gravitino/TestMetadataObjects.java | 52 ++++++++++++++
.../authorization/TestSecurableObjects.java | 46 ++++++++++++
.../src/main/java/org/apache/gravitino/Entity.java | 1 +
.../authorization/AuthorizationUtils.java | 3 +-
.../gravitino/storage/relational/JDBCBackend.java | 2 +
.../apache/gravitino/utils/MetadataObjectUtil.java | 2 +
.../apache/gravitino/utils/NameIdentifierUtil.java | 30 ++++++++
.../org/apache/gravitino/utils/NamespaceUtil.java | 25 +++++++
.../gravitino/utils/TestMetadataObjectUtil.java | 10 +++
.../gravitino/utils/TestNameIdentifierUtil.java | 17 +++++
.../apache/gravitino/utils/TestNamespaceUtil.java | 8 +++
docs/security/access-control.md | 19 ++++-
17 files changed, 319 insertions(+), 4 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/MetadataObject.java
b/api/src/main/java/org/apache/gravitino/MetadataObject.java
index d773205065..84ec879e81 100644
--- a/api/src/main/java/org/apache/gravitino/MetadataObject.java
+++ b/api/src/main/java/org/apache/gravitino/MetadataObject.java
@@ -52,6 +52,8 @@ public interface MetadataObject {
FILESET,
/** A table is mapped the table of relational data sources like Apache
Hive, MySQL, etc. */
TABLE,
+ /** A view is mapped to the view of relational data sources like Apache
Hive, MySQL, etc. */
+ VIEW,
/**
* A topic is mapped the topic of messaging data sources like Apache
Kafka, Apache Pulsar, etc.
*/
diff --git a/api/src/main/java/org/apache/gravitino/MetadataObjects.java
b/api/src/main/java/org/apache/gravitino/MetadataObjects.java
index 586e1785b4..9bce690183 100644
--- a/api/src/main/java/org/apache/gravitino/MetadataObjects.java
+++ b/api/src/main/java/org/apache/gravitino/MetadataObjects.java
@@ -57,6 +57,7 @@ public class MetadataObjects {
Sets.newHashSet(
MetadataObject.Type.FILESET,
MetadataObject.Type.TABLE,
+ MetadataObject.Type.VIEW,
MetadataObject.Type.TOPIC,
MetadataObject.Type.MODEL);
@@ -146,6 +147,7 @@ public class MetadataObjects {
parentType = MetadataObject.Type.TABLE;
break;
case TABLE:
+ case VIEW:
case FILESET:
case TOPIC:
case MODEL:
diff --git
a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
index ea94c30b4b..3aac7af18d 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
@@ -141,7 +141,11 @@ public interface Privilege {
/** The privilege to use a job template */
USE_JOB_TEMPLATE(0L, 1L << 26),
/** The privilege to run a job */
- RUN_JOB(0L, 1L << 27);
+ RUN_JOB(0L, 1L << 27),
+ /** The privilege to create a view. */
+ CREATE_VIEW(0L, 1L << 28),
+ /** The privilege to select data from a view. */
+ SELECT_VIEW(0L, 1L << 29);
private final long highBits;
private final long lowBits;
diff --git
a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
index 45e40f5268..34944e3664 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
@@ -57,6 +57,13 @@ public class Privileges {
MetadataObject.Type.SCHEMA,
MetadataObject.Type.FILESET);
+ private static final Set<MetadataObject.Type> VIEW_SUPPORTED_TYPES =
+ Sets.immutableEnumSet(
+ MetadataObject.Type.METALAKE,
+ MetadataObject.Type.CATALOG,
+ MetadataObject.Type.SCHEMA,
+ MetadataObject.Type.VIEW);
+
/**
* Returns the Privilege with allow condition from the string representation.
*
@@ -160,6 +167,12 @@ public class Privileges {
case RUN_JOB:
return RunJob.allow();
+ // View
+ case CREATE_VIEW:
+ return CreateView.allow();
+ case SELECT_VIEW:
+ return SelectView.allow();
+
default:
throw new IllegalArgumentException("Doesn't support the privilege: " +
name);
}
@@ -267,6 +280,13 @@ public class Privileges {
// Job
case RUN_JOB:
return RunJob.deny();
+
+ // View
+ case CREATE_VIEW:
+ return CreateView.deny();
+ case SELECT_VIEW:
+ return SelectView.deny();
+
default:
throw new IllegalArgumentException("Doesn't support the privilege: " +
name);
}
@@ -1237,4 +1257,66 @@ public class Privileges {
return type == MetadataObject.Type.METALAKE || type ==
MetadataObject.Type.JOB_TEMPLATE;
}
}
+
+ /** The privilege to create a view. */
+ public static class CreateView extends GenericPrivilege<CreateView> {
+ private static final CreateView ALLOW_INSTANCE =
+ new CreateView(Condition.ALLOW, Name.CREATE_VIEW);
+ private static final CreateView DENY_INSTANCE =
+ new CreateView(Condition.DENY, Name.CREATE_VIEW);
+
+ private CreateView(Condition condition, Name name) {
+ super(condition, name);
+ }
+
+ /**
+ * @return The instance with allow condition of the privilege.
+ */
+ public static CreateView allow() {
+ return ALLOW_INSTANCE;
+ }
+
+ /**
+ * @return The instance with deny condition of the privilege.
+ */
+ public static CreateView deny() {
+ return DENY_INSTANCE;
+ }
+
+ @Override
+ public boolean canBindTo(MetadataObject.Type type) {
+ return SCHEMA_SUPPORTED_TYPES.contains(type);
+ }
+ }
+
+ /** The privilege to select data from a view. */
+ public static class SelectView extends GenericPrivilege<SelectView> {
+ private static final SelectView ALLOW_INSTANCE =
+ new SelectView(Condition.ALLOW, Name.SELECT_VIEW);
+ private static final SelectView DENY_INSTANCE =
+ new SelectView(Condition.DENY, Name.SELECT_VIEW);
+
+ private SelectView(Condition condition, Name name) {
+ super(condition, name);
+ }
+
+ /**
+ * @return The instance with allow condition of the privilege.
+ */
+ public static SelectView allow() {
+ return ALLOW_INSTANCE;
+ }
+
+ /**
+ * @return The instance with deny condition of the privilege.
+ */
+ public static SelectView deny() {
+ return DENY_INSTANCE;
+ }
+
+ @Override
+ public boolean canBindTo(MetadataObject.Type type) {
+ return VIEW_SUPPORTED_TYPES.contains(type);
+ }
+ }
}
diff --git
a/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
b/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
index e6f444cbb9..970ae683ba 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
@@ -90,6 +90,22 @@ public class SecurableObjects {
return of(MetadataObject.Type.TABLE, names, privileges);
}
+ /**
+ * Create the view {@link SecurableObject} with the given securable schema
object, view name and
+ * privileges.
+ *
+ * @param schema The schema securable object
+ * @param view The view name
+ * @param privileges The privileges of the view
+ * @return The created view {@link SecurableObject}
+ */
+ public static SecurableObject ofView(
+ SecurableObject schema, String view, List<Privilege> privileges) {
+ List<String> names =
Lists.newArrayList(DOT_SPLITTER.splitToList(schema.fullName()));
+ names.add(view);
+ return of(MetadataObject.Type.VIEW, names, privileges);
+ }
+
/**
* Create the topic {@link SecurableObject} with the given securable schema
object ,topic name and
* privileges.
diff --git a/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java
b/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java
index 391a150eba..ed41561033 100644
--- a/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java
+++ b/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java
@@ -127,4 +127,56 @@ public class TestMetadataObjects {
IllegalArgumentException.class,
() -> MetadataObjects.of("parent", "template_abc",
MetadataObject.Type.JOB_TEMPLATE));
}
+
+ @Test
+ public void testViewObject() {
+ MetadataObject viewObject =
+ MetadataObjects.of("catalog.schema", "view1",
MetadataObject.Type.VIEW);
+ Assertions.assertEquals("catalog.schema", viewObject.parent());
+ Assertions.assertEquals("view1", viewObject.name());
+ Assertions.assertEquals(MetadataObject.Type.VIEW, viewObject.type());
+ Assertions.assertEquals("catalog.schema.view1", viewObject.fullName());
+
+ MetadataObject viewObject2 =
+ MetadataObjects.of(
+ Lists.newArrayList("catalog", "schema", "view2"),
MetadataObject.Type.VIEW);
+ Assertions.assertEquals("catalog.schema", viewObject2.parent());
+ Assertions.assertEquals("view2", viewObject2.name());
+ Assertions.assertEquals(MetadataObject.Type.VIEW, viewObject2.type());
+ Assertions.assertEquals("catalog.schema.view2", viewObject2.fullName());
+
+ MetadataObject viewObject3 =
+ MetadataObjects.parse("catalog.schema.view3",
MetadataObject.Type.VIEW);
+ Assertions.assertEquals("catalog.schema", viewObject3.parent());
+ Assertions.assertEquals("view3", viewObject3.name());
+ Assertions.assertEquals(MetadataObject.Type.VIEW, viewObject3.type());
+ Assertions.assertEquals("catalog.schema.view3", viewObject3.fullName());
+
+ // Test parent
+ MetadataObject parent = MetadataObjects.parent(viewObject);
+ Assertions.assertEquals("catalog.schema", parent.fullName());
+ Assertions.assertEquals("catalog", parent.parent());
+ Assertions.assertEquals("schema", parent.name());
+ Assertions.assertEquals(MetadataObject.Type.SCHEMA, parent.type());
+
+ // Test incomplete name
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> MetadataObjects.parse("view1", MetadataObject.Type.VIEW));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> MetadataObjects.parse("catalog", MetadataObject.Type.VIEW));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> MetadataObjects.parse("catalog.schema",
MetadataObject.Type.VIEW));
+
+ // Test incomplete name list
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> MetadataObjects.of(Lists.newArrayList("catalog"),
MetadataObject.Type.VIEW));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ MetadataObjects.of(Lists.newArrayList("catalog", "schema"),
MetadataObject.Type.VIEW));
+ }
}
diff --git
a/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
b/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
index d6c4d592ee..c622fd7d63 100644
---
a/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
+++
b/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
@@ -100,6 +100,18 @@ public class TestSecurableObjects {
Lists.newArrayList(Privileges.ConsumeTopic.allow()));
Assertions.assertEquals(topic, anotherTopic);
+ SecurableObject view =
+ SecurableObjects.ofView(schema, "view",
Lists.newArrayList(Privileges.SelectView.allow()));
+ Assertions.assertEquals("catalog.schema.view", view.fullName());
+ Assertions.assertEquals(MetadataObject.Type.VIEW, view.type());
+
+ SecurableObject anotherView =
+ SecurableObjects.of(
+ MetadataObject.Type.VIEW,
+ Lists.newArrayList("catalog", "schema", "view"),
+ Lists.newArrayList(Privileges.SelectView.allow()));
+ Assertions.assertEquals(view, anotherView);
+
Exception e =
Assertions.assertThrows(
IllegalArgumentException.class,
@@ -156,6 +168,16 @@ public class TestSecurableObjects {
Lists.newArrayList("catalog", "schema", "table"),
Lists.newArrayList(Privileges.UseSchema.allow())));
Assertions.assertTrue(e.getMessage().contains("the length of names must be
2"));
+
+ e =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecurableObjects.of(
+ MetadataObject.Type.VIEW,
+ Lists.newArrayList("metalake"),
+ Lists.newArrayList(Privileges.SelectView.allow())));
+ Assertions.assertTrue(e.getMessage().contains("the length of names must be
3"));
}
@Test
@@ -187,6 +209,8 @@ public class TestSecurableObjects {
Privilege registerJobTemplate = Privileges.RegisterJobTemplate.allow();
Privilege runJob = Privileges.RunJob.allow();
Privilege useJobTemplate = Privileges.UseJobTemplate.allow();
+ Privilege createView = Privileges.CreateView.allow();
+ Privilege selectView = Privileges.SelectView.allow();
// Test create catalog
Assertions.assertTrue(createCatalog.canBindTo(MetadataObject.Type.METALAKE));
@@ -456,5 +480,27 @@ public class TestSecurableObjects {
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.COLUMN));
Assertions.assertTrue(useJobTemplate.canBindTo(MetadataObject.Type.JOB_TEMPLATE));
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.JOB));
+
+ // Test create view
+ Assertions.assertTrue(createView.canBindTo(MetadataObject.Type.METALAKE));
+ Assertions.assertTrue(createView.canBindTo(MetadataObject.Type.CATALOG));
+ Assertions.assertTrue(createView.canBindTo(MetadataObject.Type.SCHEMA));
+ Assertions.assertFalse(createView.canBindTo(MetadataObject.Type.TABLE));
+ Assertions.assertFalse(createView.canBindTo(MetadataObject.Type.TOPIC));
+ Assertions.assertFalse(createView.canBindTo(MetadataObject.Type.FILESET));
+ Assertions.assertFalse(createView.canBindTo(MetadataObject.Type.ROLE));
+ Assertions.assertFalse(createView.canBindTo(MetadataObject.Type.COLUMN));
+ Assertions.assertFalse(createView.canBindTo(MetadataObject.Type.VIEW));
+
+ // Test select view
+ Assertions.assertTrue(selectView.canBindTo(MetadataObject.Type.METALAKE));
+ Assertions.assertTrue(selectView.canBindTo(MetadataObject.Type.CATALOG));
+ Assertions.assertTrue(selectView.canBindTo(MetadataObject.Type.SCHEMA));
+ Assertions.assertFalse(selectView.canBindTo(MetadataObject.Type.TABLE));
+ Assertions.assertFalse(selectView.canBindTo(MetadataObject.Type.TOPIC));
+ Assertions.assertFalse(selectView.canBindTo(MetadataObject.Type.FILESET));
+ Assertions.assertFalse(selectView.canBindTo(MetadataObject.Type.ROLE));
+ Assertions.assertFalse(selectView.canBindTo(MetadataObject.Type.COLUMN));
+ Assertions.assertTrue(selectView.canBindTo(MetadataObject.Type.VIEW));
}
}
diff --git a/core/src/main/java/org/apache/gravitino/Entity.java
b/core/src/main/java/org/apache/gravitino/Entity.java
index 2df8e7a8da..3447b225b3 100644
--- a/core/src/main/java/org/apache/gravitino/Entity.java
+++ b/core/src/main/java/org/apache/gravitino/Entity.java
@@ -67,6 +67,7 @@ public interface Entity extends Serializable {
CATALOG,
SCHEMA,
TABLE,
+ VIEW,
COLUMN,
FILESET,
TOPIC,
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
index 387a618165..4938ff6034 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
@@ -95,7 +95,8 @@ public class AuthorizationUtils {
MetadataObject.Type.JOB,
MetadataObject.Type.JOB_TEMPLATE,
MetadataObject.Type.TAG,
- MetadataObject.Type.POLICY);
+ MetadataObject.Type.POLICY,
+ MetadataObject.Type.VIEW);
private static final Set<Privilege.Name> FILESET_PRIVILEGES =
Sets.immutableEnumSet(
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index fee9d4f16f..f660653067 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -398,6 +398,7 @@ public class JDBCBackend implements RelationalBackend {
.deleteJobsByLegacyTimeline(legacyTimeline,
GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT);
case AUDIT:
case FUNCTION:
+ case VIEW:
return 0;
// TODO: Implement hard delete logic for these entity types.
@@ -428,6 +429,7 @@ public class JDBCBackend implements RelationalBackend {
case JOB_TEMPLATE:
case JOB:
case FUNCTION: // todo: remove once function versioning is supported
+ case VIEW:
// These entity types have not implemented multi-versions, so we can
skip.
return 0;
diff --git
a/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
b/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
index 09f7ed271c..0b0cb18760 100644
--- a/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
@@ -59,6 +59,7 @@ public class MetadataObjectUtil {
.put(MetadataObject.Type.POLICY, Entity.EntityType.POLICY)
.put(MetadataObject.Type.JOB_TEMPLATE,
Entity.EntityType.JOB_TEMPLATE)
.put(MetadataObject.Type.JOB, Entity.EntityType.JOB)
+ .put(MetadataObject.Type.VIEW, Entity.EntityType.VIEW)
.build();
private MetadataObjectUtil() {}
@@ -120,6 +121,7 @@ public class MetadataObjectUtil {
return NameIdentifierUtil.ofJob(metalakeName, metadataObject.name());
case JOB_TEMPLATE:
return NameIdentifierUtil.ofJobTemplate(metalakeName,
metadataObject.name());
+ case VIEW:
case CATALOG:
case SCHEMA:
case TABLE:
diff --git
a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
index f79e948f22..02a6761fe7 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
@@ -107,6 +107,19 @@ public class NameIdentifierUtil {
return NameIdentifier.of(metalake, catalog, schema, table);
}
+ /**
+ * Create the view {@link NameIdentifier} with the given metalake, catalog,
schema and view name.
+ *
+ * @param metalake The metalake name
+ * @param catalog The catalog name
+ * @param schema The schema name
+ * @param view The view name
+ * @return The created view {@link NameIdentifier}
+ */
+ public static NameIdentifier ofView(String metalake, String catalog, String
schema, String view) {
+ return NameIdentifier.of(metalake, catalog, schema, view);
+ }
+
/**
* Create the tag {@link NameIdentifier} with the given metalake and tag
name.
*
@@ -466,6 +479,17 @@ public class NameIdentifierUtil {
NamespaceUtil.checkTable(ident.namespace());
}
+ /**
+ * Check the given {@link NameIdentifier} is a view identifier. Throw an
{@link
+ * IllegalNameIdentifierException} if it's not.
+ *
+ * @param ident The view {@link NameIdentifier} to check.
+ */
+ public static void checkView(NameIdentifier ident) {
+ NameIdentifier.check(ident != null, "View identifier must not be null");
+ NamespaceUtil.checkView(ident.namespace());
+ }
+
/**
* Check the given {@link NameIdentifier} is a column identifier. Throw an
{@link
* IllegalNameIdentifierException} if it's not.
@@ -577,6 +601,11 @@ public class NameIdentifierUtil {
String tableParent = dot.join(ident.namespace().level(1),
ident.namespace().level(2));
return MetadataObjects.of(tableParent, ident.name(),
MetadataObject.Type.TABLE);
+ case VIEW:
+ checkView(ident);
+ String viewParent = dot.join(ident.namespace().level(1),
ident.namespace().level(2));
+ return MetadataObjects.of(viewParent, ident.name(),
MetadataObject.Type.VIEW);
+
case COLUMN:
checkColumn(ident);
Namespace columnNs = ident.namespace();
@@ -836,6 +865,7 @@ public class NameIdentifierUtil {
return Entity.EntityType.MODEL;
case TABLE:
+ case VIEW:
case FILESET:
case MODEL:
case TOPIC:
diff --git a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
index 1578c264fa..3d6f76699f 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
@@ -72,6 +72,18 @@ public class NamespaceUtil {
return Namespace.of(metalake, catalog, schema);
}
+ /**
+ * Create a namespace for view.
+ *
+ * @param metalake The metalake name
+ * @param catalog The catalog name
+ * @param schema The schema name
+ * @return A namespace for view
+ */
+ public static Namespace ofView(String metalake, String catalog, String
schema) {
+ return Namespace.of(metalake, catalog, schema);
+ }
+
/**
* Create a namespace for tag.
*
@@ -272,6 +284,19 @@ public class NamespaceUtil {
namespace);
}
+ /**
+ * Check if the given view namespace is legal, throw an {@link
IllegalNamespaceException} if it's
+ * illegal.
+ *
+ * @param namespace The view namespace
+ */
+ public static void checkView(Namespace namespace) {
+ check(
+ namespace != null && namespace.length() == 3,
+ "View namespace must be non-null and have 3 levels, the input
namespace is %s",
+ namespace);
+ }
+
/**
* Check if the given column namespace is legal, throw an {@link
IllegalNamespaceException} if
* it's illegal.
diff --git
a/core/src/test/java/org/apache/gravitino/utils/TestMetadataObjectUtil.java
b/core/src/test/java/org/apache/gravitino/utils/TestMetadataObjectUtil.java
index 9e3ca43161..97c1fbf0a9 100644
--- a/core/src/test/java/org/apache/gravitino/utils/TestMetadataObjectUtil.java
+++ b/core/src/test/java/org/apache/gravitino/utils/TestMetadataObjectUtil.java
@@ -73,6 +73,11 @@ public class TestMetadataObjectUtil {
Entity.EntityType.MODEL,
MetadataObjectUtil.toEntityType(
MetadataObjects.of("catalog.schema", "model",
MetadataObject.Type.MODEL)));
+
+ Assertions.assertEquals(
+ Entity.EntityType.VIEW,
+ MetadataObjectUtil.toEntityType(
+ MetadataObjects.of("catalog.schema", "view",
MetadataObject.Type.VIEW)));
}
@Test
@@ -128,5 +133,10 @@ public class TestMetadataObjectUtil {
MetadataObjectUtil.toEntityIdent(
"metalake",
MetadataObjects.of("catalog.schema.table", "column",
MetadataObject.Type.COLUMN)));
+
+ Assertions.assertEquals(
+ NameIdentifier.of("metalake", "catalog", "schema", "view"),
+ MetadataObjectUtil.toEntityIdent(
+ "metalake", MetadataObjects.of("catalog.schema", "view",
MetadataObject.Type.VIEW)));
}
}
diff --git
a/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java
b/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java
index 6a75a5c5b6..99967d2ad3 100644
--- a/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java
+++ b/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java
@@ -136,6 +136,12 @@ public class TestNameIdentifierUtil {
MetadataObjects.parse("catalog1.schema1.model1",
MetadataObject.Type.MODEL);
assertEquals(modelObject, NameIdentifierUtil.toMetadataObject(model,
Entity.EntityType.MODEL));
+ // test view
+ NameIdentifier view = NameIdentifier.of("metalake1", "catalog1",
"schema1", "view1");
+ MetadataObject viewObject =
+ MetadataObjects.parse("catalog1.schema1.view1",
MetadataObject.Type.VIEW);
+ assertEquals(viewObject, NameIdentifierUtil.toMetadataObject(view,
Entity.EntityType.VIEW));
+
// test null
Throwable e1 =
assertThrows(
@@ -346,5 +352,16 @@ public class TestNameIdentifierUtil {
NameIdentifierUtil.buildNameIdentifier(Entity.EntityType.TOPIC,
topicName, topicEntities);
assertEquals(NameIdentifier.of(metalake, catalog, schema, topicName),
topicIdent);
assertEquals(topicName, topicIdent.name());
+
+ // Test 13: Build a VIEW identifier
+ String viewName = "my_view";
+ Map<Entity.EntityType, String> viewEntities = Maps.newHashMap();
+ viewEntities.put(Entity.EntityType.METALAKE, metalake);
+ viewEntities.put(Entity.EntityType.CATALOG, catalog);
+ viewEntities.put(Entity.EntityType.SCHEMA, schema);
+ NameIdentifier viewIdent =
+ NameIdentifierUtil.buildNameIdentifier(Entity.EntityType.VIEW,
viewName, viewEntities);
+ assertEquals(NameIdentifier.of(metalake, catalog, schema, viewName),
viewIdent);
+ assertEquals(viewName, viewIdent.name());
}
}
diff --git
a/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java
b/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java
index 58703b23b9..5255d661b8 100644
--- a/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java
+++ b/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java
@@ -83,6 +83,14 @@ public class TestNamespaceUtil {
IllegalNamespaceException.class, () ->
NamespaceUtil.checkModelVersion(ab));
Assertions.assertTrue(
excep5.getMessage().contains("Model version namespace must be non-null
and have 4 levels"));
+
+ // Test view
+ Assertions.assertThrows(IllegalNamespaceException.class, () ->
NamespaceUtil.checkView(null));
+ Throwable excep6 =
+ Assertions.assertThrows(
+ IllegalNamespaceException.class, () ->
NamespaceUtil.checkView(abcd));
+ Assertions.assertTrue(
+ excep6.getMessage().contains("View namespace must be non-null and have
3 levels"));
}
@Test
diff --git a/docs/security/access-control.md b/docs/security/access-control.md
index e2ae649a21..af5d26e6ce 100644
--- a/docs/security/access-control.md
+++ b/docs/security/access-control.md
@@ -147,6 +147,7 @@ The following metadata objects support ownership:
| Catalog |
| Schema |
| Table |
+| View |
| Topic |
| Fileset |
| Role |
@@ -202,6 +203,7 @@ Metalake (top level)
└── Catalog (represents a data source)
└── Schema
├── Table
+ ├── View
├── Topic
└── Fileset
```
@@ -313,7 +315,14 @@ and `USE_SCHEMA` privileges on its parent schema.
| SELECT_TABLE | Metalake, Catalog, Schema, Table | Select data from a table
|
DENY `MODIFY_TABLE` won't deny the `SELECT_TABLE` operation if the user has
the privilege to `ALLOW SELECT_TABLE` on the table.
-DENY `SELECT_TABLE` won‘t deny the `MODIFY_TABLE` operation if the user has
the privilege `ALLOW MODIFY_TABLE` on the table.
+DENY `SELECT_TABLE` won't deny the `MODIFY_TABLE` operation if the user has
the privilege `ALLOW MODIFY_TABLE` on the table.
+
+### View privileges
+
+| Name | Supports Securable Object | Operation |
+|-------------|---------------------------------|--------------------------|
+| CREATE_VIEW | Metalake, Catalog, Schema | Create a view |
+| SELECT_VIEW | Metalake, Catalog, Schema, View | Select data from a view |
### Topic privileges
@@ -1240,6 +1249,7 @@ The following table lists the required privileges for
each API.
| load schema | First, you should have the privilege to
load the catalog. Then, you are the owner of the metalake, catalog, schema or
have `USE_SCHEMA` on the metalake, catalog, schema.
|
| create table | First, you should have the privilege to
load the catalog and the schema. `CREATE_TABLE` on the metalake, catalog,
schema or the owner of the metalake, catalog, schema
|
| alter table | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the table,
schema,catalog, metalake or have `MODIFY_TABLE` on the table, schema, catalog,
metalake |
+| rename table across schema | First, you should have the privilege to
load the catalog and both schemas. Then, you must be the owner of the table and
have `CREATE_TABLE` privilege on the target schema, catalog, or metalake
|
| update table statistics | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the table,
schema,catalog, metalake or have `MODIFY_TABLE` on the table, schema, catalog,
metalake |
| drop table statistics | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the table,
schema,catalog, metalake or have `MODIFY_TABLE` on the table, schema, catalog,
metalake |
| update table partition statistics | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the table,
schema,catalog, metalake or have `MODIFY_TABLE` on the table, schema, catalog,
metalake |
@@ -1249,6 +1259,12 @@ The following table lists the required privileges for
each API.
| load table | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the table,
schema, metalake, catalog or have either `SELECT_TABLE` or `MODIFY_TABLE` on
the table, schema, catalog, metalake |
| list table statistics | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the table,
schema, metalake, catalog or have either `SELECT_TABLE` or `MODIFY_TABLE` on
the table, schema, catalog, metalake |
| list table partition statistics | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the table,
schema, metalake, catalog or have either `SELECT_TABLE` or `MODIFY_TABLE` on
the table, schema, catalog, metalake |
+| create view | First, you should have the privilege to
load the catalog and the schema. `CREATE_VIEW` on the metalake, catalog, schema
or the owner of the metalake, catalog, schema
|
+| alter view | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the view,
schema, catalog, metalake
|
+| rename view across schema | First, you should have the privilege to
load the catalog and both schemas. Then, you must be the owner of the view and
have `CREATE_VIEW` privilege on the target schema, catalog, or metalake
|
+| drop view | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the view,
schema, catalog, metalake
|
+| list view | First, you should have the privilege to
load the catalog and the schema. Then, the owner of the schema, catalog,
metalake can see all the views, others can see the views which they can load
|
+| load view | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the view,
schema, metalake, catalog or have `SELECT_VIEW` on the view, schema, catalog,
metalake |
| create topic | First, you should have the privilege to
load the catalog and the schema. Then, you have `CREATE_TOPIC` on the metalake,
catalog, schema or are the owner of the metalake, catalog, schema
|
| alter topic | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the topic,
schema,catalog, metalake or have `PRODUCE_TOPIC` on the topic, schema, catalog,
metalake |
| drop topic | First, you should have the privilege to
load the catalog and the schema. Then, you are one of the owners of the topic,
schema, catalog, metalake
|
@@ -1319,4 +1335,3 @@ The following table lists the required privileges for
each API.
| get a job | The owner of the metalake or the job.
|
| cancel a job | The owner of the metalake or the job.
|
| get credential | If you can load the metadata object, you
can get its credential.
|
-