Skip to content

Commit

Permalink
[#6496] feat(CLI): Support plain format output for Schema and Table c…
Browse files Browse the repository at this point in the history
…ommand (#6497)

### What changes were proposed in this pull request?

Support plain format output for Schema and Table command

### Why are the changes needed?

Fix: #6496

### Does this PR introduce _any_ user-facing change?

No

### How was this patch tested?

local test
```bash
gcli schema list -m demo_metalake --name Hive_catalog  -i
default,Default Hive database

gcli table list -m demo_metalake --name Hive_catalog.default -i 
employee_partitions
source_table
test_dt_partition
test_key_en
sales
order_tb1
tmp
float_test
test_tbl

gcli table details -m demo_metalake --name Hive_catalog.default.test1  -i 
test1,N/A

gcli schema details -m demo_metalake --name Hive_catalog.default -i 
default,Default Hive database
```
  • Loading branch information
Abyss-lord authored Feb 26, 2025
1 parent d36ebac commit af9c2b4
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*/
public interface OutputFormat<T> {
/** Joiner for creating comma-separated output strings, ignoring null values */
Joiner COMMA_JOINER = Joiner.on(", ").skipNulls();
Joiner COMMA_JOINER = Joiner.on(",").skipNulls();
/** Joiner for creating line-separated output strings, ignoring null values */
Joiner NEWLINE_JOINER = Joiner.on(System.lineSeparator()).skipNulls();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.gravitino.Audit;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Metalake;
import org.apache.gravitino.Schema;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.rel.Table;

/** Plain format to print a pretty string to standard out. */
public abstract class PlainFormat<T> extends BaseOutputFormat<T> {
Expand All @@ -45,6 +48,16 @@ public static void output(Object entity, CommandContext context) {
new CatalogPlainFormat(context).output((Catalog) entity);
} else if (entity instanceof Catalog[]) {
new CatalogListPlainFormat(context).output((Catalog[]) entity);
} else if (entity instanceof Schema) {
new SchemaPlainFormat(context).output((Schema) entity);
} else if (entity instanceof Schema[]) {
new SchemaListPlainFormat(context).output((Schema[]) entity);
} else if (entity instanceof Table) {
new TablePlainFormat(context).output((Table) entity);
} else if (entity instanceof Table[]) {
new TableListPlainFormat(context).output((Table[]) entity);
} else if (entity instanceof Audit) {
new AuditPlainFormat(context).output((Audit) entity);
} else {
throw new IllegalArgumentException("Unsupported object type");
}
Expand Down Expand Up @@ -124,14 +137,98 @@ public CatalogListPlainFormat(CommandContext context) {
/** {@inheritDoc} */
@Override
public String getOutput(Catalog[] catalogs) {
if (catalogs == null || catalogs.length == 0) {
output("No catalogs exist.", System.out);
return null;
} else {
List<String> catalogNames =
Arrays.stream(catalogs).map(Catalog::name).collect(Collectors.toList());
return NEWLINE_JOINER.join(catalogNames);
}

List<String> catalogNames =
Arrays.stream(catalogs).map(Catalog::name).collect(Collectors.toList());
return NEWLINE_JOINER.join(catalogNames);
}
}

/**
* Formats a single {@link Schema} instance as a comma-separated string. Output format: name,
* comment
*/
static final class SchemaPlainFormat extends PlainFormat<Schema> {
public SchemaPlainFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Schema schema) {
return COMMA_JOINER.join(schema.name(), schema.comment());
}
}

/**
* Formats an array of Schemas, outputting one name per line. Returns null if the array is empty
* or null.
*/
static final class SchemaListPlainFormat extends PlainFormat<Schema[]> {
public SchemaListPlainFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Schema[] schemas) {
List<String> schemaNames =
Arrays.stream(schemas).map(Schema::name).collect(Collectors.toList());
return NEWLINE_JOINER.join(schemaNames);
}
}

/**
* Formats a single Table instance with detailed column information. Output format: table_name,
* table_comment
*/
static final class TablePlainFormat extends PlainFormat<Table> {
public TablePlainFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Table table) {
String comment = table.comment() == null ? "N/A" : table.comment();
return COMMA_JOINER.join(new String[] {table.name(), comment});
}
}

/**
* Formats an array of Tables, outputting one name per line. Returns null if the array is empty or
* null.
*/
static final class TableListPlainFormat extends PlainFormat<Table[]> {
public TableListPlainFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Table[] tables) {
List<String> tableNames = Arrays.stream(tables).map(Table::name).collect(Collectors.toList());
return NEWLINE_JOINER.join(tableNames);
}
}

