You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by ab...@apache.org on 2018/10/16 08:45:14 UTC

kudu git commit: [tools] Add a describe table tool

Repository: kudu
Updated Branches:
  refs/heads/master 1197eb01a -> 614b446e1


[tools] Add a describe table tool

This adds a very basic tool to describe a table, printing out its
schema, its partition schema, the current range partitions, and the
replication factor. It eschews completeness for brevity, leaving out,
for example, column storage attributes, since they clutter the output
and make it hard to read. My use case for this tool is as an easy way a
user can gather the fundamental info about a table and show it to
someone else. It should be easy to copy and paste, usually fit on one
line in a terminal window, and not depend on fancy formatting or a
fixed-width font to display correctly.

Sample output:

$ kudu table describe localhost:7053 default.loadgen_auto_f6d439ab21e4408486786289297f7db0
TABLE default.loadgen_auto_f6d439ab21e4408486786289297f7db0 (
    key INT64 NOT NULL,
    int_val INT32 NULLABLE,
    string_val STRING NULLABLE,
    PRIMARY KEY (key)
)
HASH (key) PARTITIONS 2,
RANGE (key) (
    PARTITION UNBOUNDED
)
REPLICAS 1

Getting this "SQL-ish" look required changing how schemas and column
schemas are stringified, so there are some knock-on changes in various
tests as well.

I also reorganized the order of `kudu table` commands so they are in
alphabetical order.

Change-Id: Ib02f2a94cf11d0e60b32cd85be920ca9b99dd977
Reviewed-on: http://gerrit.cloudera.org:8080/11665
Tested-by: Kudu Jenkins
Reviewed-by: Mike Percy <mp...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/614b446e
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/614b446e
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/614b446e

Branch: refs/heads/master
Commit: 614b446e1f9291189657b265830d05d0395c5d1c
Parents: 1197eb0
Author: Will Berkeley <wd...@gmail.org>
Authored: Thu Oct 11 11:30:36 2018 -0700
Committer: Will Berkeley <wd...@gmail.com>
Committed: Tue Oct 16 05:39:39 2018 +0000

----------------------------------------------------------------------
 src/kudu/client/client-test.cc           |   2 +-
 src/kudu/client/client-unittest.cc       |  46 +++++-----
 src/kudu/client/client.h                 |  11 ++-
 src/kudu/client/schema.cc                |   2 +-
 src/kudu/common/partial_row-test.cc      |   2 +-
 src/kudu/common/row_changelist-test.cc   |   4 +-
 src/kudu/common/row_operations-test.cc   |  16 ++--
 src/kudu/common/schema-test.cc           |  98 +++++++++++----------
 src/kudu/common/schema.cc                |  27 +++---
 src/kudu/tablet/tablet_bootstrap-test.cc |   2 +-
 src/kudu/tools/CMakeLists.txt            |   2 +-
 src/kudu/tools/kudu-admin-test.cc        | 121 ++++++++++++++++++++++++++
 src/kudu/tools/kudu-tool-test.cc         |   6 +-
 src/kudu/tools/tool_action_common.cc     |  46 +++++++++-
 src/kudu/tools/tool_action_common.h      |   8 +-
 src/kudu/tools/tool_action_table.cc      |  90 +++++++++++++++----
 src/kudu/tserver/tablet_server-test.cc   |  22 ++---
 17 files changed, 375 insertions(+), 130 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/client/client-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/client-test.cc b/src/kudu/client/client-test.cc
