diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java index fd00c2a0e07..7514d2b930e 100644 --- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java +++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java @@ -88,6 +88,7 @@ import org.apache.gravitino.listener.TableEventDispatcher; import org.apache.gravitino.listener.TagEventDispatcher; import org.apache.gravitino.listener.TopicEventDispatcher; +import org.apache.gravitino.listener.ViewEventDispatcher; import org.apache.gravitino.lock.LockManager; import org.apache.gravitino.metalake.MetalakeDispatcher; import org.apache.gravitino.metalake.MetalakeManager; @@ -633,13 +634,17 @@ private void initGravitinoServerComponents() { new FunctionEventDispatcher(eventBus, functionNormalizeDispatcher); this.functionDispatcher = new FunctionHookDispatcher(functionEventDispatcher); - // TODO: Add ViewHookDispatcher and ViewEventDispatcher when needed for view-specific hooks - // and event handling. + // View operation chain: ViewEventDispatcher -> ViewNormalizeDispatcher -> + // ViewOperationDispatcher. + // TODO(#11007): Add ViewHookDispatcher for view ownership and privilege hooks when view + // privilege support is finalized. ViewOperationDispatcher viewOperationDispatcher = new ViewOperationDispatcher(catalogManager, entityStore, idGenerator); ViewNormalizeDispatcher viewNormalizeDispatcher = new ViewNormalizeDispatcher(viewOperationDispatcher, catalogManager); - this.viewDispatcher = viewNormalizeDispatcher; + ViewEventDispatcher viewEventDispatcher = + new ViewEventDispatcher(eventBus, viewNormalizeDispatcher); + this.viewDispatcher = viewEventDispatcher; this.statisticDispatcher = new StatisticEventDispatcher( diff --git a/core/src/main/java/org/apache/gravitino/audit/AuditLog.java b/core/src/main/java/org/apache/gravitino/audit/AuditLog.java index 1d03be7d3ef..bfd76e84cce 100644 --- a/core/src/main/java/org/apache/gravitino/audit/AuditLog.java +++ b/core/src/main/java/org/apache/gravitino/audit/AuditLog.java @@ -105,6 +105,16 @@ import org.apache.gravitino.listener.api.event.PurgePartitionFailureEvent; import org.apache.gravitino.listener.api.event.PurgeTableEvent; import org.apache.gravitino.listener.api.event.PurgeTableFailureEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.DropViewEvent; +import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.ListViewEvent; +import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent; /** The interface define unified audit log schema. */ public interface AuditLog { @@ -551,6 +561,16 @@ public static Operation fromEvent(Event event) { return LOAD_TOPIC; } else if (event instanceof ListTopicEvent || event instanceof ListTopicFailureEvent) { return LIST_TOPIC; + } else if (event instanceof CreateViewEvent || event instanceof CreateViewFailureEvent) { + return CREATE_VIEW; + } else if (event instanceof AlterViewEvent || event instanceof AlterViewFailureEvent) { + return ALTER_VIEW; + } else if (event instanceof DropViewEvent || event instanceof DropViewFailureEvent) { + return DROP_VIEW; + } else if (event instanceof LoadViewEvent || event instanceof LoadViewFailureEvent) { + return LOAD_VIEW; + } else if (event instanceof ListViewEvent || event instanceof ListViewFailureEvent) { + return LIST_VIEW; } else if (event instanceof CreateFilesetEvent || event instanceof CreateFilesetFailureEvent) { return CREATE_FILESET; 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 4938ff60341..34632d965cb 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java +++ b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java @@ -602,6 +602,10 @@ public static List getMetadataObjectLocation( case TOPIC: // Topic doesn't have locations now. break; + case VIEW: + // Views are logical metadata objects without a single storage location; privilege plugins + // operate on metadata only (same idea as TOPIC). + break; default: throw new AuthorizationPluginException( "Failed to get location paths for metadata object %s type %s", ident, type); diff --git a/core/src/main/java/org/apache/gravitino/listener/ViewEventDispatcher.java b/core/src/main/java/org/apache/gravitino/listener/ViewEventDispatcher.java new file mode 100644 index 00000000000..764e989c63c --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/ViewEventDispatcher.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener; + +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; +import org.apache.gravitino.catalog.ViewDispatcher; +import org.apache.gravitino.catalog.ViewOperationDispatcher; +import org.apache.gravitino.exceptions.NoSuchSchemaException; +import org.apache.gravitino.exceptions.NoSuchViewException; +import org.apache.gravitino.exceptions.ViewAlreadyExistsException; +import org.apache.gravitino.listener.api.event.view.AlterViewEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewPreEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewPreEvent; +import org.apache.gravitino.listener.api.event.view.DropViewEvent; +import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.DropViewPreEvent; +import org.apache.gravitino.listener.api.event.view.ListViewEvent; +import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.ListViewPreEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewPreEvent; +import org.apache.gravitino.listener.api.info.ViewInfo; +import org.apache.gravitino.rel.Column; +import org.apache.gravitino.rel.Representation; +import org.apache.gravitino.rel.View; +import org.apache.gravitino.rel.ViewChange; +import org.apache.gravitino.utils.PrincipalUtils; + +/** + * Decorates a {@link ViewDispatcher} to dispatch pre/post/failure view events to an {@link + * EventBus}, mirroring {@link TableEventDispatcher}. + */ +public class ViewEventDispatcher implements ViewDispatcher { + + private final EventBus eventBus; + private final ViewDispatcher dispatcher; + + /** + * @param eventBus event bus for listener plugins + * @param dispatcher underlying dispatcher (for example {@link ViewOperationDispatcher} behind a + * normalize layer) + */ + public ViewEventDispatcher(EventBus eventBus, ViewDispatcher dispatcher) { + this.eventBus = eventBus; + this.dispatcher = dispatcher; + } + + @Override + public NameIdentifier[] listViews(Namespace namespace) throws NoSuchSchemaException { + eventBus.dispatchEvent(new ListViewPreEvent(PrincipalUtils.getCurrentUserName(), namespace)); + try { + NameIdentifier[] identifiers = dispatcher.listViews(namespace); + eventBus.dispatchEvent(new ListViewEvent(PrincipalUtils.getCurrentUserName(), namespace)); + return identifiers; + } catch (Exception e) { + eventBus.dispatchEvent( + new ListViewFailureEvent(PrincipalUtils.getCurrentUserName(), namespace, e)); + throw e; + } + } + + @Override + public View loadView(NameIdentifier ident) throws NoSuchViewException { + eventBus.dispatchEvent(new LoadViewPreEvent(PrincipalUtils.getCurrentUserName(), ident)); + try { + View view = dispatcher.loadView(ident); + eventBus.dispatchEvent( + new LoadViewEvent(PrincipalUtils.getCurrentUserName(), ident, new ViewInfo(view))); + return view; + } catch (Exception e) { + eventBus.dispatchEvent( + new LoadViewFailureEvent(PrincipalUtils.getCurrentUserName(), ident, e)); + throw e; + } + } + + @Override + public boolean viewExists(NameIdentifier ident) { + return dispatcher.viewExists(ident); + } + + @Override + public View createView( + NameIdentifier ident, + @Nullable String comment, + Column[] columns, + Representation[] representations, + @Nullable String defaultCatalog, + @Nullable String defaultSchema, + Map properties) + throws NoSuchSchemaException, ViewAlreadyExistsException { + ViewInfo createRequest = + new ViewInfo( + ident.name(), + columns, + comment, + representations, + defaultCatalog, + defaultSchema, + properties, + null); + eventBus.dispatchEvent( + new CreateViewPreEvent(PrincipalUtils.getCurrentUserName(), ident, createRequest)); + try { + View view = + dispatcher.createView( + ident, comment, columns, representations, defaultCatalog, defaultSchema, properties); + eventBus.dispatchEvent( + new CreateViewEvent(PrincipalUtils.getCurrentUserName(), ident, new ViewInfo(view))); + return view; + } catch (Exception e) { + eventBus.dispatchEvent( + new CreateViewFailureEvent(PrincipalUtils.getCurrentUserName(), ident, e, createRequest)); + throw e; + } + } + + @Override + public View alterView(NameIdentifier ident, ViewChange... changes) + throws NoSuchViewException, IllegalArgumentException { + eventBus.dispatchEvent( + new AlterViewPreEvent(PrincipalUtils.getCurrentUserName(), ident, changes)); + try { + View view = dispatcher.alterView(ident, changes); + eventBus.dispatchEvent( + new AlterViewEvent( + PrincipalUtils.getCurrentUserName(), ident, changes, new ViewInfo(view))); + return view; + } catch (Exception e) { + eventBus.dispatchEvent( + new AlterViewFailureEvent(PrincipalUtils.getCurrentUserName(), ident, e, changes)); + throw e; + } + } + + @Override + public boolean dropView(NameIdentifier ident) { + eventBus.dispatchEvent(new DropViewPreEvent(PrincipalUtils.getCurrentUserName(), ident)); + try { + boolean existed = dispatcher.dropView(ident); + eventBus.dispatchEvent( + new DropViewEvent(PrincipalUtils.getCurrentUserName(), ident, existed)); + return existed; + } catch (Exception e) { + eventBus.dispatchEvent( + new DropViewFailureEvent(PrincipalUtils.getCurrentUserName(), ident, e)); + throw e; + } + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewEvent.java new file mode 100644 index 00000000000..d9c73ca3310 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewEvent.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; +import org.apache.gravitino.listener.api.info.ViewInfo; +import org.apache.gravitino.rel.ViewChange; + +/** Successful alter-view event. */ +@DeveloperApi +public final class AlterViewEvent extends ViewEvent { + private final ViewInfo updatedViewInfo; + private final ViewChange[] viewChanges; + + public AlterViewEvent( + String user, NameIdentifier identifier, ViewChange[] viewChanges, ViewInfo updatedViewInfo) { + super(user, identifier); + this.viewChanges = viewChanges.clone(); + this.updatedViewInfo = updatedViewInfo; + } + + public ViewInfo updatedViewInfo() { + return updatedViewInfo; + } + + public ViewChange[] viewChanges() { + return viewChanges; + } + + @Override + public OperationType operationType() { + return OperationType.ALTER_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewFailureEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewFailureEvent.java new file mode 100644 index 00000000000..0a8a6d9e740 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewFailureEvent.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; +import org.apache.gravitino.rel.ViewChange; + +/** Failure event for altering a view. */ +@DeveloperApi +public final class AlterViewFailureEvent extends ViewFailureEvent { + private final ViewChange[] viewChanges; + + public AlterViewFailureEvent( + String user, NameIdentifier identifier, Exception exception, ViewChange[] viewChanges) { + super(user, identifier, exception); + this.viewChanges = viewChanges.clone(); + } + + public ViewChange[] viewChanges() { + return viewChanges; + } + + @Override + public OperationType operationType() { + return OperationType.ALTER_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewPreEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewPreEvent.java new file mode 100644 index 00000000000..022ad33026d --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewPreEvent.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; +import org.apache.gravitino.rel.ViewChange; + +/** Pre-event before altering a view. */ +@DeveloperApi +public class AlterViewPreEvent extends ViewPreEvent { + private final ViewChange[] viewChanges; + + public AlterViewPreEvent(String user, NameIdentifier identifier, ViewChange[] viewChanges) { + super(user, identifier); + this.viewChanges = viewChanges; + } + + public ViewChange[] viewChanges() { + return viewChanges; + } + + @Override + public OperationType operationType() { + return OperationType.ALTER_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewEvent.java new file mode 100644 index 00000000000..df6f490db4a --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewEvent.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; +import org.apache.gravitino.listener.api.info.ViewInfo; + +/** Successful create-view event. */ +@DeveloperApi +public final class CreateViewEvent extends ViewEvent { + private final ViewInfo createdViewInfo; + + public CreateViewEvent(String user, NameIdentifier identifier, ViewInfo createdViewInfo) { + super(user, identifier); + this.createdViewInfo = createdViewInfo; + } + + public ViewInfo createdViewInfo() { + return createdViewInfo; + } + + @Override + public OperationType operationType() { + return OperationType.CREATE_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewFailureEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewFailureEvent.java new file mode 100644 index 00000000000..e40f0fa3256 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewFailureEvent.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; +import org.apache.gravitino.listener.api.info.ViewInfo; + +/** Failure event for creating a view. */ +@DeveloperApi +public final class CreateViewFailureEvent extends ViewFailureEvent { + private final ViewInfo createViewRequest; + + public CreateViewFailureEvent( + String user, NameIdentifier identifier, Exception exception, ViewInfo createViewRequest) { + super(user, identifier, exception); + this.createViewRequest = createViewRequest; + } + + public ViewInfo createViewRequest() { + return createViewRequest; + } + + @Override + public OperationType operationType() { + return OperationType.CREATE_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewPreEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewPreEvent.java new file mode 100644 index 00000000000..16599a57a81 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewPreEvent.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; +import org.apache.gravitino.listener.api.info.ViewInfo; + +/** Pre-event before creating a view. */ +@DeveloperApi +public class CreateViewPreEvent extends ViewPreEvent { + private final ViewInfo createViewRequest; + + public CreateViewPreEvent(String user, NameIdentifier identifier, ViewInfo createViewRequest) { + super(user, identifier); + this.createViewRequest = createViewRequest; + } + + public ViewInfo createViewRequest() { + return createViewRequest; + } + + @Override + public OperationType operationType() { + return OperationType.CREATE_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewEvent.java new file mode 100644 index 00000000000..0772f4323a3 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewEvent.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** Successful drop-view event. */ +@DeveloperApi +public final class DropViewEvent extends ViewEvent { + private final boolean isExists; + + public DropViewEvent(String user, NameIdentifier identifier, boolean isExists) { + super(user, identifier); + this.isExists = isExists; + } + + public boolean isExists() { + return isExists; + } + + @Override + public OperationType operationType() { + return OperationType.DROP_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewFailureEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewFailureEvent.java new file mode 100644 index 00000000000..07ec06f0291 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewFailureEvent.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** Failure event for dropping a view. */ +@DeveloperApi +public final class DropViewFailureEvent extends ViewFailureEvent { + public DropViewFailureEvent(String user, NameIdentifier identifier, Exception exception) { + super(user, identifier, exception); + } + + @Override + public OperationType operationType() { + return OperationType.DROP_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewPreEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewPreEvent.java new file mode 100644 index 00000000000..3acb2fe4338 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewPreEvent.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** Pre-event before dropping a view. */ +@DeveloperApi +public class DropViewPreEvent extends ViewPreEvent { + public DropViewPreEvent(String user, NameIdentifier identifier) { + super(user, identifier); + } + + @Override + public OperationType operationType() { + return OperationType.DROP_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewEvent.java new file mode 100644 index 00000000000..a4afbb59ebf --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewEvent.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** + * Successful list-views event. Like {@link org.apache.gravitino.listener.api.event.ListTableEvent}, + * listed identifiers are not stored on the event. + */ +@DeveloperApi +public final class ListViewEvent extends ViewEvent { + private final Namespace namespace; + + public ListViewEvent(String user, Namespace namespace) { + super(user, NameIdentifier.of(namespace.levels())); + this.namespace = namespace; + } + + public Namespace namespace() { + return namespace; + } + + @Override + public OperationType operationType() { + return OperationType.LIST_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewFailureEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewFailureEvent.java new file mode 100644 index 00000000000..bf4b482dd60 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewFailureEvent.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** Failure event for listing views. */ +@DeveloperApi +public final class ListViewFailureEvent extends ViewFailureEvent { + private final Namespace namespace; + + public ListViewFailureEvent(String user, Namespace namespace, Exception exception) { + super(user, NameIdentifier.of(namespace.levels()), exception); + this.namespace = namespace; + } + + public Namespace namespace() { + return namespace; + } + + @Override + public OperationType operationType() { + return OperationType.LIST_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewPreEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewPreEvent.java new file mode 100644 index 00000000000..3bd2acdf915 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewPreEvent.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** Pre-event before listing views in a namespace. */ +@DeveloperApi +public class ListViewPreEvent extends ViewPreEvent { + private final Namespace namespace; + + public ListViewPreEvent(String user, Namespace namespace) { + super(user, NameIdentifier.of(namespace.levels())); + this.namespace = namespace; + } + + public Namespace namespace() { + return namespace; + } + + @Override + public OperationType operationType() { + return OperationType.LIST_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewEvent.java new file mode 100644 index 00000000000..a8120131c93 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewEvent.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; +import org.apache.gravitino.listener.api.info.ViewInfo; + +/** Successful load-view event. */ +@DeveloperApi +public final class LoadViewEvent extends ViewEvent { + private final ViewInfo loadedViewInfo; + + public LoadViewEvent(String user, NameIdentifier identifier, ViewInfo loadedViewInfo) { + super(user, identifier); + this.loadedViewInfo = loadedViewInfo; + } + + /** + * Returns the loaded view information. + * + * @return the loaded view information + */ + public ViewInfo loadedViewInfo() { + return loadedViewInfo; + } + + @Override + public OperationType operationType() { + return OperationType.LOAD_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewFailureEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewFailureEvent.java new file mode 100644 index 00000000000..100a978dce0 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewFailureEvent.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** Failure event for loading a view. */ +@DeveloperApi +public final class LoadViewFailureEvent extends ViewFailureEvent { + public LoadViewFailureEvent(String user, NameIdentifier identifier, Exception exception) { + super(user, identifier, exception); + } + + @Override + public OperationType operationType() { + return OperationType.LOAD_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewPreEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewPreEvent.java new file mode 100644 index 00000000000..3f4ca917462 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewPreEvent.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.OperationType; + +/** Pre-event before loading a view. */ +@DeveloperApi +public class LoadViewPreEvent extends ViewPreEvent { + public LoadViewPreEvent(String user, NameIdentifier identifier) { + super(user, identifier); + } + + @Override + public OperationType operationType() { + return OperationType.LOAD_VIEW; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewEvent.java new file mode 100644 index 00000000000..8fcdc1e2792 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewEvent.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.Event; +import org.apache.gravitino.listener.api.event.OperationStatus; + +/** Base class for successful view operation events. */ +@DeveloperApi +public abstract class ViewEvent extends Event { + + protected ViewEvent(String user, NameIdentifier identifier) { + super(user, identifier); + } + + @Override + public OperationStatus operationStatus() { + return OperationStatus.SUCCESS; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewFailureEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewFailureEvent.java new file mode 100644 index 00000000000..b14ce130e80 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewFailureEvent.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.FailureEvent; + +/** Base class for view operation failure events. */ +@DeveloperApi +public abstract class ViewFailureEvent extends FailureEvent { + + protected ViewFailureEvent(String user, NameIdentifier identifier, Exception exception) { + super(user, identifier, exception); + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewPreEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewPreEvent.java new file mode 100644 index 00000000000..0d0a64da45c --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewPreEvent.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event.view; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.listener.api.event.PreEvent; + +/** Pre-event base class for view operations. */ +@DeveloperApi +public abstract class ViewPreEvent extends PreEvent { + protected ViewPreEvent(String user, NameIdentifier identifier) { + super(user, identifier); + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/info/ViewInfo.java b/core/src/main/java/org/apache/gravitino/listener/api/info/ViewInfo.java new file mode 100644 index 00000000000..e9b31e843f9 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/info/ViewInfo.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.info; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.gravitino.Audit; +import org.apache.gravitino.annotation.DeveloperApi; +import org.apache.gravitino.rel.Column; +import org.apache.gravitino.rel.Representation; +import org.apache.gravitino.rel.View; + +/** + * ViewInfo exposes view information for event listeners; it is read-only. Most fields are shallow + * copies internally, not deep copies, for performance. + */ +@DeveloperApi +public final class ViewInfo { + private final String name; + private final Column[] columns; + @Nullable private final String comment; + private final Representation[] representations; + @Nullable private final String defaultCatalog; + @Nullable private final String defaultSchema; + private final Map properties; + @Nullable private final Audit auditInfo; + + /** Constructs a ViewInfo from a {@link View}. */ + public ViewInfo(View view) { + this( + view.name(), + view.columns(), + view.comment(), + view.representations(), + view.defaultCatalog(), + view.defaultSchema(), + view.properties(), + view.auditInfo()); + } + + /** + * Constructs ViewInfo with the given fields. + * + * @param name View name + * @param columns Output columns + * @param comment Optional comment + * @param representations View representations (at least one in a valid create request) + * @param defaultCatalog Optional default catalog for the definition + * @param defaultSchema Optional default schema for the definition + * @param properties View properties; copied defensively + * @param auditInfo Optional audit information + */ + public ViewInfo( + String name, + Column[] columns, + @Nullable String comment, + Representation[] representations, + @Nullable String defaultCatalog, + @Nullable String defaultSchema, + Map properties, + @Nullable Audit auditInfo) { + this.name = name; + this.columns = columns == null ? new Column[0] : columns.clone(); + this.comment = comment; + this.representations = + representations == null ? new Representation[0] : representations.clone(); + this.defaultCatalog = defaultCatalog; + this.defaultSchema = defaultSchema; + this.properties = properties == null ? ImmutableMap.of() : ImmutableMap.copyOf(properties); + this.auditInfo = auditInfo; + } + + /** Returns the view name. */ + public String name() { + return name; + } + + /** Returns the output columns of the view. */ + public Column[] columns() { + return columns; + } + + /** Returns the optional comment for the view. */ + @Nullable + public String comment() { + return comment; + } + + /** Returns the view representations. */ + public Representation[] representations() { + return representations; + } + + /** Returns the optional default catalog used by the view definition. */ + @Nullable + public String defaultCatalog() { + return defaultCatalog; + } + + /** Returns the optional default schema used by the view definition. */ + @Nullable + public String defaultSchema() { + return defaultSchema; + } + + /** Returns the view properties. */ + public Map properties() { + return properties; + } + + /** Returns the optional audit information for the view. */ + @Nullable + public Audit auditInfo() { + return auditInfo; + } +} diff --git a/core/src/test/java/org/apache/gravitino/audit/TestOperation.java b/core/src/test/java/org/apache/gravitino/audit/TestOperation.java index 3a9bedb9832..2a7032e45e9 100644 --- a/core/src/test/java/org/apache/gravitino/audit/TestOperation.java +++ b/core/src/test/java/org/apache/gravitino/audit/TestOperation.java @@ -107,17 +107,31 @@ import org.apache.gravitino.listener.api.event.PurgePartitionEvent; import org.apache.gravitino.listener.api.event.PurgePartitionFailureEvent; import org.apache.gravitino.listener.api.event.PurgeTableEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.DropViewEvent; +import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.ListViewEvent; +import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent; import org.apache.gravitino.listener.api.info.CatalogInfo; import org.apache.gravitino.listener.api.info.FilesetInfo; import org.apache.gravitino.listener.api.info.MetalakeInfo; import org.apache.gravitino.listener.api.info.SchemaInfo; import org.apache.gravitino.listener.api.info.TableInfo; import org.apache.gravitino.listener.api.info.TopicInfo; +import org.apache.gravitino.listener.api.info.ViewInfo; import org.apache.gravitino.listener.api.info.partitions.IdentityPartitionInfo; import org.apache.gravitino.listener.api.info.partitions.PartitionInfo; import org.apache.gravitino.messaging.TopicChange; import org.apache.gravitino.rel.Column; +import org.apache.gravitino.rel.Representation; +import org.apache.gravitino.rel.SQLRepresentation; import org.apache.gravitino.rel.TableChange; +import org.apache.gravitino.rel.ViewChange; import org.apache.gravitino.rel.expressions.NamedReference; import org.apache.gravitino.rel.expressions.distributions.Distributions; import org.apache.gravitino.rel.expressions.distributions.Strategy; @@ -162,6 +176,10 @@ public class TestOperation { private TopicInfo topicInfo; + private NameIdentifier viewIdentifier; + + private ViewInfo viewInfo; + private NameIdentifier partitionIdentifier; private PartitionInfo partitionInfo; @@ -187,6 +205,9 @@ public void init() { this.topicIdentifier = mockTopicIdentifier(); this.topicInfo = mockTopicInfo(); + this.viewIdentifier = mockViewIdentifier(); + this.viewInfo = mockViewInfo(); + this.filesetIdentifier = mockFilesetIdentifier(); this.filesetInfo = mockFilesetInfo(); @@ -243,6 +264,14 @@ public void testCreateOperation() { new CreateTopicFailureEvent(USER, topicIdentifier, new Exception(), topicInfo); Assertions.assertEquals( AuditLog.Operation.fromEvent(createTopicFailureEvent), AuditLog.Operation.CREATE_TOPIC); + + Event createViewEvent = new CreateViewEvent(USER, viewIdentifier, viewInfo); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(createViewEvent), AuditLog.Operation.CREATE_VIEW); + Event createViewFailureEvent = + new CreateViewFailureEvent(USER, viewIdentifier, new Exception(), viewInfo); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(createViewFailureEvent), AuditLog.Operation.CREATE_VIEW); } @Test @@ -303,6 +332,14 @@ public void testAlterOperation() { new AlterTopicFailureEvent(USER, topicIdentifier, new Exception(), new TopicChange[] {}); Assertions.assertEquals( AuditLog.Operation.fromEvent(alterTopicFailureEvent), AuditLog.Operation.ALTER_TOPIC); + + Event alterViewEvent = new AlterViewEvent(USER, viewIdentifier, new ViewChange[] {}, viewInfo); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(alterViewEvent), AuditLog.Operation.ALTER_VIEW); + Event alterViewFailureEvent = + new AlterViewFailureEvent(USER, viewIdentifier, new Exception(), new ViewChange[] {}); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(alterViewFailureEvent), AuditLog.Operation.ALTER_VIEW); } @Test @@ -351,6 +388,13 @@ public void testDropOperation() { Event dropTopicFailureEvent = new DropTopicFailureEvent(USER, topicIdentifier, new Exception()); Assertions.assertEquals( AuditLog.Operation.fromEvent(dropTopicFailureEvent), AuditLog.Operation.DROP_TOPIC); + + Event dropViewEvent = new DropViewEvent(USER, viewIdentifier, true); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(dropViewEvent), AuditLog.Operation.DROP_VIEW); + Event dropViewFailureEvent = new DropViewFailureEvent(USER, viewIdentifier, new Exception()); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(dropViewFailureEvent), AuditLog.Operation.DROP_VIEW); } @Test @@ -426,6 +470,14 @@ public void testListOperation() { Assertions.assertEquals( AuditLog.Operation.fromEvent(listTopicFailureEvent), AuditLog.Operation.LIST_TOPIC); + Namespace viewNamespace = Namespace.of("metalake", "catalog", "schema"); + Event listViewEvent = new ListViewEvent(USER, viewNamespace); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(listViewEvent), AuditLog.Operation.LIST_VIEW); + Event listViewFailureEvent = new ListViewFailureEvent(USER, viewNamespace, new Exception()); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(listViewFailureEvent), AuditLog.Operation.LIST_VIEW); + Event listFilesetEvent = new ListFilesetEvent(USER, namespace); Assertions.assertEquals( AuditLog.Operation.fromEvent(listFilesetEvent), AuditLog.Operation.LIST_FILESET); @@ -489,6 +541,13 @@ public void testLoadOperation() { Event loadTopicFailureEvent = new LoadTopicFailureEvent(USER, topicIdentifier, new Exception()); Assertions.assertEquals( AuditLog.Operation.fromEvent(loadTopicFailureEvent), AuditLog.Operation.LOAD_TOPIC); + + Event loadViewEvent = new LoadViewEvent(USER, viewIdentifier, viewInfo); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(loadViewEvent), AuditLog.Operation.LOAD_VIEW); + Event loadViewFailureEvent = new LoadViewFailureEvent(USER, viewIdentifier, new Exception()); + Assertions.assertEquals( + AuditLog.Operation.fromEvent(loadViewFailureEvent), AuditLog.Operation.LOAD_VIEW); } @Test @@ -602,6 +661,24 @@ private TopicInfo mockTopicInfo() { return new TopicInfo("topic", "comment", ImmutableMap.of("a", "b"), null); } + private NameIdentifier mockViewIdentifier() { + return NameIdentifier.of("metalake", "catalog", "schema", "view"); + } + + private ViewInfo mockViewInfo() { + return new ViewInfo( + "view", + new Column[] {Column.of("a", Types.IntegerType.get())}, + "comment", + new Representation[] { + SQLRepresentation.builder().withDialect("trino").withSql("SELECT 1").build() + }, + "dc", + "ds", + ImmutableMap.of("a", "b"), + null); + } + private NameIdentifier mockPartitionIdentifier() { return NameIdentifier.of("metalake", "catalog", "schema", "table", PARTITION_NAME); } diff --git a/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java b/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java index 3071e23f9a6..402fb595258 100644 --- a/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java +++ b/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java @@ -253,6 +253,12 @@ void testGetMetadataObjectLocation() throws IllegalAccessException { NameIdentifier.of("catalog", "schema", "fileset"), Entity.EntityType.TABLE); Assertions.assertEquals(1, locations.size()); Assertions.assertEquals("gs://bucket/1", locations.get(0)); + + Mockito.clearInvocations(catalogDispatcher, tableDispatcher); + locations = + AuthorizationUtils.getMetadataObjectLocation( + NameIdentifier.of("metalake", "catalog", "schema", "view"), Entity.EntityType.VIEW); + Assertions.assertTrue(locations.isEmpty()); } @Test diff --git a/core/src/test/java/org/apache/gravitino/listener/api/event/TestViewEvent.java b/core/src/test/java/org/apache/gravitino/listener/api/event/TestViewEvent.java new file mode 100644 index 00000000000..1d9529ce22c --- /dev/null +++ b/core/src/test/java/org/apache/gravitino/listener/api/event/TestViewEvent.java @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.gravitino.listener.api.event; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.Map; +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; +import org.apache.gravitino.catalog.ViewDispatcher; +import org.apache.gravitino.exceptions.GravitinoRuntimeException; +import org.apache.gravitino.listener.DummyEventListener; +import org.apache.gravitino.listener.EventBus; +import org.apache.gravitino.listener.ViewEventDispatcher; +import org.apache.gravitino.listener.api.event.view.AlterViewEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.AlterViewPreEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.CreateViewPreEvent; +import org.apache.gravitino.listener.api.event.view.DropViewEvent; +import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.DropViewPreEvent; +import org.apache.gravitino.listener.api.event.view.ListViewEvent; +import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.ListViewPreEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent; +import org.apache.gravitino.listener.api.event.view.LoadViewPreEvent; +import org.apache.gravitino.listener.api.info.ViewInfo; +import org.apache.gravitino.rel.Column; +import org.apache.gravitino.rel.Representation; +import org.apache.gravitino.rel.SQLRepresentation; +import org.apache.gravitino.rel.View; +import org.apache.gravitino.rel.ViewChange; +import org.apache.gravitino.rel.types.Types; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +@TestInstance(Lifecycle.PER_CLASS) +public class TestViewEvent { + private ViewEventDispatcher dispatcher; + private ViewEventDispatcher failureDispatcher; + private DummyEventListener dummyEventListener; + private View view; + + @BeforeAll + void init() { + this.view = mockView(); + this.dummyEventListener = new DummyEventListener(); + EventBus eventBus = new EventBus(Arrays.asList(dummyEventListener)); + ViewDispatcher viewDispatcher = mockViewDispatcher(); + this.dispatcher = new ViewEventDispatcher(eventBus, viewDispatcher); + ViewDispatcher exceptionDispatcher = mockExceptionViewDispatcher(); + this.failureDispatcher = new ViewEventDispatcher(eventBus, exceptionDispatcher); + } + + @Test + void testCreateViewEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + dispatcher.createView( + identifier, + view.comment(), + view.columns(), + view.representations(), + view.defaultCatalog(), + view.defaultSchema(), + view.properties()); + + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(CreateViewEvent.class, event.getClass()); + checkViewInfo(((CreateViewEvent) event).createdViewInfo(), view); + Assertions.assertEquals(OperationType.CREATE_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus()); + + PreEvent preEvent = dummyEventListener.popPreEvent(); + Assertions.assertEquals(identifier, preEvent.identifier()); + Assertions.assertEquals(CreateViewPreEvent.class, preEvent.getClass()); + checkViewInfo(((CreateViewPreEvent) preEvent).createViewRequest(), view); + Assertions.assertEquals(OperationType.CREATE_VIEW, preEvent.operationType()); + Assertions.assertEquals(OperationStatus.UNPROCESSED, preEvent.operationStatus()); + } + + @Test + void testLoadViewEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + dispatcher.loadView(identifier); + + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(LoadViewEvent.class, event.getClass()); + checkViewInfo(((LoadViewEvent) event).loadedViewInfo(), view); + Assertions.assertEquals(OperationType.LOAD_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus()); + + PreEvent preEvent = dummyEventListener.popPreEvent(); + Assertions.assertEquals(identifier, preEvent.identifier()); + Assertions.assertEquals(LoadViewPreEvent.class, preEvent.getClass()); + Assertions.assertEquals(OperationType.LOAD_VIEW, preEvent.operationType()); + Assertions.assertEquals(OperationStatus.UNPROCESSED, preEvent.operationStatus()); + } + + @Test + void testAlterViewEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + ViewChange change = ViewChange.setProperty("a", "b"); + dispatcher.alterView(identifier, change); + + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(AlterViewEvent.class, event.getClass()); + checkViewInfo(((AlterViewEvent) event).updatedViewInfo(), view); + Assertions.assertEquals(1, ((AlterViewEvent) event).viewChanges().length); + Assertions.assertEquals(change, ((AlterViewEvent) event).viewChanges()[0]); + Assertions.assertEquals(OperationType.ALTER_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus()); + + PreEvent preEvent = dummyEventListener.popPreEvent(); + Assertions.assertEquals(identifier, preEvent.identifier()); + Assertions.assertEquals(AlterViewPreEvent.class, preEvent.getClass()); + Assertions.assertEquals(1, ((AlterViewPreEvent) preEvent).viewChanges().length); + Assertions.assertEquals(change, ((AlterViewPreEvent) preEvent).viewChanges()[0]); + Assertions.assertEquals(OperationType.ALTER_VIEW, preEvent.operationType()); + Assertions.assertEquals(OperationStatus.UNPROCESSED, preEvent.operationStatus()); + } + + @Test + void testDropViewEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + dispatcher.dropView(identifier); + + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(DropViewEvent.class, event.getClass()); + Assertions.assertTrue(((DropViewEvent) event).isExists()); + Assertions.assertEquals(OperationType.DROP_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus()); + + PreEvent preEvent = dummyEventListener.popPreEvent(); + Assertions.assertEquals(identifier, preEvent.identifier()); + Assertions.assertEquals(DropViewPreEvent.class, preEvent.getClass()); + Assertions.assertEquals(OperationType.DROP_VIEW, preEvent.operationType()); + Assertions.assertEquals(OperationStatus.UNPROCESSED, preEvent.operationStatus()); + } + + @Test + void testListViewEvent() { + Namespace namespace = Namespace.of("metalake", "catalog", "schema"); + dispatcher.listViews(namespace); + + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(namespace.toString(), event.identifier().toString()); + Assertions.assertEquals(ListViewEvent.class, event.getClass()); + Assertions.assertEquals(namespace, ((ListViewEvent) event).namespace()); + Assertions.assertEquals(OperationType.LIST_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus()); + + PreEvent preEvent = dummyEventListener.popPreEvent(); + Assertions.assertEquals(namespace.toString(), preEvent.identifier().toString()); + Assertions.assertEquals(ListViewPreEvent.class, preEvent.getClass()); + Assertions.assertEquals(namespace, ((ListViewPreEvent) preEvent).namespace()); + Assertions.assertEquals(OperationType.LIST_VIEW, preEvent.operationType()); + Assertions.assertEquals(OperationStatus.UNPROCESSED, preEvent.operationStatus()); + } + + @Test + void testCreateViewFailureEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + Assertions.assertThrowsExactly( + GravitinoRuntimeException.class, + () -> + failureDispatcher.createView( + identifier, + view.comment(), + view.columns(), + view.representations(), + view.defaultCatalog(), + view.defaultSchema(), + view.properties())); + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(CreateViewFailureEvent.class, event.getClass()); + Assertions.assertEquals( + GravitinoRuntimeException.class, ((CreateViewFailureEvent) event).exception().getClass()); + checkViewInfo(((CreateViewFailureEvent) event).createViewRequest(), view); + Assertions.assertEquals(OperationType.CREATE_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus()); + } + + @Test + void testLoadViewFailureEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + Assertions.assertThrowsExactly( + GravitinoRuntimeException.class, () -> failureDispatcher.loadView(identifier)); + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(LoadViewFailureEvent.class, event.getClass()); + Assertions.assertEquals( + GravitinoRuntimeException.class, ((LoadViewFailureEvent) event).exception().getClass()); + Assertions.assertEquals(OperationType.LOAD_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus()); + } + + @Test + void testAlterViewFailureEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + ViewChange change = ViewChange.setProperty("a", "b"); + Assertions.assertThrowsExactly( + GravitinoRuntimeException.class, () -> failureDispatcher.alterView(identifier, change)); + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(AlterViewFailureEvent.class, event.getClass()); + Assertions.assertEquals( + GravitinoRuntimeException.class, ((AlterViewFailureEvent) event).exception().getClass()); + Assertions.assertEquals(1, ((AlterViewFailureEvent) event).viewChanges().length); + Assertions.assertEquals(change, ((AlterViewFailureEvent) event).viewChanges()[0]); + Assertions.assertEquals(OperationType.ALTER_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus()); + } + + @Test + void testDropViewFailureEvent() { + NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", "schema", view.name()); + Assertions.assertThrowsExactly( + GravitinoRuntimeException.class, () -> failureDispatcher.dropView(identifier)); + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(identifier, event.identifier()); + Assertions.assertEquals(DropViewFailureEvent.class, event.getClass()); + Assertions.assertEquals( + GravitinoRuntimeException.class, ((DropViewFailureEvent) event).exception().getClass()); + Assertions.assertEquals(OperationType.DROP_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus()); + } + + @Test + void testListViewFailureEvent() { + Namespace namespace = Namespace.of("metalake", "catalog", "schema"); + Assertions.assertThrowsExactly( + GravitinoRuntimeException.class, () -> failureDispatcher.listViews(namespace)); + Event event = dummyEventListener.popPostEvent(); + Assertions.assertEquals(namespace.toString(), event.identifier().toString()); + Assertions.assertEquals(ListViewFailureEvent.class, event.getClass()); + Assertions.assertEquals( + GravitinoRuntimeException.class, ((ListViewFailureEvent) event).exception().getClass()); + Assertions.assertEquals(namespace, ((ListViewFailureEvent) event).namespace()); + Assertions.assertEquals(OperationType.LIST_VIEW, event.operationType()); + Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus()); + } + + private void checkViewInfo(ViewInfo viewInfo, View view) { + Assertions.assertEquals(view.name(), viewInfo.name()); + Assertions.assertEquals(view.comment(), viewInfo.comment()); + Assertions.assertEquals(view.defaultCatalog(), viewInfo.defaultCatalog()); + Assertions.assertEquals(view.defaultSchema(), viewInfo.defaultSchema()); + Assertions.assertEquals(view.properties(), viewInfo.properties()); + Assertions.assertArrayEquals(view.columns(), viewInfo.columns()); + Assertions.assertArrayEquals(view.representations(), viewInfo.representations()); + Assertions.assertEquals(view.auditInfo(), viewInfo.auditInfo()); + } + + private View mockView() { + View v = mock(View.class); + Representation rep = + SQLRepresentation.builder().withDialect("trino").withSql("SELECT 1").build(); + when(v.name()).thenReturn("view"); + when(v.comment()).thenReturn("comment"); + when(v.properties()).thenReturn(ImmutableMap.of("a", "b")); + when(v.columns()).thenReturn(new Column[] {Column.of("a", Types.IntegerType.get())}); + when(v.representations()).thenReturn(new Representation[] {rep}); + when(v.defaultCatalog()).thenReturn("dc"); + when(v.defaultSchema()).thenReturn("ds"); + when(v.auditInfo()).thenReturn(null); + return v; + } + + private ViewDispatcher mockViewDispatcher() { + ViewDispatcher d = mock(ViewDispatcher.class); + when(d.createView( + any(NameIdentifier.class), + any(), + any(Column[].class), + any(Representation[].class), + any(), + any(), + any(Map.class))) + .thenReturn(view); + when(d.loadView(any(NameIdentifier.class))).thenReturn(view); + when(d.dropView(any(NameIdentifier.class))).thenReturn(true); + when(d.listViews(any(Namespace.class))).thenReturn(null); + when(d.alterView(any(NameIdentifier.class), any(ViewChange.class))).thenReturn(view); + return d; + } + + private ViewDispatcher mockExceptionViewDispatcher() { + return mock( + ViewDispatcher.class, + invocation -> { + throw new GravitinoRuntimeException("Exception for all methods"); + }); + } +}