/**
* Formats an instance of {@link Audit} , outputting the audit information. Output format:
* creator, create_time, modified, modified_time
*/
static final class AuditPlainFormat extends PlainFormat<Audit> {
public AuditPlainFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Audit audit) {
return COMMA_JOINER.join(
audit.creator(),
audit.createTime() == null ? "N/A" : audit.createTime(),
audit.lastModifier() == null ? "N/A" : audit.lastModifier(),
audit.lastModifiedTime() == null ? "N/A" : audit.lastModifiedTime());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
import org.apache.gravitino.Schema;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.outputs.PlainFormat;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.types.Type;
import org.apache.gravitino.rel.types.Types;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -64,7 +67,7 @@ void testMetalakeDetailsWithPlainFormat() {

PlainFormat.output(mockMetalake, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals("demo_metalake, This is a demo metalake", output);
Assertions.assertEquals("demo_metalake,This is a demo metalake", output);
}

@Test
Expand All @@ -85,8 +88,7 @@ void testCatalogDetailsWithPlainFormat() {

PlainFormat.output(mockCatalog, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals(
"demo_catalog, RELATIONAL, demo_provider, This is a demo catalog", output);
Assertions.assertEquals("demo_catalog,RELATIONAL,demo_provider,This is a demo catalog", output);
}

@Test
Expand All @@ -102,6 +104,75 @@ void testListCatalogWithPlainFormat() {
Assertions.assertEquals("catalog1\n" + "catalog2", output);
}

@Test
void testSchemaDetailsWithPlainFormat() {
CommandContext mockContext = getMockContext();
Schema mockSchema = getMockSchema();
PlainFormat.output(mockSchema, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals("demo_schema,This is a demo schema", output);
}

@Test
void testListSchemaWithPlainFormat() {
CommandContext mockContext = getMockContext();
Schema mockSchema1 = getMockSchema("schema1", "This is a schema");
Schema mockSchema2 = getMockSchema("schema2", "This is another schema");

PlainFormat.output(new Schema[] {mockSchema1, mockSchema2}, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals("schema1\n" + "schema2", output);
}

@Test
void testTableDetailsWithPlainFormat() {
CommandContext mockContext = getMockContext();
Table mockTable = getMockTable();
PlainFormat.output(mockTable, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals("demo_table,This is a demo table", output);
}

@Test
void testAuditWithTableFormat() {
CommandContext mockContext = getMockContext();
Audit mockAudit = mock(Audit.class);
when(mockAudit.creator()).thenReturn("demo_user");
when(mockAudit.createTime()).thenReturn(Instant.ofEpochMilli(1611111111111L));
when(mockAudit.lastModifier()).thenReturn("demo_user");
when(mockAudit.lastModifiedTime()).thenReturn(Instant.ofEpochMilli(1611111111111L));

PlainFormat.output(mockAudit, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals(
"demo_user,2021-01-20T02:51:51.111Z,demo_user,2021-01-20T02:51:51.111Z", output);
}

@Test
void testAuditWithTableFormatWithNullValues() {
CommandContext mockContext = getMockContext();
Audit mockAudit = mock(Audit.class);
when(mockAudit.creator()).thenReturn("demo_user");
when(mockAudit.createTime()).thenReturn(null);
when(mockAudit.lastModifier()).thenReturn(null);
when(mockAudit.lastModifiedTime()).thenReturn(null);

PlainFormat.output(mockAudit, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals("demo_user,N/A,N/A,N/A", output);
}

@Test
void testListTableWithPlainFormat() {
CommandContext mockContext = getMockContext();
Table mockTable1 = getMockTable("table1", "This is a table");
Table mockTable2 = getMockTable("table2", "This is another table");

PlainFormat.output(new Table[] {mockTable1, mockTable2}, mockContext);
String output = new String(outContent.toByteArray(), StandardCharsets.UTF_8).trim();
Assertions.assertEquals("table1\n" + "table2", output);
}

@Test
void testOutputWithUnsupportType() {
CommandContext mockContext = getMockContext();
Expand Down Expand Up @@ -159,13 +230,35 @@ private Schema getMockSchema(String name, String comment) {
return mockSchema;
}

private Audit getMockAudit() {
Audit mockAudit = mock(Audit.class);
when(mockAudit.creator()).thenReturn("demo_user");
when(mockAudit.createTime()).thenReturn(Instant.ofEpochMilli(1611111111111L));
when(mockAudit.lastModifier()).thenReturn("demo_user");
when(mockAudit.lastModifiedTime()).thenReturn(Instant.ofEpochMilli(1611111111111L));
private Table getMockTable() {
return getMockTable("demo_table", "This is a demo table");
}

private Table getMockTable(String name, String comment) {
Table mockTable = mock(Table.class);
org.apache.gravitino.rel.Column mockColumnInt =
getMockColumn("id", Types.IntegerType.get(), "This is a int column", false, true);
org.apache.gravitino.rel.Column mockColumnString =
getMockColumn("name", Types.StringType.get(), "This is a string column", true, false);

when(mockTable.name()).thenReturn(name);
when(mockTable.comment()).thenReturn(comment);
when(mockTable.columns())
.thenReturn(new org.apache.gravitino.rel.Column[] {mockColumnInt, mockColumnString});

return mockTable;
}

private org.apache.gravitino.rel.Column getMockColumn(
String name, Type dataType, String comment, boolean nullable, boolean autoIncrement) {

org.apache.gravitino.rel.Column mockColumn = mock(org.apache.gravitino.rel.Column.class);
when(mockColumn.name()).thenReturn(name);
when(mockColumn.dataType()).thenReturn(dataType);
when(mockColumn.comment()).thenReturn(comment);
when(mockColumn.nullable()).thenReturn(nullable);
when(mockColumn.autoIncrement()).thenReturn(autoIncrement);

return mockAudit;
return mockColumn;
}
}

0 comments on commit af9c2b4

Please sign in to comment.