index 1322452..dc7e7b8 100644
--- a/src/kudu/client/client-test.cc
+++ b/src/kudu/client/client-test.cc
@@ -3618,7 +3618,7 @@ TEST_F(ClientTest, TestWriteWithBadSchema) {
   unique_ptr<KuduError> error = GetSingleErrorFromSession(session.get());
   ASSERT_TRUE(error->status().IsInvalidArgument());
   ASSERT_STR_CONTAINS(error->status().ToString(),
-            "Client provided column int_val[int32 NOT NULL] "
+            "Client provided column int_val INT32 NOT NULL "
             "not present in tablet");
   ASSERT_EQ(error->failed_op().ToString(),
             R"(INSERT int32 key=12345, int32 int_val=12345, string string_val="x")");

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/client/client-unittest.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/client-unittest.cc b/src/kudu/client/client-unittest.cc
index d2c4866..f1b2fea 100644
--- a/src/kudu/client/client-unittest.cc
+++ b/src/kudu/client/client-unittest.cc
@@ -238,18 +238,18 @@ TEST(ClientUnitTest, TestKuduSchemaToString) {
     ->Default(KuduValue::FromInt(12345));
   ASSERT_OK(b1.Build(&s1));
 
-  string schema_str_1 = "Schema [\n"
-                        "\tprimary key (key),\n"
-                        "\tkey[int32 NOT NULL],\n"
-                        "\tint_val[int32 NOT NULL],\n"
-                        "\tstring_val[string NULLABLE],\n"
-                        "\tnon_null_with_default[int32 NOT NULL]\n"
-                        "]";
+  string schema_str_1 = "(\n"
+                        "    key INT32 NOT NULL,\n"
+                        "    int_val INT32 NOT NULL,\n"
+                        "    string_val STRING NULLABLE,\n"
+                        "    non_null_with_default INT32 NOT NULL,\n"
+                        "    PRIMARY KEY (key)\n"
+                        ")";
   EXPECT_EQ(schema_str_1, s1.ToString());
 
   // Test empty schema.
   KuduSchema s2;
-  EXPECT_EQ("Schema []", s2.ToString());
+  EXPECT_EQ("()", s2.ToString());
 
   // Test on composite PK.
   // Create a different schema with a multi-column PK.
@@ -265,16 +265,16 @@ TEST(ClientUnitTest, TestKuduSchemaToString) {
   b2.SetPrimaryKey({"k1", "k2", "k3"});
   ASSERT_OK(b2.Build(&s2));
 
-  string schema_str_2 = "Schema [\n"
-                        "\tprimary key (k1, k2, k3),\n"
-                        "\tk1[int32 NOT NULL],\n"
-                        "\tk2[unixtime_micros NOT NULL],\n"
-                        "\tk3[int8 NOT NULL],\n"
-                        "\tdec_val[decimal(9, 2) NULLABLE],\n"
-                        "\tint_val[int32 NOT NULL],\n"
-                        "\tstring_val[string NULLABLE],\n"
-                        "\tnon_null_with_default[int32 NOT NULL]\n"
-                        "]";
+  string schema_str_2 = "(\n"
+                        "    k1 INT32 NOT NULL,\n"
+                        "    k2 UNIXTIME_MICROS NOT NULL,\n"
+                        "    k3 INT8 NOT NULL,\n"
+                        "    dec_val DECIMAL(9, 2) NULLABLE,\n"
+                        "    int_val INT32 NOT NULL,\n"
+                        "    string_val STRING NULLABLE,\n"
+                        "    non_null_with_default INT32 NOT NULL,\n"
+                        "    PRIMARY KEY (k1, k2, k3)\n"
+                        ")";
   EXPECT_EQ(schema_str_2, s2.ToString());
 }
 
@@ -289,10 +289,16 @@ TEST(ClientUnitTest, TestKuduSchemaToStringWithColumnIds) {
   // The string version of the KuduSchema should not have column ids, even
   // though the default string version of the underlying Schema should.
   EXPECT_EQ(
-      Substitute("Schema [\n\tprimary key (key),\n\t$0:key[int32 NOT NULL]\n]",
+      Substitute("(\n"
+                 "    $0:key INT32 NOT NULL,\n"
+                 "    PRIMARY KEY (key)\n"
+                 ")",
                  schema.column_id(0)),
       schema.ToString());
-  EXPECT_EQ("Schema [\n\tprimary key (key),\n\tkey[int32 NOT NULL]\n]",
+  EXPECT_EQ("(\n"
+            "    key INT32 NOT NULL,\n"
+            "    PRIMARY KEY (key)\n"
+            ")",
             kudu_schema.ToString());
 }
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/client/client.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/client.h b/src/kudu/client/client.h
index e479aa9..79153a0 100644
--- a/src/kudu/client/client.h
+++ b/src/kudu/client/client.h
@@ -52,16 +52,19 @@ namespace kudu {
 class ClientStressTest_TestUniqueClientIds_Test;
 class KuduPartialRow;
 class MonoDelta;
+class Partition;
 class PartitionSchema;
 class SecurityUnknownTskTest;
 
 namespace client {
 class KuduClient;
-class KuduTableAlterer;
-}
+class KuduTable;
+} // namespace client
 
 namespace tools {
 class LeaderMasterProxy;
+Status ListPartitions(const client::sp::shared_ptr<client::KuduTable>& table,
+                      std::vector<Partition>* partitions);
 std::string GetMasterAddresses(const client::KuduClient&);
 } // namespace tools
 
@@ -75,6 +78,7 @@ class KuduScanBatch;
 class KuduSession;
 class KuduStatusCallback;
 class KuduTable;
+class KuduTableAlterer;
 class KuduTableCreator;
 class KuduTablet;
 class KuduTabletServer;
@@ -586,6 +590,9 @@ class KUDU_EXPORT KuduClient : public sp::enable_shared_from_this<KuduClient> {
   friend class internal::WriteRpc;
   friend class kudu::SecurityUnknownTskTest;
   friend class tools::LeaderMasterProxy;
+  friend Status tools::ListPartitions(
+      const client::sp::shared_ptr<client::KuduTable>& table,
+      std::vector<Partition>* partitions);
   friend std::string tools::GetMasterAddresses(const client::KuduClient&);
 
   FRIEND_TEST(kudu::ClientStressTest, TestUniqueClientIds);

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/client/schema.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/schema.cc b/src/kudu/client/schema.cc
index 308e49d..085e161 100644
--- a/src/kudu/client/schema.cc
+++ b/src/kudu/client/schema.cc
@@ -739,7 +739,7 @@ void KuduSchema::GetPrimaryKeyColumnIndexes(vector<int>* indexes) const {
 
 string KuduSchema::ToString() const {
   return schema_ ? schema_->ToString(Schema::ToStringMode::WITHOUT_COLUMN_IDS)
-                 : "Schema []";
+                 : "()";
 }
 
 } // namespace client

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/common/partial_row-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/partial_row-test.cc b/src/kudu/common/partial_row-test.cc
index 20f1d1e..fc4671c 100644
--- a/src/kudu/common/partial_row-test.cc
+++ b/src/kudu/common/partial_row-test.cc
@@ -179,7 +179,7 @@ TEST_F(PartialRowTest, UnitTest) {
 
   // Try to set a non-nullable entry to NULL
   s = row.SetNull("key");
-  EXPECT_EQ("Invalid argument: column not nullable: key[int32 NOT NULL]", s.ToString());
+  EXPECT_EQ("Invalid argument: column not nullable: key INT32 NOT NULL", s.ToString());
 
   // Set the NULL string back to non-NULL
   EXPECT_OK(row.SetStringCopy("string_val", "goodbye world"));

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/common/row_changelist-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/row_changelist-test.cc b/src/kudu/common/row_changelist-test.cc
index 8a5765b..1797c89 100644
--- a/src/kudu/common/row_changelist-test.cc
+++ b/src/kudu/common/row_changelist-test.cc
@@ -217,7 +217,7 @@ TEST_F(TestRowChangeList, TestInvalid_SetNullForNonNullableColumn) {
   rcl.EncodeColumnMutationRaw(schema_.column_id(0), true, Slice());
 
   ASSERT_EQ("[invalid update: Corruption: decoded set-to-NULL "
-            "for non-nullable column: col1[string NOT NULL], "
+            "for non-nullable column: col1 STRING NOT NULL, "
             "before corruption: SET ]",
             RowChangeList(Slice(buf)).ToString(schema_));
 }
@@ -231,7 +231,7 @@ TEST_F(TestRowChangeList, TestInvalid_SetWrongSizeForIntColumn) {
   rcl.EncodeColumnMutationRaw(schema_.column_id(2), false, Slice("\xff"));
 
   ASSERT_EQ("[invalid update: Corruption: invalid value \\xff "
-            "for column col3[uint32 NOT NULL], "
+            "for column col3 UINT32 NOT NULL, "
             "before corruption: SET ]",
             RowChangeList(Slice(buf)).ToString(schema_));
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/common/row_operations-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/row_operations-test.cc b/src/kudu/common/row_operations-test.cc
index 4404c93..464ee91 100644
--- a/src/kudu/common/row_operations-test.cc
+++ b/src/kudu/common/row_operations-test.cc
@@ -381,7 +381,7 @@ TEST_F(RowOperationsTest, ProjectionTestWholeSchemaSpecified) {
     KuduPartialRow client_row(&client_schema);
     CHECK_OK(client_row.SetInt32("key", 12345));
     EXPECT_EQ("error: Invalid argument: No value provided for required column: "
-              "int_val[int32 NOT NULL]",
+              "int_val INT32 NOT NULL",
               TestProjection(RowOperationsPB::INSERT, client_row, schema_));
   }
 
@@ -487,7 +487,7 @@ TEST_F(RowOperationsTest, ProjectionTestWithClientHavingValidSubset) {
     KuduPartialRow client_row(&client_schema);
     CHECK_OK(client_row.SetInt32("key", 12345));
     EXPECT_EQ("error: Invalid argument: No value provided for required column:"
-              " int_val[int32 NOT NULL]",
+              " int_val INT32 NOT NULL",
               TestProjection(RowOperationsPB::INSERT, client_row, server_schema));
   }
 
@@ -518,7 +518,7 @@ TEST_F(RowOperationsTest, ProjectionTestWithClientHavingInvalidSubset) {
     KuduPartialRow client_row(&client_schema);
     CHECK_OK(client_row.SetInt32("key", 12345));
     EXPECT_EQ("error: Invalid argument: Client missing required column:"
-              " int_val[int32 NOT NULL]",
+              " int_val INT32 NOT NULL",
               TestProjection(RowOperationsPB::INSERT, client_row, server_schema));
   }
 }
@@ -533,7 +533,7 @@ TEST_F(RowOperationsTest, TestProjectUpdates) {
 
   // Check without specifying any columns
   KuduPartialRow client_row(&client_schema);
-  EXPECT_EQ("error: Invalid argument: No value provided for key column: key[int32 NOT NULL]",
+  EXPECT_EQ("error: Invalid argument: No value provided for key column: key INT32 NOT NULL",
             TestProjection(RowOperationsPB::UPDATE, client_row, server_schema));
 
   // Specify the key and no columns to update
@@ -610,7 +610,7 @@ TEST_F(RowOperationsTest, TestClientMismatchedType) {
   ASSERT_OK(client_row.SetInt32("key", 12345));
   ASSERT_OK(client_row.SetInt8("int_val", 1));
   EXPECT_EQ("error: Invalid argument: The column 'int_val' must have type "
-            "int32 NOT NULL found int8 NOT NULL",
+            "INT32 NOT NULL found INT8 NOT NULL",
             TestProjection(RowOperationsPB::UPDATE, client_row, server_schema));
 }
 
@@ -623,12 +623,12 @@ TEST_F(RowOperationsTest, TestProjectDeletes) {
 
   KuduPartialRow client_row(&client_schema);
   // No columns set
-  EXPECT_EQ("error: Invalid argument: No value provided for key column: key[int32 NOT NULL]",
+  EXPECT_EQ("error: Invalid argument: No value provided for key column: key INT32 NOT NULL",
             TestProjection(RowOperationsPB::DELETE, client_row, server_schema));
 
   // Only half the key set
   ASSERT_OK(client_row.SetInt32("key", 12345));
-  EXPECT_EQ("error: Invalid argument: No value provided for key column: key_2[int32 NOT NULL]",
+  EXPECT_EQ("error: Invalid argument: No value provided for key column: key_2 INT32 NOT NULL",
             TestProjection(RowOperationsPB::DELETE, client_row, server_schema));
 
   // Whole key set (correct)
@@ -639,7 +639,7 @@ TEST_F(RowOperationsTest, TestProjectDeletes) {
   // Extra column set (incorrect)
   ASSERT_OK(client_row.SetStringNoCopy("string_val", "hello"));
   EXPECT_EQ("error: Invalid argument: DELETE should not have a value for column: "
-            "string_val[string NULLABLE]",
+            "string_val STRING NULLABLE",
             TestProjection(RowOperationsPB::DELETE, client_row, server_schema));
 }
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/common/schema-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/schema-test.cc b/src/kudu/common/schema-test.cc
index 3fcbdbb..d32e666 100644
--- a/src/kudu/common/schema-test.cc
+++ b/src/kudu/common/schema-test.cc
@@ -92,15 +92,15 @@ TEST_F(TestSchema, TestSchema) {
   ASSERT_GT(schema.memory_footprint_excluding_this(),
             empty_schema.memory_footprint_excluding_this());
 
-  EXPECT_EQ("Schema [\n"
-            "\tprimary key (key),\n"
-            "\tkey[string NOT NULL],\n"
-            "\tuint32val[uint32 NULLABLE],\n"
-            "\tint32val[int32 NOT NULL]\n"
-            "]",
+  EXPECT_EQ("(\n"
+            "    key STRING NOT NULL,\n"
+            "    uint32val UINT32 NULLABLE,\n"
+            "    int32val INT32 NOT NULL,\n"
+            "    PRIMARY KEY (key)\n"
+            ")",
             schema.ToString());
-  EXPECT_EQ("key[string NOT NULL]", schema.column(0).ToString());
-  EXPECT_EQ("uint32 NULLABLE", schema.column(1).TypeToString());
+  EXPECT_EQ("key STRING NOT NULL", schema.column(0).ToString());
+  EXPECT_EQ("UINT32 NULLABLE", schema.column(1).TypeToString());
 }
 
 TEST_F(TestSchema, TestSchemaToStringMode) {
@@ -108,10 +108,16 @@ TEST_F(TestSchema, TestSchemaToStringMode) {
   builder.AddKeyColumn("key", DataType::INT32);
   const auto schema = builder.Build();
   EXPECT_EQ(
-      Substitute("Schema [\n\tprimary key (key),\n\t$0:key[int32 NOT NULL]\n]",
+      Substitute("(\n"
+                 "    $0:key INT32 NOT NULL,\n"
+                 "    PRIMARY KEY (key)\n"
+                 ")",
                  schema.column_id(0)),
       schema.ToString());
-  EXPECT_EQ("Schema [\n\tprimary key (key),\n\tkey[int32 NOT NULL]\n]",
+  EXPECT_EQ("(\n"
+            "    key INT32 NOT NULL,\n"
+            "    PRIMARY KEY (key)\n"
+            ")",
             schema.ToString(Schema::ToStringMode::WITHOUT_COLUMN_IDS));
 }
 
@@ -123,15 +129,15 @@ TEST_F(TestSchema, TestCopyAndMove) {
     ASSERT_EQ(0, schema.column_offset(0));
     ASSERT_EQ(sizeof(Slice), schema.column_offset(1));
 
-    EXPECT_EQ("Schema [\n"
-              "\tprimary key (key),\n"
-              "\tkey[string NOT NULL],\n"
-              "\tuint32val[uint32 NULLABLE],\n"
-              "\tint32val[int32 NOT NULL]\n"
-              "]",
+    EXPECT_EQ("(\n"
+              "    key STRING NOT NULL,\n"
+              "    uint32val UINT32 NULLABLE,\n"
+              "    int32val INT32 NOT NULL,\n"
+              "    PRIMARY KEY (key)\n"
+              ")",
               schema.ToString());
-    EXPECT_EQ("key[string NOT NULL]", schema.column(0).ToString());
-    EXPECT_EQ("uint32 NULLABLE", schema.column(1).TypeToString());
+    EXPECT_EQ("key STRING NOT NULL", schema.column(0).ToString());
+    EXPECT_EQ("UINT32 NULLABLE", schema.column(1).TypeToString());
   };
 
   ColumnSchema col1("key", STRING);
@@ -194,18 +200,18 @@ TEST_F(TestSchema, TestSchemaWithDecimal) {
                 sizeof(int64_t) + sizeof(int128_t),
             schema.byte_size());
 
-  EXPECT_EQ("Schema [\n"
-                "\tprimary key (key),\n"
-                "\tkey[string NOT NULL],\n"
-                "\tdecimal32val[decimal(9, 4) NOT NULL],\n"
-                "\tdecimal64val[decimal(18, 10) NULLABLE],\n"
-                "\tdecimal128val[decimal(38, 2) NULLABLE]\n"
-                "]",
+  EXPECT_EQ("(\n"
+            "    key STRING NOT NULL,\n"
+            "    decimal32val DECIMAL(9, 4) NOT NULL,\n"
+            "    decimal64val DECIMAL(18, 10) NULLABLE,\n"
+            "    decimal128val DECIMAL(38, 2) NULLABLE,\n"
+            "    PRIMARY KEY (key)\n"
+            ")",
             schema.ToString());
 
-  EXPECT_EQ("decimal(9, 4) NOT NULL", schema.column(1).TypeToString());
-  EXPECT_EQ("decimal(18, 10) NULLABLE", schema.column(2).TypeToString());
-  EXPECT_EQ("decimal(38, 2) NULLABLE", schema.column(3).TypeToString());
+  EXPECT_EQ("DECIMAL(9, 4) NOT NULL", schema.column(1).TypeToString());
+  EXPECT_EQ("DECIMAL(18, 10) NULLABLE", schema.column(2).TypeToString());
+  EXPECT_EQ("DECIMAL(38, 2) NULLABLE", schema.column(3).TypeToString());
 }
 
 // Test Schema::Equals respects decimal column attributes
@@ -535,22 +541,22 @@ TEST_F(TestSchema, TestCreateProjection) {
 
   // By names, without IDs
   ASSERT_OK(schema.CreateProjectionByNames({ "col1", "col2", "col4" }, &partial_schema));
-  EXPECT_EQ("Schema [\n"
-            "\tprimary key (),\n"
-            "\tcol1[string NOT NULL],\n"
-            "\tcol2[string NOT NULL],\n"
-            "\tcol4[string NOT NULL]\n"
-            "]",
+  EXPECT_EQ("(\n"
+            "    col1 STRING NOT NULL,\n"
+            "    col2 STRING NOT NULL,\n"
+            "    col4 STRING NOT NULL,\n"
+            "    PRIMARY KEY ()\n"
+            ")",
             partial_schema.ToString());
 
   // By names, with IDS
   ASSERT_OK(schema_with_ids.CreateProjectionByNames({ "col1", "col2", "col4" }, &partial_schema));
-  EXPECT_EQ(Substitute("Schema [\n"
-                       "\tprimary key (),\n"
-                       "\t$0:col1[string NOT NULL],\n"
-                       "\t$1:col2[string NOT NULL],\n"
-                       "\t$2:col4[string NOT NULL]\n"
-                       "]",
+  EXPECT_EQ(Substitute("(\n"
+                       "    $0:col1 STRING NOT NULL,\n"
+                       "    $1:col2 STRING NOT NULL,\n"
+                       "    $2:col4 STRING NOT NULL,\n"
+                       "    PRIMARY KEY ()\n"
+                       ")",
                        schema_with_ids.column_id(0),
                        schema_with_ids.column_id(1),
                        schema_with_ids.column_id(3)),
@@ -566,12 +572,12 @@ TEST_F(TestSchema, TestCreateProjection) {
                                                                  ColumnId(1000), // missing column
                                                                  schema_with_ids.column_id(3) },
                                                                &partial_schema));
-  EXPECT_EQ(Substitute("Schema [\n"
-                       "\tprimary key (),\n"
-                       "\t$0:col1[string NOT NULL],\n"
-                       "\t$1:col2[string NOT NULL],\n"
-                       "\t$2:col4[string NOT NULL]\n"
-                       "]",
+  EXPECT_EQ(Substitute("(\n"
+                       "    $0:col1 STRING NOT NULL,\n"
+                       "    $1:col2 STRING NOT NULL,\n"
+                       "    $2:col4 STRING NOT NULL,\n"
+                       "    PRIMARY KEY ()\n"
+                       ")",
                        schema_with_ids.column_id(0),
                        schema_with_ids.column_id(1),
                        schema_with_ids.column_id(3)),

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/common/schema.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/schema.cc b/src/kudu/common/schema.cc
index dbd9cc7..df04e67 100644
--- a/src/kudu/common/schema.cc
+++ b/src/kudu/common/schema.cc
@@ -28,6 +28,7 @@
 #include "kudu/util/malloc.h"
 #include "kudu/util/memory/arena.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 
 using std::string;
 using std::unordered_set;
@@ -72,10 +73,12 @@ string ColumnTypeAttributes::ToStringForType(DataType type) const {
 }
 
 string ColumnStorageAttributes::ToString() const {
-  return Substitute("encoding=$0, compression=$1, cfile_block_size=$2",
+  const string cfile_block_size_str =
+      cfile_block_size == 0 ? "" : Substitute(" $0", cfile_block_size);
+  return Substitute("$0 $1$2",
                     EncodingType_Name(encoding),
                     CompressionType_Name(compression),
-                    cfile_block_size);
+                    cfile_block_size_str);
 }
 
 Status ColumnSchema::ApplyDelta(const ColumnSchemaDelta& col_delta) {
@@ -115,17 +118,17 @@ Status ColumnSchema::ApplyDelta(const ColumnSchemaDelta& col_delta) {
   return Status::OK();
 }
 
-// TODO(wdb): include attributes_.ToString() -- need to fix unit tests
-// first
 string ColumnSchema::ToString() const {
-  return Substitute("$0[$1]",
+  return Substitute("$0 $1",
                     name_,
                     TypeToString());
 }
 
 string ColumnSchema::TypeToString() const {
+  string type_name = type_info_->name();
+  ToUpperCase(type_name, &type_name);
   return Substitute("$0$1 $2",
-                    type_info_->name(),
+                    type_name,
                     type_attributes().ToStringForType(type_info()->type()),
                     is_nullable_ ? "NULLABLE" : "NOT NULL");
 }
@@ -389,7 +392,7 @@ Status Schema::GetMappedReadProjection(const Schema& projection,
 }
 
 string Schema::ToString(ToStringMode mode) const {
-  if (cols_.empty()) return "Schema []";
+  if (cols_.empty()) return "()";
 
   vector<string> pk_strs;
   for (int i = 0; i < num_key_columns_; i++) {
@@ -407,11 +410,13 @@ string Schema::ToString(ToStringMode mode) const {
     }
   }
 
-  return StrCat("Schema [\n\tprimary key (",
+  return StrCat("(\n    ",
+                JoinStrings(col_strs, ",\n    "),
+                ",\n    ",
+                "PRIMARY KEY (",
                 JoinStrings(pk_strs, ", "),
-                "),\n\t",
-                JoinStrings(col_strs, ",\n\t"),
-                "\n]");
+                ")",
+                "\n)");
 }
 
 Status Schema::DecodeRowKey(Slice encoded_key,

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tablet/tablet_bootstrap-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tablet/tablet_bootstrap-test.cc b/src/kudu/tablet/tablet_bootstrap-test.cc
index dc2b83f..f5159d3 100644
--- a/src/kudu/tablet/tablet_bootstrap-test.cc
+++ b/src/kudu/tablet/tablet_bootstrap-test.cc
@@ -743,7 +743,7 @@ TEST_F(BootstrapTest, TestKudu2509) {
   ASSERT_STR_CONTAINS(status_msg,
       "Unable to bootstrap test tablet: Failed log replay.");
   ASSERT_STR_CONTAINS(status_msg,
-      "column string_val_extra[string NULLABLE] not present in tablet");
+      "column string_val_extra STRING NULLABLE not present in tablet");
 }
 
 } // namespace tablet

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tools/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/tools/CMakeLists.txt b/src/kudu/tools/CMakeLists.txt
index 890e0fd..c13fa65 100644
--- a/src/kudu/tools/CMakeLists.txt
+++ b/src/kudu/tools/CMakeLists.txt
@@ -50,6 +50,7 @@ target_link_libraries(kudu_tools_util
   consensus
   gutil
   kudu_client
+  kudu_client_test_util
   kudu_common
   kudu_fs
   kudu_util
@@ -126,7 +127,6 @@ target_link_libraries(kudu
   krpc
   ksck
   kudu_client
-  kudu_client_test_util
   kudu_common
   kudu_fs
   kudu_tools_rebalance

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tools/kudu-admin-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/kudu-admin-test.cc b/src/kudu/tools/kudu-admin-test.cc
index 7702777..046e8db 100644
--- a/src/kudu/tools/kudu-admin-test.cc
+++ b/src/kudu/tools/kudu-admin-test.cc
@@ -39,6 +39,7 @@
 #include "kudu/client/schema.h"
 #include "kudu/client/shared_ptr.h"
 #include "kudu/common/common.pb.h"
+#include "kudu/common/partial_row.h"
 #include "kudu/common/wire_protocol.pb.h"
 #include "kudu/consensus/consensus.pb.h"
 #include "kudu/consensus/metadata.pb.h"
@@ -1357,5 +1358,125 @@ TEST_F(AdminCliTest, TestListTablesDetail) {
   }
 }
 
+TEST_F(AdminCliTest, TestDescribeTable) {
+  FLAGS_num_tablet_servers = 1;
+  FLAGS_num_replicas = 1;
+
+  NO_FATALS(BuildAndStart());
+
+  // The default table has a range partition with only one partition.
+  string stdout, stderr;
+  Status s = RunKuduTool({
+    "table",
+    "describe",
+    cluster_->master()->bound_rpc_addr().ToString(),
+    kTableId
+  }, &stdout, &stderr);
+  ASSERT_TRUE(s.ok()) << ToolRunInfo(s, stdout, stderr);
+
+  ASSERT_STR_CONTAINS(
+      stdout,
+      "(\n"
+      "    key INT32 NOT NULL,\n"
+      "    int_val INT32 NOT NULL,\n"
+      "    string_val STRING NULLABLE,\n"
+      "    PRIMARY KEY (key)\n"
+      ")\n"
+      "RANGE (key) (\n"
+      "    PARTITION UNBOUNDED"
+      "\n"
+      ")\n"
+      "REPLICAS 1");
+
+  // Test a table with all types in its schema, multiple hash partitioning
+  // levels, multiple range partitions, and non-covered ranges.
+  const string kAnotherTableId = "TestAnotherTable";
+  KuduSchema schema;
+
+  // Build the schema.
+  {
+    KuduSchemaBuilder builder;
+    builder.AddColumn("key_hash0")->Type(KuduColumnSchema::INT32)->NotNull();
+    builder.AddColumn("key_hash1")->Type(KuduColumnSchema::INT32)->NotNull();
+    builder.AddColumn("key_hash2")->Type(KuduColumnSchema::INT32)->NotNull();
+    builder.AddColumn("key_range")->Type(KuduColumnSchema::INT32)->NotNull();
+    builder.AddColumn("int8_val")->Type(KuduColumnSchema::INT8);
+    builder.AddColumn("int16_val")->Type(KuduColumnSchema::INT16);
+    builder.AddColumn("int32_val")->Type(KuduColumnSchema::INT32);
+    builder.AddColumn("int64_val")->Type(KuduColumnSchema::INT64);
+    builder.AddColumn("timestamp_val")->Type(KuduColumnSchema::UNIXTIME_MICROS);
+    builder.AddColumn("string_val")->Type(KuduColumnSchema::STRING);
+    builder.AddColumn("bool_val")->Type(KuduColumnSchema::BOOL);
+    builder.AddColumn("float_val")->Type(KuduColumnSchema::FLOAT);
+    builder.AddColumn("double_val")->Type(KuduColumnSchema::DOUBLE);
+    builder.AddColumn("binary_val")->Type(KuduColumnSchema::BINARY);
+    builder.AddColumn("decimal_val")->Type(KuduColumnSchema::DECIMAL)
+        ->Precision(10)
+        ->Scale(4);
+    builder.SetPrimaryKey({ "key_hash0", "key_hash1", "key_hash2", "key_range" });
+    ASSERT_OK(builder.Build(&schema));
+  }
+
+  // Set up partitioning and create the table.
+  {
+    unique_ptr<KuduPartialRow> lower_bound0(schema.NewRow());
+    ASSERT_OK(lower_bound0->SetInt32("key_range", 0));
+    unique_ptr<KuduPartialRow> upper_bound0(schema.NewRow());
+    ASSERT_OK(upper_bound0->SetInt32("key_range", 1));
+    unique_ptr<KuduPartialRow> lower_bound1(schema.NewRow());
+    ASSERT_OK(lower_bound1->SetInt32("key_range", 2));
+    unique_ptr<KuduPartialRow> upper_bound1(schema.NewRow());
+    ASSERT_OK(upper_bound1->SetInt32("key_range", 3));
+    unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+    ASSERT_OK(table_creator->table_name(kAnotherTableId)
+             .schema(&schema)
+             .add_hash_partitions({ "key_hash0" }, 2)
+             .add_hash_partitions({ "key_hash1", "key_hash2" }, 3)
+             .set_range_partition_columns({ "key_range" })
+             .add_range_partition(lower_bound0.release(), upper_bound0.release())
+             .add_range_partition(lower_bound1.release(), upper_bound1.release())
+             .num_replicas(FLAGS_num_replicas)
+             .Create());
+  }
+
+  // OK, all that busywork is done. Test the describe output.
+  stdout.clear();
+  stderr.clear();
+  s = RunKuduTool({
+    "table",
+    "describe",
+    cluster_->master()->bound_rpc_addr().ToString(),
+    kAnotherTableId
+  }, &stdout, &stderr);
+  ASSERT_TRUE(s.ok()) << ToolRunInfo(s, stdout, stderr);
+
+  ASSERT_STR_CONTAINS(
+      stdout,
+      "(\n"
+      "    key_hash0 INT32 NOT NULL,\n"
+      "    key_hash1 INT32 NOT NULL,\n"
+      "    key_hash2 INT32 NOT NULL,\n"
+      "    key_range INT32 NOT NULL,\n"
+      "    int8_val INT8 NULLABLE,\n"
+      "    int16_val INT16 NULLABLE,\n"
+      "    int32_val INT32 NULLABLE,\n"
+      "    int64_val INT64 NULLABLE,\n"
+      "    timestamp_val UNIXTIME_MICROS NULLABLE,\n"
+      "    string_val STRING NULLABLE,\n"
+      "    bool_val BOOL NULLABLE,\n"
+      "    float_val FLOAT NULLABLE,\n"
+      "    double_val DOUBLE NULLABLE,\n"
+      "    binary_val BINARY NULLABLE,\n"
+      "    decimal_val DECIMAL(10, 4) NULLABLE,\n"
+      "    PRIMARY KEY (key_hash0, key_hash1, key_hash2, key_range)\n"
+      ")\n"
+      "HASH (key_hash0) PARTITIONS 2,\n"
+      "HASH (key_hash1, key_hash2) PARTITIONS 3,\n"
+      "RANGE (key_range) (\n"
+      "    PARTITION 0 <= VALUES < 1,\n"
+      "    PARTITION 2 <= VALUES < 3\n"
+      ")\n"
+      "REPLICAS 1");
+}
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tools/kudu-tool-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/kudu-tool-test.cc b/src/kudu/tools/kudu-tool-test.cc
index f6fabc9..a4ae0a4 100644
--- a/src/kudu/tools/kudu-tool-test.cc
+++ b/src/kudu/tools/kudu-tool-test.cc
@@ -1241,9 +1241,9 @@ TEST_F(ToolTest, TestLocalReplicaOps) {
     ASSERT_STR_CONTAINS(stdout, tablet_out);
     ASSERT_STR_CONTAINS(stdout, "Rowset ");
     ASSERT_STR_MATCHES(stdout, "Column block for column ID .*");
-    ASSERT_STR_CONTAINS(stdout, "key[int32 NOT NULL]");
-    ASSERT_STR_CONTAINS(stdout, "int_val[int32 NOT NULL]");
-    ASSERT_STR_CONTAINS(stdout, "string_val[string NULLABLE]");
+    ASSERT_STR_CONTAINS(stdout, "key INT32 NOT NULL");
+    ASSERT_STR_CONTAINS(stdout, "int_val INT32 NOT NULL");
+    ASSERT_STR_CONTAINS(stdout, "string_val STRING NULLABLE");
   }
   {
     string stdout;

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tools/tool_action_common.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_common.cc b/src/kudu/tools/tool_action_common.cc
index 2005059..099a32c 100644
--- a/src/kudu/tools/tool_action_common.cc
+++ b/src/kudu/tools/tool_action_common.cc
@@ -37,10 +37,15 @@
 #include <google/protobuf/util/json_util.h>
 
 #include "kudu/client/client-internal.h"  // IWYU pragma: keep
+#include "kudu/client/client-test-util.h"
 #include "kudu/client/client.h"
+#include "kudu/client/meta_cache.h"
 #include "kudu/client/shared_ptr.h"
 #include "kudu/common/common.pb.h"
+#include "kudu/common/partition.h"
+#include "kudu/common/partition_pruner.h"
 #include "kudu/common/row_operations.h"
+#include "kudu/common/scan_spec.h"
 #include "kudu/common/schema.h"
 #include "kudu/common/wire_protocol.h"
 #include "kudu/consensus/consensus.pb.h"
@@ -68,6 +73,7 @@
 #include "kudu/tserver/tserver.pb.h"
 #include "kudu/tserver/tserver_admin.proxy.h"   // IWYU pragma: keep
 #include "kudu/tserver/tserver_service.proxy.h" // IWYU pragma: keep
+#include "kudu/util/async_util.h"
 #include "kudu/util/faststring.h"
 #include "kudu/util/jsonwriter.h"
 #include "kudu/util/memory/arena.h"
@@ -127,9 +133,8 @@ namespace tools {
 
 using client::KuduClient;
 using client::KuduClientBuilder;
-using client::KuduTablet;
-using client::KuduTabletServer;
-using consensus::ConsensusServiceProxy;
+using client::KuduTable;
+using consensus::ConsensusServiceProxy; // NOLINT
 using consensus::ReplicateMsg;
 using log::LogEntryPB;
 using log::LogEntryReader;
@@ -853,5 +858,40 @@ Status ControlShellProtocol::SendMessage(const ControlShellRequestPB& message);
 template
 Status ControlShellProtocol::SendMessage(const ControlShellResponsePB& message);
 
+// The strategy for retrieving the partitions from the metacache is adapted
+// from KuduScanTokenBuilder::Data::Build.
+Status ListPartitions(const client::sp::shared_ptr<KuduTable>& table,
+                      vector<Partition>* partitions) {
+  DCHECK(table);
+  DCHECK(partitions);
+  auto* client = table->client();
+  const auto deadline = MonoTime::Now() + client->default_admin_operation_timeout();
+  PartitionPruner pruner;
+  const auto& schema_internal = SchemaFromKuduSchema(table->schema());
+  pruner.Init(schema_internal, table->partition_schema(), ScanSpec());
+  while (pruner.HasMorePartitionKeyRanges()) {
+    scoped_refptr<client::internal::RemoteTablet> tablet;
+    Synchronizer sync;
+    const string& partition_key = pruner.NextPartitionKey();
+    client->data_->meta_cache_->LookupTabletByKey(
+        table.get(),
+        partition_key,
+        deadline,
+        client::internal::MetaCache::LookupType::kLowerBound,
+        &tablet,
+        sync.AsStatusCallback());
+    Status s = sync.Wait();
+    if (s.IsNotFound()) {
+      // No more tablets.
+      break;
+    }
+    RETURN_NOT_OK(s);
+
+    partitions->emplace_back(tablet->partition());
+    pruner.RemovePartitionKeyRange(tablet->partition().partition_key_end());
+  }
+
+  return Status::OK();
+}
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tools/tool_action_common.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_common.h b/src/kudu/tools/tool_action_common.h
index 5d1e5a0..b71889c 100644
--- a/src/kudu/tools/tool_action_common.h
+++ b/src/kudu/tools/tool_action_common.h
@@ -37,10 +37,12 @@ class function;
 namespace kudu {
 
 class MonoDelta;
-class faststring;
+class Partition;
+class faststring; // NOLINT
 
 namespace client {
 class KuduClient;
+class KuduTable;
 } // namespace client
 
 namespace master {
@@ -140,6 +142,10 @@ std::string GetMasterAddresses(const client::KuduClient& client);
 // 'patterns' is empty.
 bool MatchesAnyPattern(const std::vector<std::string>& patterns, const std::string& str);
 
+// Populates `partitions` with the partitions of the table `table`.
+Status ListPartitions(const client::sp::shared_ptr<client::KuduTable>& table,
+                      std::vector<Partition>* partitions);
+
 // A table of data to present to the user.
 //
 // Supports formatting based on the --format flag.

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tools/tool_action_table.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_table.cc b/src/kudu/tools/tool_action_table.cc
index c15c69d..33d6eb7 100644
--- a/src/kudu/tools/tool_action_table.cc
+++ b/src/kudu/tools/tool_action_table.cc
@@ -15,20 +15,24 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <algorithm>
+#include <cstdint>
 #include <iostream>
 #include <memory>
 #include <string>
 #include <unordered_map>
-#include <utility>
 #include <vector>
 
 #include <gflags/gflags.h>
 #include <gflags/gflags_declare.h>
 
+#include "kudu/client/client-test-util.h"
 #include "kudu/client/client.h"
 #include "kudu/client/replica_controller-internal.h"
 #include "kudu/client/schema.h"
 #include "kudu/client/shared_ptr.h"
+#include "kudu/common/partition.h"
+#include "kudu/common/schema.h"
 #include "kudu/gutil/map-util.h"
 #include "kudu/gutil/stl_util.h"
 #include "kudu/gutil/strings/split.h"
@@ -130,6 +134,48 @@ Status DeleteTable(const RunnerContext& context) {
   return client->DeleteTableInCatalogs(table_name, FLAGS_modify_external_catalogs);
 }
 
+Status DescribeTable(const RunnerContext& context) {
+  client::sp::shared_ptr<KuduClient> client;
+  RETURN_NOT_OK(CreateKuduClient(context, &client));
+
+  const string& table_name = FindOrDie(context.required_args, kTableNameArg);
+  client::sp::shared_ptr<KuduTable> table;
+  RETURN_NOT_OK(client->OpenTable(table_name, &table));
+
+  // The schema.
+  const client::KuduSchema& schema = table->schema();
+  cout << "TABLE " << table_name << " " << schema.ToString() << endl;
+
+  // The partition schema with current range partitions.
+  vector<Partition> partitions;
+  RETURN_NOT_OK_PREPEND(ListPartitions(table, &partitions),
+                        "failed to retrieve current partitions");
+  const auto& schema_internal = client::SchemaFromKuduSchema(schema);
+  const auto& partition_schema = table->partition_schema();
+  vector<string> partition_strs;
+  for (const auto& partition : partitions) {
+    // Deduplicate by hash bucket to get a unique entry per range partition.
+    const auto& hash_buckets = partition.hash_buckets();
+    if (!std::all_of(hash_buckets.begin(),
+                     hash_buckets.end(),
+                     [](int32_t bucket) { return bucket == 0; })) {
+      continue;
+    }
+    auto range_partition_str =
+        partition_schema.RangePartitionDebugString(partition.range_key_start(),
+                                                   partition.range_key_end(),
+                                                   schema_internal);
+    partition_strs.push_back(std::move(range_partition_str));
+  }
+  cout << partition_schema.DisplayString(schema_internal, partition_strs)
+       << endl;
+
+  // Finally, the replication factor.
+  cout << "REPLICAS " << table->num_replicas() << endl;
+
+  return Status::OK();
+}
+
 Status RenameTable(const RunnerContext& context) {
   const string& table_name = FindOrDie(context.required_args, kTableNameArg);
   const string& new_table_name = FindOrDie(context.required_args, kNewTableNameArg);
@@ -171,24 +217,13 @@ unique_ptr<Mode> BuildTableMode() {
       .AddOptionalParameter("modify_external_catalogs")
       .Build();
 
-  unique_ptr<Action> rename_table =
-      ActionBuilder("rename_table", &RenameTable)
-      .Description("Rename a table")
+  unique_ptr<Action> describe_table =
+      ActionBuilder("describe", &DescribeTable)
+      .Description("Describe a table")
       .AddRequiredParameter({ kMasterAddressesArg, kMasterAddressesArgDesc })
-      .AddRequiredParameter({ kTableNameArg, "Name of the table to rename" })
-      .AddRequiredParameter({ kNewTableNameArg, "New table name" })
-      .AddOptionalParameter("modify_external_catalogs")
+      .AddRequiredParameter({ kTableNameArg, "Name of the table to describe" })
       .Build();
 
-  unique_ptr<Action> rename_column =
-      ActionBuilder("rename_column", &RenameColumn)
-          .Description("Rename a column")
-          .AddRequiredParameter({ kMasterAddressesArg, kMasterAddressesArgDesc })
-          .AddRequiredParameter({ kTableNameArg, "Name of the table to alter" })
-          .AddRequiredParameter({ kColumnNameArg, "Name of the table column to rename" })
-          .AddRequiredParameter({ kNewColumnNameArg, "New column name" })
-          .Build();
-
   unique_ptr<Action> list_tables =
       ActionBuilder("list", &ListTables)
       .Description("List tables")
@@ -197,12 +232,31 @@ unique_ptr<Mode> BuildTableMode() {
       .AddOptionalParameter("list_tablets")
       .Build();
 
+  unique_ptr<Action> rename_column =
+      ActionBuilder("rename_column", &RenameColumn)
+      .Description("Rename a column")
+      .AddRequiredParameter({ kMasterAddressesArg, kMasterAddressesArgDesc })
+      .AddRequiredParameter({ kTableNameArg, "Name of the table to alter" })
+      .AddRequiredParameter({ kColumnNameArg, "Name of the table column to rename" })
+      .AddRequiredParameter({ kNewColumnNameArg, "New column name" })
+      .Build();
+
+  unique_ptr<Action> rename_table =
+      ActionBuilder("rename_table", &RenameTable)
+      .Description("Rename a table")
+      .AddRequiredParameter({ kMasterAddressesArg, kMasterAddressesArgDesc })
+      .AddRequiredParameter({ kTableNameArg, "Name of the table to rename" })
+      .AddRequiredParameter({ kNewTableNameArg, "New table name" })
+      .AddOptionalParameter("modify_external_catalogs")
+      .Build();
+
   return ModeBuilder("table")
       .Description("Operate on Kudu tables")
       .AddAction(std::move(delete_table))
-      .AddAction(std::move(rename_table))
-      .AddAction(std::move(rename_column))
+      .AddAction(std::move(describe_table))
       .AddAction(std::move(list_tables))
+      .AddAction(std::move(rename_column))
+      .AddAction(std::move(rename_table))
       .Build();
 }
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/614b446e/src/kudu/tserver/tablet_server-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tablet_server-test.cc b/src/kudu/tserver/tablet_server-test.cc
index 4a19318..3a734ea 100644
--- a/src/kudu/tserver/tablet_server-test.cc
+++ b/src/kudu/tserver/tablet_server-test.cc
@@ -407,7 +407,7 @@ TEST_F(TabletServerTest, TestWebPages) {
   ASSERT_OK(c.FetchURL(Substitute("http://$0/tablet?id=$1", addr, kTabletId),
                        &buf));
   ASSERT_STR_CONTAINS(buf.ToString(), "key");
-  ASSERT_STR_CONTAINS(buf.ToString(), "string NULLABLE");
+  ASSERT_STR_CONTAINS(buf.ToString(), "STRING NULLABLE");
 
   // Test fetching metrics.
   // Fetching metrics has the side effect of retiring metrics, but not in a single pass.
@@ -719,7 +719,7 @@ TEST_F(TabletServerTest, TestInsert) {
     Status s = StatusFromPB(resp.error().status());
     EXPECT_TRUE(s.IsInvalidArgument());
     ASSERT_STR_CONTAINS(s.ToString(),
-                        "Client missing required column: key[int32 NOT NULL]");
+                        "Client missing required column: key INT32 NOT NULL");
     req.clear_row_operations();
   }
 
@@ -1117,7 +1117,7 @@ TEST_F(TabletServerTest, TestInvalidWriteRequest_BadSchema) {
     ASSERT_TRUE(resp.has_error());
     ASSERT_EQ(TabletServerErrorPB::MISMATCHED_SCHEMA, resp.error().code());
     ASSERT_STR_CONTAINS(resp.error().status().message(),
-                        "Client provided column col_doesnt_exist[int32 NOT NULL]"
+                        "Client provided column col_doesnt_exist INT32 NOT NULL"
                         " not present in tablet");
   }
 
@@ -2388,8 +2388,8 @@ TEST_F(TabletServerTest, TestInvalidScanRequest_BadProjectionTypes) {
                      0));
   VerifyScanRequestFailure(projection,
                            TabletServerErrorPB::MISMATCHED_SCHEMA,
-                           "The column 'int_val' must have type int32 NOT "
-                           "NULL found int32 NULLABLE");
+                           "The column 'int_val' must have type INT32 NOT "
+                           "NULL found INT32 NULLABLE");
 
   // Verify mismatched nullability for the nullable string field
   ASSERT_OK(
@@ -2397,8 +2397,8 @@ TEST_F(TabletServerTest, TestInvalidScanRequest_BadProjectionTypes) {
                      0));
   VerifyScanRequestFailure(projection,
                            TabletServerErrorPB::MISMATCHED_SCHEMA,
-                           "The column 'string_val' must have type string "
-                           "NULLABLE found string NOT NULL");
+                           "The column 'string_val' must have type STRING "
+                           "NULLABLE found STRING NOT NULL");
 
   // Verify mismatched type for the not-null int field
   ASSERT_OK(
@@ -2406,8 +2406,8 @@ TEST_F(TabletServerTest, TestInvalidScanRequest_BadProjectionTypes) {
                      0));
   VerifyScanRequestFailure(projection,
                            TabletServerErrorPB::MISMATCHED_SCHEMA,
-                           "The column 'int_val' must have type int32 NOT "
-                           "NULL found int16 NOT NULL");
+                           "The column 'int_val' must have type INT32 NOT "
+                           "NULL found INT16 NOT NULL");
 
   // Verify mismatched type for the nullable string field
   ASSERT_OK(projection.Reset(
@@ -2415,8 +2415,8 @@ TEST_F(TabletServerTest, TestInvalidScanRequest_BadProjectionTypes) {
         0));
   VerifyScanRequestFailure(projection,
                            TabletServerErrorPB::MISMATCHED_SCHEMA,
-                           "The column 'string_val' must have type string "
-                           "NULLABLE found int32 NULLABLE");
+                           "The column 'string_val' must have type STRING "
+                           "NULLABLE found INT32 NULLABLE");
 }
 
 // Test that passing a projection with Column IDs throws an exception.