HAWQ-1772. Add INTERVAL_TO_TEXT function
diff --git a/depends/dbcommon/src/dbcommon/function/func-kind.h b/depends/dbcommon/src/dbcommon/function/func-kind.h
index 18ba651..36402ac 100644
--- a/depends/dbcommon/src/dbcommon/function/func-kind.h
+++ b/depends/dbcommon/src/dbcommon/function/func-kind.h
@@ -195,6 +195,7 @@
   TEXT_TO_CHAR,
   INT_TO_CHAR,
   DOUBLE_TO_TIMESTAMP,
+  INTERVAL_TO_TEXT,
 
   // date/time related functions
   DATE_TO_TIMESTAMP,
diff --git a/depends/dbcommon/src/dbcommon/function/func.cc b/depends/dbcommon/src/dbcommon/function/func.cc
index bb1ab0b..06ae53e 100644
--- a/depends/dbcommon/src/dbcommon/function/func.cc
+++ b/depends/dbcommon/src/dbcommon/function/func.cc
@@ -464,6 +464,7 @@
   FuncEntryArray.push_back({TEXT_TO_DOUBLE, "text_double", DOUBLEID, {STRINGID}, text_to_float8});
   FuncEntryArray.push_back{TEXT_TO_DECIMAL, "text_decimal", DECIMALNEWID, {STRINGID}, textToDecimal});
   FuncEntryArray.push_back({TO_NUMBER, "to_number", DECIMALNEWID, {STRINGID, STRINGID}, toNumber});
+  FuncEntryArray.push_back({INTERVAL_TO_TEXT, "interval_text", STRINGID, {INTERVALID}, intervalToText});
 
   FuncEntryArray.push_back({BOOLEAN_TO_SMALLINT, "boolean_smallint", SMALLINTID, {BOOLEANID}, bool_to_int2});
   FuncEntryArray.push_back({BOOLEAN_TO_INT, "boolean_int", INTID, {BOOLEANID}, bool_to_int4});
diff --git a/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.cc b/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.cc
index 4cd91b4..7a0b20b 100644
--- a/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.cc
+++ b/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.cc
@@ -38,6 +38,7 @@
 #include "dbcommon/type/type-util.h"
 #include "dbcommon/utils/macro.h"
 #include "dbcommon/utils/string-util.h"
+#include "dbcommon/utils/timezone-util.h"
 
 namespace dbcommon {
 
@@ -563,4 +564,128 @@
   return text_to_Float<double>(params, size);
 }
 
