Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#6496] feat(CLI): Support plain format output for Schema and Table command #6497

Merged
merged 4 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}