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

[#6493] feat(CLI): Support table format output for Schema and Table command #6495

Merged
merged 7 commits into from
Feb 24, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ protected <T extends GravitinoClientBase> Builder<T> constructClient(Builder<T>
* @param entity The entity to output.
* @param <T> The type of entity.
*/
protected <T> void output(T entity) {
private <T> void output(T entity) {
if (outputFormat == null) {
PlainFormat.output(entity, context);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

package org.apache.gravitino.cli.commands;

import com.google.common.base.Joiner;
import org.apache.gravitino.Audit;
import org.apache.gravitino.Schema;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.ErrorMessages;
import org.apache.gravitino.client.GravitinoClient;
Expand Down Expand Up @@ -49,8 +50,10 @@ public ListSchema(CommandContext context, String metalake, String catalog) {
@Override
public void handle() {
String[] schemas = new String[0];
GravitinoClient client;

try {
GravitinoClient client = buildClient(metalake);
client = buildClient(metalake);
schemas = client.loadCatalog(catalog).asSchemas().listSchemas();
} catch (NoSuchMetalakeException err) {
exitWithError(ErrorMessages.UNKNOWN_METALAKE);
Expand All @@ -60,8 +63,29 @@ public void handle() {
exitWithError(exp.getMessage());
}

String all = schemas.length == 0 ? "No schemas exist." : Joiner.on(",").join(schemas);
if (schemas.length == 0) {
printInformation("No schemas exist.");
return;
}

Schema[] schemaObjects = new Schema[schemas.length];
for (int i = 0; i < schemas.length; i++) {
String schemaName = schemas[i];
Schema gSchema =
new Schema() {
@Override
public String name() {
return schemaName;
}

@Override
public Audit auditInfo() {
return null;
}
};
schemaObjects[i] = gSchema;
}

printInformation(all);
printResults(schemaObjects);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@

package org.apache.gravitino.cli.commands;

import com.google.common.base.Joiner;
import java.util.ArrayList;
import java.util.List;
import org.apache.gravitino.Audit;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.Table;

/** List the names of all tables in a schema. */
public class ListTables extends TableCommand {
Expand All @@ -49,22 +49,41 @@ public ListTables(CommandContext context, String metalake, String catalog, Strin
public void handle() {
NameIdentifier[] tables = null;
Namespace name = Namespace.of(schema);

try {
tables = tableCatalog().listTables(name);
} catch (Exception exp) {
exitWithError(exp.getMessage());
}

List<String> tableNames = new ArrayList<>();
for (int i = 0; i < tables.length; i++) {
tableNames.add(tables[i].name());
if (tables.length == 0) {
printInformation("No tables exist.");
return;
}

String all =
tableNames.isEmpty()
? "No tables exist."
: Joiner.on(System.lineSeparator()).join(tableNames);
Table[] gTables = new Table[tables.length];
for (int i = 0; i < tables.length; i++) {
String tableName = tables[i].name();
gTables[i] =
new Table() {

@Override
public String name() {
return tableName;
}

@Override
public Column[] columns() {
return new Column[0];
}

@Override
public Audit auditInfo() {
return null;
}
};
}

printResults(all);
printResults(gTables);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void handle() {
}

if (result != null) {
printInformation(result.name() + "," + result.comment());
printResults(result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public void handle() {
exitWithError(exp.getMessage());
}

printInformation(gTable.name() + "," + gTable.comment());
if (gTable != null) {
printResults(gTable);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
import java.util.Objects;
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;

/**
* Abstract base class for formatting entity information into ASCII-art tables. Provides
Expand Down Expand Up @@ -75,6 +77,14 @@ public static void output(Object entity, CommandContext context) {
new CatalogTableFormat(context).output((Catalog) entity);
} else if (entity instanceof Catalog[]) {
new CatalogListTableFormat(context).output((Catalog[]) entity);
} else if (entity instanceof Schema) {
new SchemaTableFormat(context).output((Schema) entity);
} else if (entity instanceof Schema[]) {
new SchemaListTableFormat(context).output((Schema[]) entity);
} else if (entity instanceof Table) {
new TableDetailsTableFormat(context).output((Table) entity);
} else if (entity instanceof Table[]) {
new TableListTableFormat(context).output((Table[]) entity);
} else {
throw new IllegalArgumentException("Unsupported object type");
}
Expand Down Expand Up @@ -540,4 +550,97 @@ public String getOutput(Catalog[] catalogs) {
return getTableFormat(columnName);
}
}

/**
* Formats a single {@link Schema} instance into a two-column table display. Displays catalog
* details including name and comment information.
*/
static final class SchemaTableFormat extends TableFormat<Schema> {
public SchemaTableFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Schema schema) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual logic that gets overridden is the process of building a list of Columns.
So is it possible to refactor this function into a protected function getColumns, and then we invoke the public getOutput function from the parent class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetching the output and fetching the columns represent two different levels of activity, but they're actually doing the same thing in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can invoke the function from the parent class, which is more generic, then you don't have to create a specialized class to invoke the getOutput method.

I mean this:

   class Animal {
      public describe() {
         legs = getLegs()
         wings = getWings()
         return "legs:" + legs + 'wings:' + wings
      }
      protected getLegs() {
         return 0
       }
      protected getWings() {
      }
    }

    class Dog extends Animal {
       protected getLegs() {
          return 4
       }
    }

    class Duck extends Animal {
      protected getWings() {
        return 2
      }
    }

When invoking this method in future, I'll do:

   // I don't need to care what kind of an animal it is.
   animal.describe()

Copy link
Contributor Author

@Abyss-lord Abyss-lord Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can invoke the function from the parent class, which is more generic, then you don't have to create a specialized class to invoke the getOutput method.

I mean this:

   class Animal {
      public describe() {
         legs = getLegs()
         wings = getWings()
         return "legs:" + legs + 'wings:' + wings
      }
      protected getLegs() {
         return 0
       }
      protected getWings() {
      }
    }

    class Dog extends Animal {
       protected getLegs() {
          return 4
       }
    }

    class Duck extends Animal {
      protected getWings() {
        return 2
      }
    }

When invoking this method in future, I'll do:

   // I don't need to care what kind of an animal it is.
   animal.describe()

@tengqm In this case, the template pattern is used, BaseOutputFormat#output is responsible for output, it fix the logic of output method, and the getOutput method is implemented by subclasses.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merit is that you don't need to instantiate the subclasses, you will save a lot of complicated if...else if...else if... calls when invoking this method.
The subclasses provide the materials (columns), the superclass does the main logic.

Column columnName = new Column(context, "schema");
Column columnComment = new Column(context, "comment");

columnName.addCell(schema.name());
columnComment.addCell(schema.comment());

return getTableFormat(columnName, columnComment);
}
}

/**
* Formats an array of Schemas into a single-column table display. Lists all schema names in a
* vertical format.
*/
static final class SchemaListTableFormat extends TableFormat<Schema[]> {
public SchemaListTableFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Schema[] schemas) {
Column column = new Column(context, "schema");
Arrays.stream(schemas).forEach(schema -> column.addCell(schema.name()));

return getTableFormat(column);
}
}

/**
* Formats a single {@link Table} instance into a two-column table display. Displays table details
* including name and comment information.
*/
static final class TableDetailsTableFormat extends TableFormat<Table> {
public TableDetailsTableFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Table table) {
Column columnName = new Column(context, "name");
Column columnType = new Column(context, "type");
Column columnAutoIncrement = new Column(context, "AutoIncrement");
Column columnNullable = new Column(context, "nullable");
Column columnComment = new Column(context, "comment");

org.apache.gravitino.rel.Column[] columns = table.columns();
for (org.apache.gravitino.rel.Column column : columns) {
columnName.addCell(column.name());
columnType.addCell(column.dataType().simpleString());
columnAutoIncrement.addCell(column.autoIncrement());
columnNullable.addCell(column.nullable());
columnComment.addCell(
column.comment() == null || column.comment().isEmpty() ? "N/A" : column.comment());
}

return getTableFormat(
columnName, columnType, columnAutoIncrement, columnNullable, columnComment);
}
}

/**
* Formats an array of {@link Table} into a single-column table display. Lists all table names in
* a vertical format.
*/
static final class TableListTableFormat extends TableFormat<Table[]> {
public TableListTableFormat(CommandContext context) {
super(context);
}

/** {@inheritDoc} */
@Override
public String getOutput(Table[] tables) {
Column column = new Column(context, "table");
Arrays.stream(tables).forEach(table -> column.addCell(table.name()));

return getTableFormat(column);
}
}
}
Loading