You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by ta...@apache.org on 2018/01/24 07:03:25 UTC

[5/9] impala git commit: IMPALA-6059: Enhance ltrim()/rtrim() functions to trim any set of characters.

IMPALA-6059: Enhance ltrim()/rtrim() functions to trim any set of
characters.

This patch generalizes ltrim()/rtrim() functions to accept a second
argument that specifies the set of characters to be removed from the
leading/trailing end of the target string:

ltrim(string text[, characters text])
rtrim(string text[, characters text])

A common string trimming method has been added to StringFunctions,
which is called from the general ltrim/rtrim/btrim functions. The
functions also share prepare and close operations.

New StringFunctions tests have been added to ExprTest for the new
forms of ltrim() and rtrim(). New tests to cover handling of special
characters have also been added.

Note that our string handling functions only work with the ASCII
character set. Handling of other character sets lies outside the
scope of this patch.

The existing ltrim()/rtrim()/trim() functions that take only one
argument have been updated to use the more general methods.

Testing: Queries like the following were run on a 1.5-billion row
tpch_parquet.lineitem table, with the old and new implementations
to ensure there is no performance regression:

  1. select count(trim(l_shipinstruct)), count(trim(l_returnflag)), ...
  2. select count(*) from t where trim(l_shipinstruct) = '' and ...

Change-Id: I8a5ae3f59762e70c3268a01e14ed57a9e36b8d79
Reviewed-on: http://gerrit.cloudera.org:8080/8349
Reviewed-by: Michael Ho <kw...@cloudera.com>
Tested-by: Impala Public Jenkins


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

Branch: refs/heads/master
Commit: c2d27ca823f30636c97c7a0dc22cddf35560236d
Parents: f68aa87
Author: Zoram Thanga <zo...@cloudera.com>
Authored: Fri Oct 20 14:20:26 2017 -0700
Committer: Impala Public Jenkins <im...@gerrit.cloudera.org>
Committed: Tue Jan 23 23:44:46 2018 +0000

----------------------------------------------------------------------
 be/src/exprs/expr-test.cc                    |  43 ++++++
 be/src/exprs/string-functions-ir.cc          | 153 +++++++++++-----------
 be/src/exprs/string-functions.h              |  44 +++++--
 common/function-registry/impala_functions.py |  26 +++-
 4 files changed, 178 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/impala/blob/c2d27ca8/be/src/exprs/expr-test.cc
