[LIVY-754][THRIFT] Encode precision and scale for decimal type.

## What changes were proposed in this pull request?

When a `org.apache.livy.thriftserver.session.DataType.DECIMAL` is converted to a `org.apache.hive.service.rpc.thrift.TTypeDesc` for sending a Thrift response to a client request for result set metadata, the `TTypeDesc` contains a `TPrimitiveTypeEntry(TTypeId.DECIMAL_TYPE)` without `TTypeQualifiers` (which are needed to capture the precision and scale).
With this change, we include the qualifiers in the `TPrimitiveTypeEntry`. We use both the name and the `DataType` of a field type to construct the `TTypeDesc`. We are able to do this without changing the existing internal representation for data types because we can obtain the precision and scale from the name of the decimal type.

## How was this patch tested?

Use beeline to connect to the Thrift server. Do a select from a table with a column of decimal type.
Also extended an existing integration test.

Author: Wing Yew Poon <wypoon@cloudera.com>

Closes #288 from wypoon/LIVY-754.
diff --git a/thriftserver/server/src/main/scala/org/apache/livy/thriftserver/types/Schema.scala b/thriftserver/server/src/main/scala/org/apache/livy/thriftserver/types/Schema.scala
index 0f4c642..5c02159 100644
--- a/thriftserver/server/src/main/scala/org/apache/livy/thriftserver/types/Schema.scala
+++ b/thriftserver/server/src/main/scala/org/apache/livy/thriftserver/types/Schema.scala
@@ -17,7 +17,7 @@
 
 package org.apache.livy.thriftserver.types
 
-import org.apache.hive.service.rpc.thrift.{TColumnDesc, TPrimitiveTypeEntry, TTableSchema, TTypeDesc, TTypeEntry, TTypeId}
+import org.apache.hive.service.rpc.thrift._
 
 import org.apache.livy.thriftserver.session.DataType
 
@@ -41,7 +41,7 @@
     case "float" => DataType.FLOAT
     case "double" => DataType.DOUBLE
     case "binary" => DataType.BINARY
-    case _ if name.contains("decimal") => DataType.DECIMAL
+    case _ if name.startsWith("decimal") => DataType.DECIMAL
     case "timestamp" => DataType.TIMESTAMP
     case "date" => DataType.DATE
     case _ => DataType.STRING
@@ -88,12 +88,12 @@
     val tColumnDesc = new TColumnDesc
     tColumnDesc.setColumnName(field.name)
     tColumnDesc.setComment(field.comment)
-    tColumnDesc.setTypeDesc(toTTypeDesc(field.fieldType.dataType))
+    tColumnDesc.setTypeDesc(toTTypeDesc(field.fieldType.name, field.fieldType.dataType))
     tColumnDesc.setPosition(index)
     tColumnDesc
   }
 
-  private def toTTypeDesc(dt: DataType): TTypeDesc = {
+  private def toTTypeDesc(name: String, dt: DataType): TTypeDesc = {
     val typeId = dt match {
       case DataType.BOOLEAN => TTypeId.BOOLEAN_TYPE
       case DataType.BYTE => TTypeId.TINYINT_TYPE
@@ -109,9 +109,38 @@
       case _ => TTypeId.STRING_TYPE
     }
     val primitiveEntry = new TPrimitiveTypeEntry(typeId)
+    if (dt == DataType.DECIMAL) {
+      val qualifiers = getDecimalQualifiers(name)
+      primitiveEntry.setTypeQualifiers(qualifiers)
+    }
     val entry = TTypeEntry.primitiveEntry(primitiveEntry)
     val desc = new TTypeDesc
     desc.addToTypes(entry)
     desc
   }
+
+  private def getDecimalQualifiers(name: String): TTypeQualifiers = {
+    // name can be one of
+    // 1. decimal
+    // 2. decimal(p)
+    // 3. decimal(p, s)
+    val (precision, scale) =
+      if (name == "decimal") {
+        (10, 0)
+      } else {
+        val suffix = name.substring("decimal".length)
+        require(suffix.startsWith("(") && suffix.endsWith(")"),
+          name + " is not of the form decimal(<precision>,<scale>)")
+        val parts = suffix.substring(1, suffix.length - 1).split(",").map(_.trim.toInt)
+        (parts(0), parts.lift(1).getOrElse(0))
+      }
+    val qMap = new java.util.HashMap[String, TTypeQualifierValue]
+    val pVal = new TTypeQualifierValue
+    pVal.setI32Value(precision)
+    qMap.put(TCLIServiceConstants.PRECISION, pVal)
+    val sVal = new TTypeQualifierValue
+    sVal.setI32Value(scale)
+    qMap.put(TCLIServiceConstants.SCALE, sVal)
+    new TTypeQualifiers(qMap)
+  }
 }
diff --git a/thriftserver/server/src/test/scala/org/apache/livy/thriftserver/ThriftServerSuites.scala b/thriftserver/server/src/test/scala/org/apache/livy/thriftserver/ThriftServerSuites.scala
index 19abb0d..44fd970 100644
--- a/thriftserver/server/src/test/scala/org/apache/livy/thriftserver/ThriftServerSuites.scala
+++ b/thriftserver/server/src/test/scala/org/apache/livy/thriftserver/ThriftServerSuites.scala
@@ -47,7 +47,9 @@
         "cast('varchar_val' as varchar(20))," +
         "cast('char_val' as char(20))," +
         "cast('2018-08-06 09:11:15' as timestamp)," +
-        "cast('2018-08-06' as date)")
+        "cast('2018-08-06' as date)," +
+        "cast(1234567890 as decimal(10))," +
+        "cast(1234567890 as decimal)")
 
     val rsMetaData = resultSet.getMetaData()
 
@@ -73,6 +75,8 @@
 
     assert(resultSet.getBigDecimal(7).doubleValue() == 7.7)
     assert(rsMetaData.getColumnTypeName(7) == "decimal")
+    assert(rsMetaData.getPrecision(7) == 10)
+    assert(rsMetaData.getScale(7) == 1)
 
     assert(resultSet.getBoolean(8) == true)
     assert(rsMetaData.getColumnTypeName(8) == "boolean")
@@ -99,6 +103,16 @@
       compareTo(Date.valueOf("2018-08-06")) == 0)
     assert(rsMetaData.getColumnTypeName(14) == "date")
 
+    assert(resultSet.getBigDecimal(15).intValue() == 1234567890)
+    assert(rsMetaData.getColumnTypeName(15) == "decimal")
+    assert(rsMetaData.getPrecision(15) == 10)
+    assert(rsMetaData.getScale(15) == 0)
+
+    assert(resultSet.getBigDecimal(16).intValue() == 1234567890)
+    assert(rsMetaData.getColumnTypeName(16) == "decimal")
+    assert(rsMetaData.getPrecision(16) == 10)
+    assert(rsMetaData.getScale(16) == 0)
+
     assert(!resultSet.next())
 
     val resultSetWithNulls = statement.executeQuery(