+Datum intervalToText(Datum *params, uint64_t size) {
+  auto inttotext = [](char *&ptr, uint32_t val) {
+    if (val == 0) {
+      *(ptr++) = '0';
+      return;
+    }
+    char str[] = "0123456789";
+    uint32_t tempval = val;
+    std::vector<char> tempvec;
+    while (tempval >= 10) {
+      tempval /= 10;
+      ++ptr;
+    }
+    char *ptemp = ptr++;
+    while (val > 0) {
+      *(ptemp--) = str[val % 10];
+      val /= 10;
+    }
+  };
+
+  auto intervalCastText = [&inttotext](ByteBuffer &buf,
+                                       IntervalVar in) -> text {
+    int32_t iyear, imonth, iday;
+    iyear = in.month / MONTHS_PER_YEAR;
+    imonth = in.month % MONTHS_PER_YEAR;
+    iday = in.day;
+
+    int32_t ihour, iminute, isecond, imsecond;
+    ihour = in.timeOffset / USECS_PER_HOUR;
+    in.timeOffset -= ihour * USECS_PER_HOUR;
+    iminute = in.timeOffset / USECS_PER_MINUTE;
+    in.timeOffset -= iminute * USECS_PER_MINUTE;
+    isecond = in.timeOffset / USECS_PER_SEC;
+    imsecond = in.timeOffset - (isecond * USECS_PER_SEC);
+
+    uint32_t lenBefore = buf.size();
+    buf.resize(lenBefore + 128);
+    char *pStart = buf.data() + lenBefore;
+    char *ptemp = buf.data() + lenBefore;
+    bool isBeforeNeg = false;
+    bool isFirst = false;
+    if (iyear) {
+      if (iyear < 0)
+        *(ptemp++) = '-';
+      else if (isBeforeNeg)
+        *(ptemp++) = '+';
+      isBeforeNeg = (iyear < 0);
+      isFirst = true;
+      inttotext(ptemp, abs(iyear));
+      *(ptemp++) = ' ';
+      memcpy(ptemp, "year", 4);
+      ptemp += 4;
+      if (iyear > 1) *(ptemp++) = 's';
+    }
+    if (imonth) {
+      if (isFirst) *(ptemp++) = ' ';
+      isFirst = true;
+      if (imonth < 0)
+        *(ptemp++) = '-';
+      else if (isBeforeNeg)
+        *(ptemp++) = '+';
+      isBeforeNeg = (imonth < 0);
+      inttotext(ptemp, abs(imonth));
+      *(ptemp++) = ' ';
+      memcpy(ptemp, "month", 5);
+      ptemp += 5;
+      if (imonth > 1) *(ptemp++) = 's';
+    }
+    if (iday) {
+      if (isFirst) *(ptemp++) = ' ';
+      isFirst = true;
+      if (iday < 0)
+        *(ptemp++) = '-';
+      else if (isBeforeNeg)
+        *(ptemp++) = '+';
+      isBeforeNeg = (iday < 0);
+      inttotext(ptemp, abs(iday));
+      *(ptemp++) = ' ';
+      memcpy(ptemp, "day", 3);
+      ptemp += 3;
+      if (abs(iday) > 1) *(ptemp++) = 's';
+    }
+    if (ihour || iminute || isecond || imsecond) {
+      if (isFirst) *(ptemp++) = ' ';
+      isFirst = true;
+      if (ihour < 0 || iminute < 0 || isecond < 0 || imsecond < 0)
+        *(ptemp++) = '-';
+      else if (isBeforeNeg)
+        *(ptemp++) = '+';
+      ihour = abs(ihour);
+      iminute = abs(iminute);
+      isecond = abs(isecond);
+      imsecond = abs(imsecond);
+      if (ihour < 10) *(ptemp++) = '0';
+      inttotext(ptemp, ihour);
+      *(ptemp++) = ':';
+      if (iminute < 10) *(ptemp++) = '0';
+      inttotext(ptemp, iminute);
+      *(ptemp++) = ':';
+      if (isecond < 10) *(ptemp++) = '0';
+      inttotext(ptemp, isecond);
+
+      if (imsecond) {
+        int64_t power_of_ten = 100000;
+        *(ptemp++) = '.';
+        while (imsecond / power_of_ten == 0) {
+          *(ptemp++) = '0';
+          power_of_ten /= 10;
+        }
+        while (imsecond % 10 == 0) {
+          imsecond /= 10;
+        }
+        inttotext(ptemp, imsecond);
+      }
+    }
+    int32_t curLen = ptemp - pStart;
+    buf.resize(lenBefore + curLen);
+
+    return text(nullptr, curLen);
+  };
+
+  return one_param_bind<text, IntervalVar>(params, size, intervalCastText);
+}
+
 }  // namespace dbcommon
diff --git a/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.h b/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.h
index beddb3b..ac24e63 100644
--- a/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.h
+++ b/depends/dbcommon/src/dbcommon/function/typecast-texttonum-func.h
@@ -33,6 +33,7 @@
 Datum text_to_float8(Datum *params, uint64_t size);
 Datum textToDecimal(Datum *params, uint64_t size);
 Datum toNumber(Datum *params, uint64_t size);
+Datum intervalToText(Datum *params, uint64_t size);
 
 }  // namespace dbcommon
 
diff --git a/depends/dbcommon/test/unit/function/test-typecast-texttonum-func.cc b/depends/dbcommon/test/unit/function/test-typecast-texttonum-func.cc
index e9fae7c..7d81e12 100644
--- a/depends/dbcommon/test/unit/function/test-typecast-texttonum-func.cc
+++ b/depends/dbcommon/test/unit/function/test-typecast-texttonum-func.cc
@@ -104,4 +104,12 @@
                            "e33 1.23eq2 1.2i3e2 1.23e2i2"},
                           ERRCODE_INVALID_TEXT_REPRESENTATION}));
 
+INSTANTIATE_TEST_CASE_P(
+    interval_to_text, TestFunction,
+    ::testing::Values(TestFunctionEntry{
+        FuncKind::INTERVAL_TO_TEXT,
+        "Vector{delimiter=x}: 4 years 2 months 560 days 00:00:30x-4 year -2 "
+        "month +560 days 00:00:00.003xNULL",
+        {"Vector: 50:560:30000000 -50:560:3000 NULL"}}));
+
 }  // namespace dbcommon