----------------------------------------------------------------------
diff --git a/be/src/exprs/expr-test.cc b/be/src/exprs/expr-test.cc
index 2df1433..c6e81f1 100644
--- a/be/src/exprs/expr-test.cc
+++ b/be/src/exprs/expr-test.cc
@@ -3744,6 +3744,46 @@ TEST_F(ExprTest, StringFunctions) {
   TestStringValue("rtrim('abc  defg')", "abc  defg");
   TestIsNull("rtrim(NULL)", TYPE_STRING);
 
+  TestStringValue("ltrim('%%%%%abcdefg%%%%%', '%')", "abcdefg%%%%%");
+  TestStringValue("ltrim('%%%%%abcdefg', '%')", "abcdefg");
+  TestStringValue("ltrim('abcdefg%%%%%', '%')", "abcdefg%%%%%");
+  TestStringValue("ltrim('%%%%%abc%%defg', '%')", "abc%%defg");
+  TestStringValue("ltrim('abcdefg', 'abc')", "defg");
+  TestStringValue("ltrim('    abcdefg', ' ')", "abcdefg");
+  TestStringValue("ltrim('abacdefg', 'abc')", "defg");
+  TestStringValue("ltrim('abacdefgcab', 'abc')", "defgcab");
+  TestStringValue("ltrim('abcacbbacbcacabcba', 'abc')", "");
+  TestStringValue("ltrim('', 'abc')", "");
+  TestStringValue("ltrim('     ', 'abc')", "     ");
+  TestIsNull("ltrim(NULL, 'abc')", TYPE_STRING);
+  TestStringValue("ltrim('abcdefg', NULL)", "abcdefg");
+  TestStringValue("ltrim('abcdefg', '')", "abcdefg");
+  TestStringValue("ltrim('abcdabcdabc', 'abc')", "dabcdabc");
+  TestStringValue("ltrim('aaaaaaaaa', 'a')", "");
+  TestStringValue("ltrim('abcdefg', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabg')", "cdefg");
+  TestStringValue("ltrim('æeioü','æü')", "eioü");
+  TestStringValue("ltrim('\\\\abcdefg', 'a\\\\')", "bcdefg");
+
+  TestStringValue("rtrim('%%%%%abcdefg%%%%%', '%')", "%%%%%abcdefg");
+  TestStringValue("rtrim('%%%%%abcdefg', '%')", "%%%%%abcdefg");
+  TestStringValue("rtrim('abcdefg%%%%%', '%')", "abcdefg");
+  TestStringValue("rtrim('abc%%defg%%%%%', '%')", "abc%%defg");
+  TestStringValue("rtrim('abcdefg', 'abc')", "abcdefg");
+  TestStringValue("rtrim('abcdefg    ', ' ')", "abcdefg");
+  TestStringValue("rtrim('abacdefg', 'efg')", "abacd");
+  TestStringValue("rtrim('abacdefgcab', 'abc')", "abacdefg");
+  TestStringValue("rtrim('abcacbbacbcacabcba', 'abc')", "");
+  TestStringValue("rtrim('', 'abc')", "");
+  TestStringValue("rtrim('     ', 'abc')", "     ");
+  TestIsNull("rtrim(NULL, 'abc')", TYPE_STRING);
+  TestStringValue("rtrim('abcdefg', NULL)", "abcdefg");
+  TestStringValue("rtrim('abcdefg', '')", "abcdefg");
+  TestStringValue("rtrim('abcdabcdabc', 'abc')", "abcdabcd");
+  TestStringValue("rtrim('aaaaaaaaa', 'a')", "");
+  TestStringValue("rtrim('abcdefg', 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeefg')", "abcd");
+  TestStringValue("rtrim('æeioü','æü')", "æeio");
+  TestStringValue("rtrim('abcdefg\\\\', 'g\\\\')", "abcdef");
+
   TestStringValue("btrim('     abcdefg   ')", "abcdefg");
   TestStringValue("btrim('     abcdefg')", "abcdefg");
   TestStringValue("btrim('abcdefg      ')", "abcdefg");
@@ -3762,11 +3802,14 @@ TEST_F(ExprTest, StringFunctions) {
   TestStringValue("btrim('abacdefgcab', 'abc')", "defg");
   TestStringValue("btrim('abcacbbacbcacabcba', 'abc')", "");
   TestStringValue("btrim('', 'abc')", "");
+  TestStringValue("btrim('     ', 'abc')", "     ");
+  TestIsNull("btrim(NULL, 'abc')", TYPE_STRING);
   TestStringValue("btrim('abcdefg', NULL)", "abcdefg");
   TestStringValue("btrim('abcdabcdabc', 'abc')", "dabcd");
   TestStringValue("btrim('aaaaaaaaa', 'a')", "");
   TestStringValue("btrim('abcdefg', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabg')", "cdef");
   TestStringValue("btrim('æeioü','æü')", "eio");
+  TestStringValue("btrim('\\\\abcdefg\\\\', 'ag\\\\')", "bcdef");
 
   TestStringValue("space(0)", "");
   TestStringValue("space(-1)", "");

http://git-wip-us.apache.org/repos/asf/impala/blob/c2d27ca8/be/src/exprs/string-functions-ir.cc
----------------------------------------------------------------------
diff --git a/be/src/exprs/string-functions-ir.cc b/be/src/exprs/string-functions-ir.cc
index 49bc8c1..50378bd 100644
--- a/be/src/exprs/string-functions-ir.cc
+++ b/be/src/exprs/string-functions-ir.cc
@@ -21,7 +21,6 @@
 #include <stdint.h>
 #include <re2/re2.h>
 #include <re2/stringpiece.h>
-#include <bitset>
 
 #include <boost/static_assert.hpp>
 
@@ -400,41 +399,97 @@ StringVal StringFunctions::Translate(FunctionContext* context, const StringVal&
   return result;
 }
 
-StringVal StringFunctions::Trim(FunctionContext* context, const StringVal& str) {
+void StringFunctions::TrimPrepare(
+    FunctionContext* context, FunctionContext::FunctionStateScope scope) {
+  if (scope != FunctionContext::THREAD_LOCAL) return;
+  // Create a bitset to hold the unique characters to trim.
+  bitset<256>* unique_chars = new bitset<256>();
+  context->SetFunctionState(scope, unique_chars);
+  // If the caller didn't specify the set of characters to trim, it means
+  // that we're only trimming whitespace. Return early in that case.
+  // There can be either 1 or 2 arguments.
+  DCHECK(context->GetNumArgs() == 1 || context->GetNumArgs() == 2);
+  if (context->GetNumArgs() == 1) {
+    unique_chars->set(static_cast<int>(' '), true);
+    return;
+  }
+  if (!context->IsArgConstant(1)) return;
+  DCHECK_EQ(context->GetArgType(1)->type, FunctionContext::TYPE_STRING);
+  StringVal* chars_to_trim = reinterpret_cast<StringVal*>(context->GetConstantArg(1));
+  if (chars_to_trim->is_null) return; // We shouldn't peek into Null StringVals
+  for (int32_t i = 0; i < chars_to_trim->len; ++i) {
+    unique_chars->set(static_cast<int>(chars_to_trim->ptr[i]), true);
+  }
+}
+
+void StringFunctions::TrimClose(
+    FunctionContext* context, FunctionContext::FunctionStateScope scope) {
+  if (scope != FunctionContext::THREAD_LOCAL) return;
+  bitset<256>* unique_chars = reinterpret_cast<bitset<256>*>(
+      context->GetFunctionState(scope));
+  delete unique_chars;
+  context->SetFunctionState(scope, nullptr);
+}
+
+template <StringFunctions::TrimPosition D, bool IS_IMPLICIT_WHITESPACE>
+StringVal StringFunctions::DoTrimString(FunctionContext* ctx,
+    const StringVal& str, const StringVal& chars_to_trim) {
   if (str.is_null) return StringVal::null();
+  bitset<256>* unique_chars = reinterpret_cast<bitset<256>*>(
+      ctx->GetFunctionState(FunctionContext::THREAD_LOCAL));
+  // When 'chars_to_trim' is unique for each element (e.g. when 'chars_to_trim'
+  // is each element of a table column), we need to prepare a bitset of unique
+  // characters here instead of using the bitset from function context.
+  if (!IS_IMPLICIT_WHITESPACE && !ctx->IsArgConstant(1)) {
+    if (chars_to_trim.is_null) return str;
+    unique_chars->reset();
+    for (int32_t i = 0; i < chars_to_trim.len; ++i) {
+      unique_chars->set(static_cast<int>(chars_to_trim.ptr[i]), true);
+    }
+  }
   // Find new starting position.
   int32_t begin = 0;
-  while (begin < str.len && str.ptr[begin] == ' ') {
-    ++begin;
+  int32_t end = str.len - 1;
+  if (D == LEADING || D == BOTH) {
+    while (begin < str.len &&
+        unique_chars->test(static_cast<int>(str.ptr[begin]))) {
+      ++begin;
+    }
   }
   // Find new ending position.
-  int32_t end = str.len - 1;
-  while (end > begin && str.ptr[end] == ' ') {
-    --end;
+  if (D == TRAILING || D == BOTH) {
+    while (end >= begin && unique_chars->test(static_cast<int>(str.ptr[end]))) {
+      --end;
+    }
   }
   return StringVal(str.ptr + begin, end - begin + 1);
 }
 
+StringVal StringFunctions::Trim(FunctionContext* context, const StringVal& str) {
+  return DoTrimString<BOTH, true>(context, str, StringVal(" "));
+}
+
 StringVal StringFunctions::Ltrim(FunctionContext* context, const StringVal& str) {
-  if (str.is_null) return StringVal::null();
-  // Find new starting position.
-  int32_t begin = 0;
-  while (begin < str.len && str.ptr[begin] == ' ') {
-    ++begin;
-  }
-  return StringVal(str.ptr + begin, str.len - begin);
+  return DoTrimString<LEADING, true>(context, str, StringVal(" "));
 }
 
 StringVal StringFunctions::Rtrim(FunctionContext* context, const StringVal& str) {
-  if (str.is_null) return StringVal::null();
-  if (str.len == 0) return str;
-  // Find new ending position.
-  int32_t end = str.len - 1;
-  while (end > 0 && str.ptr[end] == ' ') {
-    --end;
-  }
-  DCHECK_GE(end, 0);
-  return StringVal(str.ptr, (str.ptr[end] == ' ') ? end : end + 1);
+  return DoTrimString<TRAILING, true>(context, str, StringVal(" "));
+}
+
+StringVal StringFunctions::LTrimString(FunctionContext* ctx,
+    const StringVal& str, const StringVal& chars_to_trim) {
+  return DoTrimString<LEADING, false>(ctx, str, chars_to_trim);
+}
+
+StringVal StringFunctions::RTrimString(FunctionContext* ctx,
+    const StringVal& str, const StringVal& chars_to_trim) {
+  return DoTrimString<TRAILING, false>(ctx, str, chars_to_trim);
+}
+
+StringVal StringFunctions::BTrimString(FunctionContext* ctx,
+    const StringVal& str, const StringVal& chars_to_trim) {
+  return DoTrimString<BOTH, false>(ctx, str, chars_to_trim);
 }
 
 IntVal StringFunctions::Ascii(FunctionContext* context, const StringVal& str) {
@@ -923,58 +978,6 @@ StringVal StringFunctions::Chr(FunctionContext* ctx, const IntVal& val) {
   return AnyValUtil::FromBuffer(ctx, &c, 1);
 }
 
-void StringFunctions::BTrimPrepare(
-    FunctionContext* context, FunctionContext::FunctionStateScope scope) {
-  if (scope != FunctionContext::THREAD_LOCAL) return;
-  // Create a bitset to hold the unique characters to trim.
-  bitset<256>* unique_chars = new bitset<256>();
-  context->SetFunctionState(scope, unique_chars);
-  if (!context->IsArgConstant(1)) return;
-  DCHECK_EQ(context->GetArgType(1)->type, FunctionContext::TYPE_STRING);
-  StringVal* chars_to_trim = reinterpret_cast<StringVal*>(context->GetConstantArg(1));
-  for (int32_t i = 0; i < chars_to_trim->len; ++i) {
-    unique_chars->set(static_cast<int>(chars_to_trim->ptr[i]), true);
-  }
-}
-
-void StringFunctions::BTrimClose(
-    FunctionContext* context, FunctionContext::FunctionStateScope scope) {
-  if (scope != FunctionContext::THREAD_LOCAL) return;
-  bitset<256>* unique_chars = reinterpret_cast<bitset<256>*>(
-      context->GetFunctionState(scope));
-  delete unique_chars;
-  context->SetFunctionState(scope, nullptr);
-}
-
-StringVal StringFunctions::BTrimString(FunctionContext* ctx,
-    const StringVal& str, const StringVal& chars_to_trim) {
-  if (str.is_null) return StringVal::null();
-  bitset<256>* unique_chars = reinterpret_cast<bitset<256>*>(
-      ctx->GetFunctionState(FunctionContext::THREAD_LOCAL));
-  // When 'chars_to_trim' is unique for each element (e.g. when 'chars_to_trim'
-  // is each element of a table column), we need to prepare a bitset of unique
-  // characters here instead of using the bitset from function context.
-  if (!ctx->IsArgConstant(1)) {
-    unique_chars->reset();
-    DCHECK(chars_to_trim.len != 0 || chars_to_trim.is_null);
-    for (int32_t i = 0; i < chars_to_trim.len; ++i) {
-      unique_chars->set(static_cast<int>(chars_to_trim.ptr[i]), true);
-    }
-  }
-  // Find new starting position.
-  int32_t begin = 0;
-  while (begin < str.len &&
-      unique_chars->test(static_cast<int>(str.ptr[begin]))) {
-    ++begin;
-  }
-  // Find new ending position.
-  int32_t end = str.len - 1;
-  while (end > begin && unique_chars->test(static_cast<int>(str.ptr[end]))) {
-    --end;
-  }
-  return StringVal(str.ptr + begin, end - begin + 1);
-}
-
 // Similar to strstr() except that the strings are not null-terminated
 static char* LocateSubstring(char* haystack, int hay_len, const char* needle, int needle_len) {
   DCHECK_GT(needle_len, 0);

http://git-wip-us.apache.org/repos/asf/impala/blob/c2d27ca8/be/src/exprs/string-functions.h
----------------------------------------------------------------------
diff --git a/be/src/exprs/string-functions.h b/be/src/exprs/string-functions.h
index 86fc547..91ad2cc 100644
--- a/be/src/exprs/string-functions.h
+++ b/be/src/exprs/string-functions.h
@@ -20,6 +20,7 @@
 #define IMPALA_EXPRS_STRING_FUNCTIONS_H
 
 #include <re2/re2.h>
+#include <bitset>
 
 #include "runtime/string-value.h"
 #include "runtime/string-search.h"
@@ -47,6 +48,12 @@ class TupleRow;
 
 class StringFunctions {
  public:
+  // String trimming position or direction
+  enum TrimPosition {
+    LEADING, // Trim from the begining, or leading end
+    TRAILING, // Trim from the right, or trailing end
+    BOTH // Trim from both ends of string
+  };
   static StringVal Substring(FunctionContext*, const StringVal& str, const BigIntVal& pos,
       const BigIntVal& len);
   static StringVal Substring(FunctionContext*, const StringVal& str,
@@ -76,6 +83,25 @@ class StringFunctions {
   static StringVal Trim(FunctionContext*, const StringVal& str);
   static StringVal Ltrim(FunctionContext*, const StringVal& str);
   static StringVal Rtrim(FunctionContext*, const StringVal& str);
+
+  /// Sets up arguments and function context for the *TrimString functions below.
+  static void TrimPrepare(FunctionContext*, FunctionContext::FunctionStateScope);
+  /// Cleans up the work done by TrimPrepare above.
+  static void TrimClose(FunctionContext*, FunctionContext::FunctionStateScope);
+
+  /// Trims occurrences of the characters in 'chars_to_trim' string from
+  /// the beginning of string 'str'.
+  static StringVal LTrimString(FunctionContext* ctx, const StringVal& str,
+      const StringVal& chars_to_trim);
+  /// Trims occurrences of the characters in 'chars_to_trim' string from
+  /// the end of string 'str'.
+  static StringVal RTrimString(FunctionContext* ctx, const StringVal& str,
+      const StringVal& chars_to_trim);
+  /// Trims occurrences of the characters in 'chars_to_trim' string from
+  /// both ends of string 'str'.
+  static StringVal BTrimString(FunctionContext* ctx, const StringVal& str,
+      const StringVal& chars_to_trim);
+
   static IntVal Ascii(FunctionContext*, const StringVal& str);
   static IntVal Instr(FunctionContext*, const StringVal& str, const StringVal& substr,
       const BigIntVal& start_position, const BigIntVal& occurrence);
@@ -118,16 +144,18 @@ class StringFunctions {
   /// Converts ASCII 'val' to corresponding character.
   static StringVal Chr(FunctionContext* context, const IntVal& val);
 
-  static void BTrimPrepare(FunctionContext*, FunctionContext::FunctionStateScope);
-  static void BTrimClose(FunctionContext*, FunctionContext::FunctionStateScope);
-
-  /// Trims occurrences of the characters in 'chars_to_trim' string from
-  /// both ends of string 'str'.
-  static StringVal BTrimString(FunctionContext* ctx, const StringVal& str,
-    const StringVal& chars_to_trim);
-
   static StringVal Base64Encode(FunctionContext* ctx, const StringVal& str);
   static StringVal Base64Decode(FunctionContext* ctx, const StringVal& str);
+
+ private:
+  /// Templatized implementation of the actual string trimming function.
+  /// The first parameter, 'D', is one of StringFunctions::TrimPosition values.
+  /// The second parameter, 'IS_IMPLICIT_WHITESPACE', is true when the set of characters
+  /// to trim is implicitly set to ' ', as a result of calling the one-arg
+  /// forms of trim/ltrim/rtrim.
+  template <TrimPosition D, bool IS_IMPLICIT_WHITESPACE>
+  static StringVal DoTrimString(FunctionContext* ctx, const StringVal& str,
+      const StringVal& chars_to_trim);
 };
 }
 #endif

http://git-wip-us.apache.org/repos/asf/impala/blob/c2d27ca8/common/function-registry/impala_functions.py
----------------------------------------------------------------------
diff --git a/common/function-registry/impala_functions.py b/common/function-registry/impala_functions.py
index aa9bb49..b78062b 100644
--- a/common/function-registry/impala_functions.py
+++ b/common/function-registry/impala_functions.py
@@ -439,9 +439,21 @@ visible_functions = [
   [['reverse'], 'STRING', ['STRING'], 'impala::StringFunctions::Reverse'],
   [['translate'], 'STRING', ['STRING', 'STRING', 'STRING'],
    'impala::StringFunctions::Translate'],
-  [['trim'], 'STRING', ['STRING'], 'impala::StringFunctions::Trim'],
-  [['ltrim'], 'STRING', ['STRING'], 'impala::StringFunctions::Ltrim'],
-  [['rtrim'], 'STRING', ['STRING'], 'impala::StringFunctions::Rtrim'],
+  [['trim'], 'STRING', ['STRING'], 'impala::StringFunctions::Trim',
+   '_ZN6impala15StringFunctions11TrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE',
+   '_ZN6impala15StringFunctions9TrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
+  [['ltrim'], 'STRING', ['STRING'], 'impala::StringFunctions::Ltrim',
+   '_ZN6impala15StringFunctions11TrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE',
+   '_ZN6impala15StringFunctions9TrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
+  [['rtrim'], 'STRING', ['STRING'], 'impala::StringFunctions::Rtrim',
+   '_ZN6impala15StringFunctions11TrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE',
+   '_ZN6impala15StringFunctions9TrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
+  [['ltrim'], 'STRING', ['STRING', 'STRING'], 'impala::StringFunctions::LTrimString',
+   '_ZN6impala15StringFunctions11TrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE',
+   '_ZN6impala15StringFunctions9TrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
+  [['rtrim'], 'STRING', ['STRING', 'STRING'], 'impala::StringFunctions::RTrimString',
+   '_ZN6impala15StringFunctions11TrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE',
+   '_ZN6impala15StringFunctions9TrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
   [['ascii'], 'INT', ['STRING'], 'impala::StringFunctions::Ascii'],
   [['instr'], 'INT', ['STRING', 'STRING'], 'impala::StringFunctions::Instr'],
   [['instr'], 'INT', ['STRING', 'STRING', 'BIGINT'], 'impala::StringFunctions::Instr'],
@@ -486,8 +498,12 @@ visible_functions = [
    '_ZN6impala15StringFunctions13ParseUrlCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
 # Netezza compatibility char functions
   [['chr'], 'STRING', ['INT'], 'impala::StringFunctions::Chr'],
-  [['btrim'], 'STRING', ['STRING'], 'impala::StringFunctions::Trim'],
-  [['btrim'], 'STRING', ['STRING', 'STRING'], 'impala::StringFunctions::BTrimString', '_ZN6impala15StringFunctions12BTrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE', '_ZN6impala15StringFunctions10BTrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
+  [['btrim'], 'STRING', ['STRING'], 'impala::StringFunctions::Trim',
+   '_ZN6impala15StringFunctions11TrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE',
+   '_ZN6impala15StringFunctions9TrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
+  [['btrim'], 'STRING', ['STRING', 'STRING'], 'impala::StringFunctions::BTrimString',
+   '_ZN6impala15StringFunctions11TrimPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE',
+   '_ZN6impala15StringFunctions9TrimCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'],
 
   # Conditional Functions
   # Some of these have empty symbols because the BE special-cases them based on the