This is an automated email from the ASF dual-hosted git repository. jmclean 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 e62c6d37a [#5528]Improvement[cli] Add and delete multiple tags at once in the Gravitino CLI (#5641) e62c6d37a is described below commit e62c6d37a4e52562dc5e512e780ccd4f3fb7bb14 Author: Xiaojian Sun <sunxiaojian...@163.com> AuthorDate: Tue Nov 26 07:06:20 2024 +0800 [#5528]Improvement[cli] Add and delete multiple tags at once in the Gravitino CLI (#5641) ### What changes were proposed in this pull request? Add and delete multiple tags at once in the Gravitino CLI ### Why are the changes needed? Close: [(#5528)](https://github.com/apache/gravitino/issues/5528) --- .../org/apache/gravitino/cli/ErrorMessages.java | 3 + .../apache/gravitino/cli/GravitinoCommandLine.java | 31 ++-- .../org/apache/gravitino/cli/GravitinoOptions.java | 7 +- .../apache/gravitino/cli/TestableCommandLine.java | 18 +- .../apache/gravitino/cli/commands/CreateTag.java | 56 +++++- .../apache/gravitino/cli/commands/DeleteTag.java | 63 ++++++- .../apache/gravitino/cli/commands/TagEntity.java | 23 +-- .../apache/gravitino/cli/commands/UntagEntity.java | 25 +-- .../org/apache/gravitino/cli/TestTagCommands.java | 203 +++++++++++++++++++-- docs/cli.md | 16 +- 10 files changed, 357 insertions(+), 88 deletions(-) diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java index 0ad750f2c..6836bd203 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java @@ -38,7 +38,10 @@ public class ErrorMessages { public static final String UNKNOWN_GROUP = "Unknown group."; public static final String GROUP_EXISTS = "Group already exists."; public static final String UNKNOWN_TAG = "Unknown tag."; + public static final String MULTIPLE_TAG_COMMAND_ERROR = + "Error: The current command only supports one --tag option."; public static final String TAG_EXISTS = "Tag already exists."; + public static final String TAG_EMPTY = "Error: Must configure --tag option."; public static final String UNKNOWN_ROLE = "Unknown role."; public static final String ROLE_EXISTS = "Role already exists."; public static final String INVALID_SET_COMMAND = diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java index c25b2f7e7..14e2cd20e 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java @@ -19,6 +19,8 @@ package org.apache.gravitino.cli; +import com.google.common.base.Preconditions; +import java.util.Arrays; import java.util.Map; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; @@ -366,10 +368,11 @@ public class GravitinoCommandLine extends TestableCommandLine { String url = getUrl(); FullName name = new FullName(line); String metalake = name.getMetalakeName(); - String tag = line.getOptionValue(GravitinoOptions.TAG); + String[] tags = line.getOptionValues(GravitinoOptions.TAG); + tags = tags != null ? Arrays.stream(tags).distinct().toArray(String[]::new) : null; if (CommandActions.DETAILS.equals(command)) { - newTagDetails(url, ignore, metalake, tag).handle(); + newTagDetails(url, ignore, metalake, getOneTag(tags)).handle(); } else if (CommandActions.LIST.equals(command)) { if (!name.hasCatalogName()) { newListTags(url, ignore, metalake).handle(); @@ -378,40 +381,44 @@ public class GravitinoCommandLine extends TestableCommandLine { } } else if (CommandActions.CREATE.equals(command)) { String comment = line.getOptionValue(GravitinoOptions.COMMENT); - newCreateTag(url, ignore, metalake, tag, comment).handle(); + newCreateTags(url, ignore, metalake, tags, comment).handle(); } else if (CommandActions.DELETE.equals(command)) { boolean force = line.hasOption(GravitinoOptions.FORCE); - newDeleteTag(url, ignore, force, metalake, tag).handle(); + newDeleteTag(url, ignore, force, metalake, tags).handle(); } else if (CommandActions.SET.equals(command)) { String property = line.getOptionValue(GravitinoOptions.PROPERTY); String value = line.getOptionValue(GravitinoOptions.VALUE); - if (property != null && value != null) { - newSetTagProperty(url, ignore, metalake, tag, property, value).handle(); + newSetTagProperty(url, ignore, metalake, getOneTag(tags), property, value).handle(); } else if (name != null && property == null && value == null) { - newTagEntity(url, ignore, metalake, name, tag).handle(); + newTagEntity(url, ignore, metalake, name, tags).handle(); } } else if (CommandActions.REMOVE.equals(command)) { String property = line.getOptionValue(GravitinoOptions.PROPERTY); if (property != null) { - newRemoveTagProperty(url, ignore, metalake, tag, property).handle(); + newRemoveTagProperty(url, ignore, metalake, getOneTag(tags), property).handle(); } else { - newUntagEntity(url, ignore, metalake, name, tag).handle(); + newUntagEntity(url, ignore, metalake, name, tags).handle(); } } else if (CommandActions.PROPERTIES.equals(command)) { - newListTagProperties(url, ignore, metalake, tag).handle(); + newListTagProperties(url, ignore, metalake, getOneTag(tags)).handle(); } else if (CommandActions.UPDATE.equals(command)) { if (line.hasOption(GravitinoOptions.COMMENT)) { String comment = line.getOptionValue(GravitinoOptions.COMMENT); - newUpdateTagComment(url, ignore, metalake, tag, comment).handle(); + newUpdateTagComment(url, ignore, metalake, getOneTag(tags), comment).handle(); } if (line.hasOption(GravitinoOptions.RENAME)) { String newName = line.getOptionValue(GravitinoOptions.RENAME); - newUpdateTagName(url, ignore, metalake, tag, newName).handle(); + newUpdateTagName(url, ignore, metalake, getOneTag(tags), newName).handle(); } } } + private String getOneTag(String[] tags) { + Preconditions.checkArgument(tags.length <= 1, ErrorMessages.MULTIPLE_TAG_COMMAND_ERROR); + return tags[0]; + } + /** Handles the command execution for Roles based on command type and the command line options. */ protected void handleRoleCommand() { String url = getUrl(); diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java index a6afdd589..8f017ba72 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java @@ -78,7 +78,7 @@ public class GravitinoOptions { "z", PROVIDER, "provider one of hadoop, hive, mysql, postgres, iceberg, kafka")); options.addOption(createArgOption("l", USER, "user name")); options.addOption(createArgOption("g", GROUP, "group name")); - options.addOption(createArgOption("t", TAG, "tag name")); + options.addOption(createArgsOption("t", TAG, "tag name")); options.addOption(createArgOption("r", ROLE, "role name")); // Properties option can have multiple values @@ -115,4 +115,9 @@ public class GravitinoOptions { public Option createArgOption(String shortName, String longName, String description) { return new Option(shortName, longName, true, description); } + + public Option createArgsOption(String shortName, String longName, String description) { + // Support multiple arguments + return Option.builder().option(shortName).longOpt(longName).hasArgs().desc(description).build(); + } } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java index 85bfa3203..9a4882b3a 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java @@ -370,14 +370,14 @@ public class TestableCommandLine { return new ListAllTags(url, ignore, metalake); } - protected CreateTag newCreateTag( - String url, boolean ignore, String metalake, String tag, String comment) { - return new CreateTag(url, ignore, metalake, tag, comment); + protected CreateTag newCreateTags( + String url, boolean ignore, String metalake, String[] tags, String comment) { + return new CreateTag(url, ignore, metalake, tags, comment); } protected DeleteTag newDeleteTag( - String url, boolean ignore, boolean force, String metalake, String tag) { - return new DeleteTag(url, ignore, force, metalake, tag); + String url, boolean ignore, boolean force, String metalake, String[] tags) { + return new DeleteTag(url, ignore, force, metalake, tags); } protected SetTagProperty newSetTagProperty( @@ -411,13 +411,13 @@ public class TestableCommandLine { } protected TagEntity newTagEntity( - String url, boolean ignore, String metalake, FullName name, String tag) { - return new TagEntity(url, ignore, metalake, name, tag); + String url, boolean ignore, String metalake, FullName name, String[] tags) { + return new TagEntity(url, ignore, metalake, name, tags); } protected UntagEntity newUntagEntity( - String url, boolean ignore, String metalake, FullName name, String tag) { - return new UntagEntity(url, ignore, metalake, name, tag); + String url, boolean ignore, String metalake, FullName name, String[] tags) { + return new UntagEntity(url, ignore, metalake, name, tags); } protected ListColumns newListColumns( diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTag.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTag.java index 76f6f85b4..004254c16 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTag.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTag.java @@ -19,6 +19,9 @@ package org.apache.gravitino.cli.commands; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.apache.gravitino.cli.ErrorMessages; import org.apache.gravitino.client.GravitinoClient; import org.apache.gravitino.exceptions.NoSuchMetalakeException; @@ -26,32 +29,41 @@ import org.apache.gravitino.exceptions.TagAlreadyExistsException; public class CreateTag extends Command { protected final String metalake; - protected final String tag; + protected final String[] tags; protected final String comment; /** - * Create a new tag. + * Create tags. * * @param url The URL of the Gravitino server. * @param ignoreVersions If true don't check the client/server versions match. * @param metalake The name of the metalake. - * @param tag The name of the tag. + * @param tags The names of the tags. * @param comment The comment of the tag. */ public CreateTag( - String url, boolean ignoreVersions, String metalake, String tag, String comment) { + String url, boolean ignoreVersions, String metalake, String[] tags, String comment) { super(url, ignoreVersions); this.metalake = metalake; - this.tag = tag; + this.tags = tags; this.comment = comment; } - /** Create a new tag. */ + /** Create tags. */ @Override public void handle() { + boolean hasOnlyOneTag = tags.length == 1; + if (hasOnlyOneTag) { + handleOnlyOneTag(); + } else { + handleMultipleTags(); + } + } + + private void handleOnlyOneTag() { try { GravitinoClient client = buildClient(metalake); - client.createTag(tag, comment, null); + client.createTag(tags[0], comment, null); } catch (NoSuchMetalakeException err) { System.err.println(ErrorMessages.UNKNOWN_METALAKE); return; @@ -63,6 +75,34 @@ public class CreateTag extends Command { return; } - System.out.println(tag + " created"); + System.out.println(tags[0] + " created"); + } + + private void handleMultipleTags() { + List<String> created = new ArrayList<>(); + try { + GravitinoClient client = buildClient(metalake); + for (String tag : tags) { + client.createTag(tag, comment, null); + created.add(tag); + } + } catch (NoSuchMetalakeException err) { + System.err.println(ErrorMessages.UNKNOWN_METALAKE); + return; + } catch (TagAlreadyExistsException err) { + System.err.println(ErrorMessages.TAG_EXISTS); + return; + } catch (Exception exp) { + System.err.println(exp.getMessage()); + return; + } + if (!created.isEmpty()) { + System.out.println("Tags " + String.join(",", created) + " created"); + } + if (created.size() < tags.length) { + List<String> remaining = Arrays.asList(tags); + remaining.removeAll(created); + System.out.println("Tags " + String.join(",", remaining) + " not created"); + } } } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTag.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTag.java index 4536897b0..0db4a8976 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTag.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTag.java @@ -19,6 +19,9 @@ package org.apache.gravitino.cli.commands; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.apache.gravitino.cli.AreYouSure; import org.apache.gravitino.cli.ErrorMessages; import org.apache.gravitino.client.GravitinoClient; @@ -28,28 +31,70 @@ import org.apache.gravitino.exceptions.NoSuchTagException; public class DeleteTag extends Command { protected final String metalake; - protected final String tag; + protected final String[] tags; protected final boolean force; /** - * Delete a tag. + * Delete tags. * * @param url The URL of the Gravitino server. * @param ignoreVersions If true don't check the client/server versions match. * @param force Force operation. * @param metalake The name of the metalake. - * @param tag The name of the tag. + * @param tags The names of the tags. */ - public DeleteTag(String url, boolean ignoreVersions, boolean force, String metalake, String tag) { + public DeleteTag( + String url, boolean ignoreVersions, boolean force, String metalake, String[] tags) { super(url, ignoreVersions); this.force = force; this.metalake = metalake; - this.tag = tag; + this.tags = tags; } - /** Delete a tag. */ + /** Delete tags. */ @Override public void handle() { + if (!AreYouSure.really(force)) { + return; + } + boolean hasOnlyOneTag = tags.length == 1; + if (hasOnlyOneTag) { + handleOnlyOneTag(); + } else { + handleMultipleTags(); + } + } + + private void handleMultipleTags() { + List<String> deleted = new ArrayList<>(); + try { + GravitinoClient client = buildClient(metalake); + for (String tag : tags) { + if (client.deleteTag(tag)) { + deleted.add(tag); + } + } + } catch (NoSuchMetalakeException err) { + System.err.println(ErrorMessages.UNKNOWN_METALAKE); + return; + } catch (NoSuchTagException err) { + System.err.println(ErrorMessages.UNKNOWN_TAG); + return; + } catch (Exception exp) { + System.err.println(exp.getMessage()); + return; + } + if (!deleted.isEmpty()) { + System.out.println("Tags " + String.join(",", deleted) + " deleted."); + } + if (deleted.size() < tags.length) { + List<String> remaining = Arrays.asList(tags); + remaining.removeAll(deleted); + System.out.println("Tags " + String.join(",", deleted) + " not deleted."); + } + } + + private void handleOnlyOneTag() { boolean deleted = false; if (!AreYouSure.really(force)) { @@ -58,7 +103,7 @@ public class DeleteTag extends Command { try { GravitinoClient client = buildClient(metalake); - deleted = client.deleteTag(tag); + deleted = client.deleteTag(tags[0]); } catch (NoSuchMetalakeException err) { System.err.println(ErrorMessages.UNKNOWN_METALAKE); return; @@ -71,9 +116,9 @@ public class DeleteTag extends Command { } if (deleted) { - System.out.println(tag + " deleted."); + System.out.println(tags[0] + " deleted."); } else { - System.out.println(tag + " not deleted."); + System.out.println(tags[0] + " not deleted."); } } } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagEntity.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagEntity.java index 08e1d2ca1..ed474c784 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagEntity.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagEntity.java @@ -34,29 +34,30 @@ import org.apache.gravitino.rel.Table; public class TagEntity extends Command { protected final String metalake; protected final FullName name; - protected final String tag; + protected final String[] tags; /** - * Tag an entity with an existing tag. + * Tag an entity with existing tags. * * @param url The URL of the Gravitino server. * @param ignoreVersions If true don't check the client/server versions match. * @param metalake The name of the metalake. * @param name The name of the entity. - * @param tag The name of the tag. + * @param tags The names of the tags. */ - public TagEntity(String url, boolean ignoreVersions, String metalake, FullName name, String tag) { + public TagEntity( + String url, boolean ignoreVersions, String metalake, FullName name, String[] tags) { super(url, ignoreVersions); this.metalake = metalake; this.name = name; - this.tag = tag; + this.tags = tags; } - /** Create a new tag. */ + /** Add tags for an entity. */ @Override public void handle() { String entity = "unknown"; - String[] tags = new String[0]; + String[] tagsToAdd = new String[0]; try { GravitinoClient client = buildClient(metalake); @@ -71,18 +72,18 @@ public class TagEntity extends Command { .loadCatalog(catalog) .asTableCatalog() .loadTable(NameIdentifier.of(schema, table)); - tags = gTable.supportsTags().associateTags(new String[] {tag}, null); + tagsToAdd = gTable.supportsTags().associateTags(tags, null); entity = table; } else if (name.hasSchemaName()) { String catalog = name.getCatalogName(); String schema = name.getSchemaName(); Schema gSchema = client.loadCatalog(catalog).asSchemas().loadSchema(schema); - tags = gSchema.supportsTags().associateTags(new String[] {tag}, null); + tagsToAdd = gSchema.supportsTags().associateTags(tags, null); entity = schema; } else if (name.hasCatalogName()) { String catalog = name.getCatalogName(); Catalog gCatalog = client.loadCatalog(catalog); - tags = gCatalog.supportsTags().associateTags(new String[] {tag}, null); + tagsToAdd = gCatalog.supportsTags().associateTags(tags, null); entity = catalog; } } catch (NoSuchMetalakeException err) { @@ -102,7 +103,7 @@ public class TagEntity extends Command { return; } - String all = String.join(",", tags); + String all = String.join(",", tagsToAdd); System.out.println(entity + " tagged with " + all); } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UntagEntity.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UntagEntity.java index 91b9fcb15..77437dafc 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UntagEntity.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UntagEntity.java @@ -34,30 +34,30 @@ import org.apache.gravitino.rel.Table; public class UntagEntity extends Command { protected final String metalake; protected final FullName name; - protected final String tag; + protected final String[] tags; /** - * Untag an entity with an existing tag. + * Remove existing tags from an entity. * * @param url The URL of the Gravitino server. * @param ignoreVersions If true don't check the client/server versions match. * @param metalake The name of the metalake. * @param name The name of the entity. - * @param tag The name of the tag. + * @param tags The names of the tags. */ public UntagEntity( - String url, boolean ignoreVersions, String metalake, FullName name, String tag) { + String url, boolean ignoreVersions, String metalake, FullName name, String[] tags) { super(url, ignoreVersions); this.metalake = metalake; this.name = name; - this.tag = tag; + this.tags = tags; } - /** Create a new tag. */ + /** Remove tags from an entity. */ @Override public void handle() { String entity = "unknown"; - String[] tags = new String[0]; + String[] removeTags = new String[0]; try { GravitinoClient client = buildClient(metalake); @@ -72,18 +72,18 @@ public class UntagEntity extends Command { .loadCatalog(catalog) .asTableCatalog() .loadTable(NameIdentifier.of(schema, table)); - tags = gTable.supportsTags().associateTags(null, new String[] {tag}); + removeTags = gTable.supportsTags().associateTags(null, tags); entity = table; } else if (name.hasSchemaName()) { String catalog = name.getCatalogName(); String schema = name.getSchemaName(); Schema gSchema = client.loadCatalog(catalog).asSchemas().loadSchema(schema); - tags = gSchema.supportsTags().associateTags(null, new String[] {tag}); + removeTags = gSchema.supportsTags().associateTags(null, tags); entity = schema; } else if (name.hasCatalogName()) { String catalog = name.getCatalogName(); Catalog gCatalog = client.loadCatalog(catalog); - tags = gCatalog.supportsTags().associateTags(null, new String[] {tag}); + removeTags = gCatalog.supportsTags().associateTags(null, tags); entity = catalog; } } catch (NoSuchMetalakeException err) { @@ -103,12 +103,13 @@ public class UntagEntity extends Command { return; } - String all = String.join(",", tags); + String all = String.join(",", removeTags); if (all.equals("")) { all = "nothing"; } - System.out.println(entity + " removed tag " + tag + ", now tagged with " + all); + System.out.println( + entity + " removed tag " + String.join(",", tags) + " now tagged with " + all); } } diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestTagCommands.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTagCommands.java index 3dfd0392c..91a809fbc 100644 --- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestTagCommands.java +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTagCommands.java @@ -19,6 +19,7 @@ package org.apache.gravitino.cli; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; @@ -41,8 +42,10 @@ import org.apache.gravitino.cli.commands.TagEntity; import org.apache.gravitino.cli.commands.UntagEntity; import org.apache.gravitino.cli.commands.UpdateTagComment; import org.apache.gravitino.cli.commands.UpdateTagName; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; class TestTagCommands { private CommandLine mockCommandLine; @@ -75,7 +78,7 @@ class TestTagCommands { TagDetails mockDetails = mock(TagDetails.class); when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); - when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); GravitinoCommandLine commandLine = spy( @@ -94,7 +97,33 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); + when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.CREATE)); + doReturn(mockCreate) + .when(commandLine) + .newCreateTags( + GravitinoCommandLine.DEFAULT_URL, + false, + "metalake_demo", + new String[] {"tagA"}, + "comment"); + commandLine.handleCommandLine(); + verify(mockCreate).handle(); + } + + @Test + void testCreateTagsCommand() { + CreateTag mockCreate = mock(CreateTag.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)) + .thenReturn(new String[] {"tagA", "tagB"}); when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment"); GravitinoCommandLine commandLine = @@ -103,7 +132,12 @@ class TestTagCommands { mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.CREATE)); doReturn(mockCreate) .when(commandLine) - .newCreateTag(GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "tagA", "comment"); + .newCreateTags( + GravitinoCommandLine.DEFAULT_URL, + false, + "metalake_demo", + new String[] {"tagA", "tagB"}, + "comment"); commandLine.handleCommandLine(); verify(mockCreate).handle(); } @@ -114,14 +148,15 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); GravitinoCommandLine commandLine = spy( new GravitinoCommandLine( mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.CREATE)); doReturn(mockCreate) .when(commandLine) - .newCreateTag(GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "tagA", null); + .newCreateTags( + GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", new String[] {"tagA"}, null); commandLine.handleCommandLine(); verify(mockCreate).handle(); } @@ -132,14 +167,39 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.DELETE)); + doReturn(mockDelete) + .when(commandLine) + .newDeleteTag( + GravitinoCommandLine.DEFAULT_URL, false, false, "metalake_demo", new String[] {"tagA"}); + commandLine.handleCommandLine(); + verify(mockDelete).handle(); + } + + @Test + void testDeleteTagsCommand() { + DeleteTag mockDelete = mock(DeleteTag.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)) + .thenReturn(new String[] {"tagA", "tagB"}); GravitinoCommandLine commandLine = spy( new GravitinoCommandLine( mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.DELETE)); doReturn(mockDelete) .when(commandLine) - .newDeleteTag(GravitinoCommandLine.DEFAULT_URL, false, false, "metalake_demo", "tagA"); + .newDeleteTag( + GravitinoCommandLine.DEFAULT_URL, + false, + false, + "metalake_demo", + new String[] {"tagA", "tagB"}); commandLine.handleCommandLine(); verify(mockDelete).handle(); } @@ -150,7 +210,7 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true); GravitinoCommandLine commandLine = spy( @@ -158,7 +218,8 @@ class TestTagCommands { mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.DELETE)); doReturn(mockDelete) .when(commandLine) - .newDeleteTag(GravitinoCommandLine.DEFAULT_URL, false, true, "metalake_demo", "tagA"); + .newDeleteTag( + GravitinoCommandLine.DEFAULT_URL, false, true, "metalake_demo", new String[] {"tagA"}); commandLine.handleCommandLine(); verify(mockDelete).handle(); } @@ -169,7 +230,7 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property"); when(mockCommandLine.hasOption(GravitinoOptions.VALUE)).thenReturn(true); @@ -186,13 +247,34 @@ class TestTagCommands { verify(mockSetProperty).handle(); } + @Test + void testSetMultipleTagPropertyCommandError() { + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)) + .thenReturn(new String[] {"tagA", "tagB"}); + when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property"); + when(mockCommandLine.hasOption(GravitinoOptions.VALUE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.VALUE)).thenReturn("value"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.SET)); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> commandLine.handleCommandLine(), + "Error: The current command only supports one --tag option."); + } + @Test void testRemoveTagPropertyCommand() { RemoveTagProperty mockRemoveProperty = mock(RemoveTagProperty.class); when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property"); GravitinoCommandLine commandLine = @@ -213,7 +295,7 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); GravitinoCommandLine commandLine = spy( new GravitinoCommandLine( @@ -232,7 +314,7 @@ class TestTagCommands { when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("new comment"); GravitinoCommandLine commandLine = spy( @@ -252,7 +334,7 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); when(mockCommandLine.hasOption(GravitinoOptions.RENAME)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.RENAME)).thenReturn("tagB"); GravitinoCommandLine commandLine = @@ -293,7 +375,39 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.table"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)).thenReturn(new String[] {"tagA"}); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.SET)); + doReturn(mockTagEntity) + .when(commandLine) + .newTagEntity( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + any(), + argThat( + new ArgumentMatcher<String[]>() { + @Override + public boolean matches(String[] argument) { + return argument != null && argument.length > 0 && "tagA".equals(argument[0]); + } + })); + commandLine.handleCommandLine(); + verify(mockTagEntity).handle(); + } + + @Test + void testTagsEntityCommand() { + TagEntity mockTagEntity = mock(TagEntity.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.table"); + when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)) + .thenReturn(new String[] {"tagA", "tagB"}); GravitinoCommandLine commandLine = spy( new GravitinoCommandLine( @@ -305,7 +419,16 @@ class TestTagCommands { eq(false), eq("metalake_demo"), any(), - eq("tagA")); + argThat( + new ArgumentMatcher<String[]>() { + @Override + public boolean matches(String[] argument) { + return argument != null + && argument.length == 2 + && "tagA".equals(argument[0]) + && "tagB".equals(argument[1]); + } + })); commandLine.handleCommandLine(); verify(mockTagEntity).handle(); } @@ -318,7 +441,42 @@ class TestTagCommands { when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.table"); when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); - when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA"); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)) + .thenReturn(new String[] {"tagA", "tagB"}); + when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(false); + when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn(null); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.TAG, CommandActions.REMOVE)); + doReturn(mockUntagEntity) + .when(commandLine) + .newUntagEntity( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + any(), + argThat( + new ArgumentMatcher<String[]>() { + @Override + public boolean matches(String[] argument) { + return argument != null && argument.length > 0 && "tagA".equals(argument[0]); + } + })); + commandLine.handleCommandLine(); + verify(mockUntagEntity).handle(); + } + + @Test + void testUntagsEntityCommand() { + UntagEntity mockUntagEntity = mock(UntagEntity.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.table"); + when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true); + when(mockCommandLine.getOptionValues(GravitinoOptions.TAG)) + .thenReturn(new String[] {"tagA", "tagB"}); when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(false); when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn(null); GravitinoCommandLine commandLine = @@ -332,7 +490,16 @@ class TestTagCommands { eq(false), eq("metalake_demo"), any(), - eq("tagA")); + argThat( + new ArgumentMatcher<String[]>() { + @Override + public boolean matches(String[] argument) { + return argument != null + && argument.length == 2 + && "tagA".equals(argument[0]) + && "tagB".equals(argument[1]); + } + })); commandLine.handleCommandLine(); verify(mockUntagEntity).handle(); } diff --git a/docs/cli.md b/docs/cli.md index d9862df9b..e37e92e10 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -456,10 +456,10 @@ gcli group delete --group new_group gcli tag details --tag tagA ``` -#### Create a tag +#### Create tags ```bash - gcli tag create --tag tagA + gcli tag create --tag tagA tagB ``` #### List all tag @@ -468,22 +468,22 @@ gcli tag details --tag tagA gcli tag list ``` -#### Delete a tag +#### Delete tags ```bash -gcli tag delete --tag tagA +gcli tag delete --tag tagA tagB ``` -#### Add a tag to an entity +#### Add tags to an entity ```bash -gcli tag set --name catalog_postgres.hr --tag tagA +gcli tag set --name catalog_postgres.hr --tag tagA tagB ``` -#### Remove a tag from an entity +#### Remove tags from an entity ```bash -gcli tag remove --name catalog_postgres.hr --tag tagA +gcli tag remove --name catalog_postgres.hr --tag tagA tagB ``` #### List all tags on an entity