blob: 149c5d5ebfd74d0b031616cfd197b60c34366e2a [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#ifndef IMPALA_UTIL_STRING_PARSER_H
#define IMPALA_UTIL_STRING_PARSER_H
#include <limits>
#include <boost/type_traits.hpp>
#include "common/compiler-util.h"
#include "common/logging.h"
#include <gtest/gtest_prod.h>
#include "runtime/decimal-value.h"
#include "runtime/date-parse-util.h"
#include "runtime/timestamp-parse-util.h"
#include "runtime/timestamp-value.h"
#include "thirdparty/fast_double_parser/fast_double_parser.h"
#include "util/decimal-util.h"
namespace impala {
/// Utility functions for doing atoi/atof on non-null terminated strings. On micro
/// benchmarks, this is significantly faster than libc (atoi/strtol and atof/strtod).
//
/// Strings with leading and trailing whitespaces are accepted.
/// Branching is heavily optimized for the non-whitespace successful case.
/// All the StringTo* functions first parse the input string assuming it has no leading
/// whitespace. If that first attempt was unsuccessful, these functions retry the parsing
/// after removing whitespace. Therefore, strings with whitespace take a perf hit on
/// branch mis-prediction.
//
/// For overflows, we are following the mysql behavior, to cap values at the max/min value
/// for that data type. This is different from hive, which returns NULL for overflow
/// slots for int types and inf/-inf for float types.
//
/// Things we tried that did not work:
/// - lookup table for converting character to digit
/// Improvements (TODO):
/// - Validate input using _sidd_compare_ranges
/// - Since we know the length, we can parallelize this: i.e. result = 100*s[0] +
/// 10*s[1] + s[2]
///
/// TODO: people went crazy with huge inline functions in this file - most should be
/// moved out-of-line.
class StringParser {
public:
enum ParseResult {
PARSE_SUCCESS = 0,
PARSE_FAILURE,
PARSE_OVERFLOW,
PARSE_UNDERFLOW,
};
template <typename T>
static inline T StringToInt(const char* s, int len, ParseResult* result) {
T ans = StringToIntInternal<T>(s, len, result);
if (LIKELY(*result == PARSE_SUCCESS)) return ans;
int i = SkipLeadingWhitespace(s, len);
return StringToIntInternal<T>(s + i, len - i, result);
}
/// Convert a string s representing a number in given base into a decimal number.
template <typename T>
static inline T StringToInt(const char* s, int len, int base, ParseResult* result) {
T ans = StringToIntInternal<T>(s, len, base, result);
if (LIKELY(*result == PARSE_SUCCESS)) return ans;
int i = SkipLeadingWhitespace(s, len);
return StringToIntInternal<T>(s + i, len - i, base, result);
}
template <typename T>
static inline T StringToFloat(const char* s, int len, ParseResult* result) {
T ans = StringToFloatInternal<T>(s, len, result);
if (LIKELY(*result == PARSE_SUCCESS)) return ans;
int i = SkipLeadingWhitespace(s, len);
return StringToFloatInternal<T>(s + i, len - i, result);
}
/// Parses a string for 'true' or 'false', case insensitive.
static inline bool StringToBool(const char* s, int len, ParseResult* result) {
bool ans = StringToBoolInternal(s, len, result);
if (LIKELY(*result == PARSE_SUCCESS)) return ans;
int i = SkipLeadingWhitespace(s, len);
return StringToBoolInternal(s + i, len - i, result);
}
/// Parse a TimestampValue from s.
static inline TimestampValue StringToTimestamp(const char* s, int len,
ParseResult* result) {
boost::gregorian::date d;
boost::posix_time::time_duration t;
*result = TimestampParser::ParseSimpleDateFormat(s, len, &d, &t) ? PARSE_SUCCESS :
PARSE_FAILURE;
return {d, t};
}
/// Parse a DateValue from s.
static inline DateValue StringToDate(const char* s, int len, ParseResult* result) {
DateValue d;
*result = DateParser::ParseSimpleDateFormat(s, len, false, &d) ? PARSE_SUCCESS :
PARSE_FAILURE;
return d;
}
/// Parses a decimal from s, returning the result.
/// The parse status is returned in *result.
/// On overflow or invalid values, the return value is undefined.
/// On underflow, the truncated value is returned.
template <typename T>
static inline DecimalValue<T> StringToDecimal(const char* s, int len,
const ColumnType& type, bool round, StringParser::ParseResult* result) {
return StringToDecimal<T>(s, len, type.precision, type.scale, round, result);
}
template <typename T>
static inline DecimalValue<T> StringToDecimal(const char* s, int len,
int type_precision, int type_scale, bool round, StringParser::ParseResult* result) {
// Special cases:
// 1) '' == Fail, an empty string fails to parse.
// 2) ' # ' == #, leading and trailing white space is ignored.
// 3) '.' == 0, a single dot parses as zero (for consistency with other types).
// 4) '#.' == '#', a trailing dot is ignored.
// Ignore leading and trailing spaces.
while (len > 0 && IsWhitespace(*s)) {
++s;
--len;
}
while (len > 0 && IsWhitespace(s[len - 1])) {
--len;
}
bool is_negative = false;
if (len > 0) {
switch (*s) {
case '-':
is_negative = true;
[[fallthrough]];
case '+':
++s;
--len;
}
}
// Ignore leading zeros.
bool found_value = false;
while (len > 0 && UNLIKELY(*s == '0')) {
found_value = true;
++s;
--len;
}
// Ignore leading zeros even after a dot. This allows for differentiating between
// cases like 0.01e2, which would fit in a DECIMAL(1, 0), and 0.10e2, which would
// overflow.
int digits_after_dot_count = 0;
int found_dot = 0;
if (len > 0 && *s == '.') {
found_dot = 1;
++s;
--len;
while (len > 0 && UNLIKELY(*s == '0')) {
found_value = true;
++digits_after_dot_count;
++s;
--len;
}
}
int total_digits_count = 0;
bool found_exponent = false;
int8_t exponent = 0;
int first_truncated_digit = 0;
T value = 0;
for (int i = 0; i < len; ++i) {
const char c = s[i];
if (LIKELY('0' <= c && c <= '9')) {
found_value = true;
// Ignore digits once the type's precision limit is reached. This avoids
// overflowing the underlying storage while handling a string like
// 10000000000e-10 into a DECIMAL(1, 0). Adjustments for ignored digits and
// an exponent will be made later.
if (LIKELY(total_digits_count < type_precision)) {
// Benchmarks are faster with parenthesis.
T new_value = (value * 10) + (c - '0');
DCHECK(new_value >= value);
value = new_value;
} else if (UNLIKELY(round && total_digits_count == type_precision)) {
first_truncated_digit = c - '0';
}
DCHECK(value >= 0); // DCHECK_GE does not work with int128_t
++total_digits_count;
digits_after_dot_count += found_dot;
} else if (c == '.' && LIKELY(!found_dot)) {
found_dot = 1;
} else if ((c == 'e' || c == 'E') && LIKELY(!found_exponent)) {
found_exponent = true;
exponent = StringToIntInternal<int8_t>(s + i + 1, len - i - 1, result);
if (UNLIKELY(*result != StringParser::PARSE_SUCCESS)) {
if (*result == StringParser::PARSE_OVERFLOW && exponent < 0) {
*result = StringParser::PARSE_UNDERFLOW;
}
return DecimalValue<T>(0);
}
break;
} else {
*result = StringParser::PARSE_FAILURE;
return DecimalValue<T>(0);
}
}
// Find the number of truncated digits before adjusting the precision for an exponent.
int truncated_digit_count = std::max(total_digits_count - type_precision, 0);
// 'scale' and 'precision' refer to the scale and precision of the number that
// is contained the string that we are parsing. The scale of 'value' may be
// different because some digits may have been truncated.
int scale, precision;
ApplyExponent(total_digits_count, digits_after_dot_count,
exponent, &value, &precision, &scale);
// Microbenchmarks show that beyond this point, returning on parse failure is slower
// than just letting the function run out.
*result = StringParser::PARSE_SUCCESS;
if (UNLIKELY(precision - scale > type_precision - type_scale)) {
// The number in the string has too many digits to the left of the dot,
// so we overflow.
*result = StringParser::PARSE_OVERFLOW;
} else if (UNLIKELY(scale > type_scale)) {
// There are too many digits to the right of the dot in the string we are parsing.
*result = StringParser::PARSE_UNDERFLOW;
// The scale of 'value'.
int value_scale = scale - truncated_digit_count;
int shift = value_scale - type_scale;
if (shift > 0) {
// There are less than maximum number of digits to the left of the dot.
value = DecimalUtil::ScaleDownAndRound<T>(value, shift, round);
DCHECK(value >= 0);
DCHECK(value < DecimalUtil::GetScaleMultiplier<int128_t>(type_precision));
} else {
// There are a maximum number of digits to the left of the dot. We round by
// looking at the first truncated digit.
DCHECK_EQ(shift, 0);
DCHECK(0 <= first_truncated_digit && first_truncated_digit <= 9);
DCHECK(first_truncated_digit == 0 || truncated_digit_count != 0);
DCHECK(first_truncated_digit == 0 || round);
// Apply the rounding.
value += (first_truncated_digit >= 5);
DCHECK(value >= 0);
DCHECK(value <= DecimalUtil::GetScaleMultiplier<int128_t>(type_precision));
if (UNLIKELY(value == DecimalUtil::GetScaleMultiplier<T>(type_precision))) {
// Overflow due to rounding.
*result = StringParser::PARSE_OVERFLOW;
}
}
} else if (UNLIKELY(!found_value && !found_dot)) {
*result = StringParser::PARSE_FAILURE;
} else if (type_scale > scale) {
// There were not enough digits after the dot, so we have scale up the value.
DCHECK_EQ(truncated_digit_count, 0);
value *= DecimalUtil::GetScaleMultiplier<T>(type_scale - scale);
// Overflow should be impossible.
DCHECK(value < DecimalUtil::GetScaleMultiplier<int128_t>(type_precision));
}
return DecimalValue<T>(is_negative ? -value : value);
}
private:
// Max length of string passed to 'fast_double_parser::parse_number'.
static const int MAX_LEN_FAST_FLOAT_PARSER = 50;
// Not using FRIEND_TEST as TestStringToFloatPreprocess is not a TestSuite
friend void TestStringToFloatPreprocess(const char* s, const char* expected);
/// This is considerably faster than glibc's implementation.
/// In the case of overflow, the max/min value for the data type will be returned.
/// Assumes s represents a decimal number.
/// Return PARSE_FAILURE on leading whitespace. Trailing whitespace is allowed.
template <typename T>
static inline T StringToIntInternal(const char* s, int len, ParseResult* result) {
if (UNLIKELY(len <= 0)) {
*result = PARSE_FAILURE;
return 0;
}
typedef typename boost::make_unsigned<T>::type UnsignedT;
UnsignedT val = 0;
UnsignedT max_val = std::numeric_limits<T>::max();
bool negative = false;
int i = 0;
switch (*s) {
case '-':
negative = true;
max_val = static_cast<UnsignedT>(std::numeric_limits<T>::max()) + 1;
[[fallthrough]];
case '+': ++i;
}
// This is the fast path where the string cannot overflow.
if (LIKELY(len - i < StringParseTraits<T>::max_ascii_len())) {
val = StringToIntNoOverflow<UnsignedT>(s + i, len - i, result);
return static_cast<T>(negative ? -val : val);
}
const T max_div_10 = max_val / 10;
const T max_mod_10 = max_val % 10;
int first = i;
for (; i < len; ++i) {
if (LIKELY(s[i] >= '0' && s[i] <= '9')) {
T digit = s[i] - '0';
// This is a tricky check to see if adding this digit will cause an overflow.
if (UNLIKELY(val > (max_div_10 - (digit > max_mod_10)))) {
*result = PARSE_OVERFLOW;
return negative ? -max_val : max_val;
}
val = val * 10 + digit;
} else {
if ((UNLIKELY(i == first || !IsAllWhitespace(s + i, len - i)))) {
// Reject the string because either the first char was not a digit,
// or the remaining chars are not all whitespace
*result = PARSE_FAILURE;
return 0;
}
// Returning here is slightly faster than breaking the loop.
*result = PARSE_SUCCESS;
return static_cast<T>(negative ? -val : val);
}
}
*result = PARSE_SUCCESS;
return static_cast<T>(negative ? -val : val);
}
/// Convert a string s representing a number in given base into a decimal number.
/// Return PARSE_FAILURE on leading whitespace. Trailing whitespace is allowed.
template <typename T>
static inline T StringToIntInternal(const char* s, int len, int base,
ParseResult* result) {
typedef typename boost::make_unsigned<T>::type UnsignedT;
UnsignedT val = 0;
UnsignedT max_val = std::numeric_limits<T>::max();
bool negative = false;
if (UNLIKELY(len <= 0)) {
*result = PARSE_FAILURE;
return 0;
}
int i = 0;
switch (*s) {
case '-':
negative = true;
max_val = static_cast<UnsignedT>(std::numeric_limits<T>::max()) + 1;
[[fallthrough]];
case '+': i = 1;
}
const T max_div_base = max_val / base;
const T max_mod_base = max_val % base;
int first = i;
for (; i < len; ++i) {
T digit;
if (LIKELY(s[i] >= '0' && s[i] <= '9')) {
digit = s[i] - '0';
} else if (s[i] >= 'a' && s[i] <= 'z') {
digit = (s[i] - 'a' + 10);
} else if (s[i] >= 'A' && s[i] <= 'Z') {
digit = (s[i] - 'A' + 10);
} else {
if ((UNLIKELY(i == first || !IsAllWhitespace(s + i, len - i)))) {
// Reject the string because either the first char was not an alpha/digit,
// or the remaining chars are not all whitespace
*result = PARSE_FAILURE;
return 0;
}
// skip trailing whitespace.
break;
}
// Bail, if we encounter a digit that is not available in base.
if (digit >= base) break;
// This is a tricky check to see if adding this digit will cause an
// overflow.
if (UNLIKELY(val > (max_div_base - (digit > max_mod_base)))) {
*result = PARSE_OVERFLOW;
return static_cast<T>(negative ? -max_val : max_val);
}
val = val * base + digit;
}
*result = PARSE_SUCCESS;
return static_cast<T>(negative ? -val : val);
}
/// Helper function that applies the exponent to the value. Also computes and
/// sets the precision and scale.
template <typename T>
static inline void ApplyExponent(int total_digits_count,
int digits_after_dot_count,int8_t exponent, T* value, int* precision, int* scale) {
if (exponent > digits_after_dot_count) {
// Ex: 0.1e3 (which at this point would have precision == 1 and scale == 1), the
// scale must be set to 0 and the value set to 100 which means a precision of 3.
*value = ArithmeticUtil::AsUnsigned<std::multiplies>(
*value, DecimalUtil::GetScaleMultiplier<T>(exponent - digits_after_dot_count));
*precision = total_digits_count + (exponent - digits_after_dot_count);
*scale = 0;
} else {
// Ex: 100e-4, the scale must be set to 4 but no adjustment to the value is needed,
// the precision must also be set to 4 but that will be done below for the
// non-exponent case anyways.
*precision = total_digits_count;
*scale = digits_after_dot_count - exponent;
// Ex: 0.001, at this point would have precision 1 and scale 3 since leading zeros
// were ignored during previous parsing.
if (*precision < *scale) *precision = *scale;
}
DCHECK_GE(*precision, *scale);
}
/// Checks if "inf" or "infinity" matches 's' in a case-insensitive manner. The match
/// has to start at the beginning of 's', leading whitespace is considered invalid.
/// Trailing whitespace characters are allowed.
/// Returns true if a match was found and false otherwise.
static inline bool IsInfinity(const char *s, int len) {
if (len >= 3 && strncasecmp(s, "inf", 3) == 0) {
int i = 3;
if (len >= 8 && strncasecmp(s + 3, "inity", 5) == 0) {
i = 8;
}
return IsAllWhitespace(s + i, len - i);
}
return false;
}
/// Checks if "nan" matches 's' in a case-insensitive manner. The match has to start at
/// the beginning of 's', leading whitespace is considered invalid. Trailing whitespace
/// characters are allowed.
/// Returns true if a match was found and false otherwise.
static inline bool IsNaN(const char *s, int len) {
return len >= 3 && strncasecmp(s, "nan", 3) == 0 && IsAllWhitespace(s + 3, len - 3);
}
/// Function to convert string to float. It is a wrapper over
/// fast_double_parser::parse_number which provides fast function to parse string into
/// double. It accepts string with notations like "1.0e10". It would not sacrifise
/// accuracy and match exactly (down the smallest bit) the result of a standard
/// function like strtod.
/// On failure result is set to PARSE_FAILURE and 0 is returned.
/// Return PARSE_FAILURE on leading whitespace. Trailing whitespace is allowed.
template <typename T>
static inline T StringToFloatInternal(const char* s, int len, ParseResult* result) {
if (UNLIKELY(len <= 0)) {
*result = PARSE_FAILURE;
return 0;
}
bool negative = false;
int i = 0;
switch (*s) {
case '-':
negative = true;
[[fallthrough]];
case '+': i = 1;
}
// Check if we have inf or NaN.
if (IsInfinity(s + i, len - i)) {
*result = PARSE_SUCCESS;
return negative ? -std::numeric_limits<T>::infinity()
: std::numeric_limits<T>::infinity();
}
if (IsNaN(s + i, len - i)) {
*result = PARSE_SUCCESS;
return negative ? -std::numeric_limits<T>::quiet_NaN()
: std::numeric_limits<T>::quiet_NaN();
}
// Using the fast_double_parser library function to parse strings
// containing decimal numbers into double-precision (binary64)
// floating-point values. Function is performant and does not sacrifice accuracy.
// The function will match exactly (down the smallest bit) the result of a
// standard function like strtod.
const char * s_end; // post-parsing will point after last character parsed.
int trailing_len; // length of remaning string not parsed by library
// Below are used for making copy of 's' if required.
// For short string c_str will be used and for longer string 's_copy' is used.
char c_str[StringParser::MAX_LEN_FAST_FLOAT_PARSER + 2];
std::string s_copy;
// Needs non-zero intitialization, as fast_parser library returns 0 for
// UNDERFLOW error.
double val = 1;
// Use stack for copying short string and use std::string (malloc) for
// longer string.
if (LIKELY(len - i < StringParser::MAX_LEN_FAST_FLOAT_PARSER)) {
// Process the string and make it compatible for the library.
int input_len = StringToFloatPreprocess(s + i, len - i, c_str);
s_end = fast_double_parser::parse_number(c_str, &val);
trailing_len = input_len - (int) (s_end - c_str);
} else {
// Parser library will invoke 'strtod' for longer string so directly invoke
// it to avoid preprocessing.
s_copy.insert(0, s + i, len - i);
s_end = fast_double_parser::parse_float_strtod(s_copy.c_str(), &val);
trailing_len = s_copy.length() - (s_end - s_copy.c_str());
}
if (UNLIKELY(s_end == nullptr)) {
// Determine if it is an overflow case
if (UNLIKELY(!std::isfinite(val))) {
*result = PARSE_OVERFLOW;
} else if (UNLIKELY(val == 0)) {
*result = PARSE_UNDERFLOW;
} else {
*result = PARSE_FAILURE;
return 0;
}
return (T)(negative ? -val : val);
}
// check if trailing characters are present and are all white spaces
DCHECK(trailing_len >= 0);
if (UNLIKELY(trailing_len > 0 && !IsAllWhitespace(s_end, trailing_len))) {
*result = PARSE_FAILURE;
return 0;
}
// Determine if it is an overflow case and update the result
if (UNLIKELY(val == std::numeric_limits<T>::infinity())) {
*result = PARSE_OVERFLOW;
} else {
*result = PARSE_SUCCESS;
}
return (T)(negative ? -val : val);
}
/// Internal utility function for `StringToFloatInternal`.
/// It performs these 3 things to make strings compatible with
/// `fast_double_parser::parse_number`:
/// 1. Removes leading 0s before another integer i.e.,'000012'->'12', '01.2'->'1.2'
/// 2. Collapsing leading 0s before any digit
/// i.e., '000.7'->'0.7', '0000'->'0', '00077.7' -> '77.7'.
/// 3. Prefix strings starting with '.' like '.56' or '-.56' with 0.
/// 'result' will point to processed string which will be null-terminated.
/// length of null-terminated string pointed to by 'result' is returned.
/// It doesn't handle strings starting with sign like "+" or "-".
static inline int StringToFloatPreprocess(const char* s, int len, char * result) {
int result_len = 0; // current length of 'result'
const char* curr = StripLeadingZeros(s, len);
int curr_len = (int) (s + len - curr); // remanining length of 'curr'
if (UNLIKELY(*curr == '.')) {
// Prefix strings starting with '.' like '.56' or '-.56' with 0.
result[result_len++] = '0';
}
// copy rest 'curr' to result 'res'
memcpy(result + result_len, curr, curr_len);
result_len += curr_len;
result[result_len] = '\0';
return result_len;
}
/// Utility to collapse leading 0s before any digit
/// E.g., '000.7'->'0.7', '0000'->'0', '00077.7'->'77.7'.
static inline const char* StripLeadingZeros(const char* s, int len) {
const char* curr = s;
const char* end = s + len - 1;
while(curr < end && *curr == '0'
&& fast_double_parser::is_integer(*(curr + 1))) {
curr++;
}
return curr;
}
/// Parses a string for 'true' or 'false', case insensitive.
/// Return PARSE_FAILURE on leading whitespace. Trailing whitespace is allowed.
static inline bool StringToBoolInternal(const char* s, int len, ParseResult* result) {
*result = PARSE_SUCCESS;
if (len >= 4 && (s[0] == 't' || s[0] == 'T')) {
bool match = (s[1] == 'r' || s[1] == 'R') &&
(s[2] == 'u' || s[2] == 'U') &&
(s[3] == 'e' || s[3] == 'E');
if (match && LIKELY(IsAllWhitespace(s+4, len-4))) return true;
} else if (len >= 5 && (s[0] == 'f' || s[0] == 'F')) {
bool match = (s[1] == 'a' || s[1] == 'A') &&
(s[2] == 'l' || s[2] == 'L') &&
(s[3] == 's' || s[3] == 'S') &&
(s[4] == 'e' || s[4] == 'E');
if (match && LIKELY(IsAllWhitespace(s+5, len-5))) return false;
}
*result = PARSE_FAILURE;
return false;
}
/// Returns the position of the first non-whitespace character in s.
static inline int SkipLeadingWhitespace(const char* s, int len) {
int i = 0;
while(i < len && IsWhitespace(s[i])) ++i;
return i;
}
/// Returns true if s only contains whitespace.
static inline bool IsAllWhitespace(const char* s, int len) {
for (int i = 0; i < len; ++i) {
if (!LIKELY(IsWhitespace(s[i]))) return false;
}
return true;
}
template<typename T>
class StringParseTraits {
public:
/// Returns the maximum ascii string length for this type.
/// e.g. the max/min int8_t has 3 characters.
static int max_ascii_len();
};
/// Converts an ascii string to an integer of type T assuming it cannot overflow
/// and the number is positive. Leading whitespace is not allowed. Trailing whitespace
/// will be skipped.
template <typename T>
static inline T StringToIntNoOverflow(const char* s, int len, ParseResult* result) {
T val = 0;
if (UNLIKELY(len == 0)) {
*result = PARSE_SUCCESS;
return val;
}
// Factor out the first char for error handling speeds up the loop.
if (LIKELY(s[0] >= '0' && s[0] <= '9')) {
val = s[0] - '0';
} else {
*result = PARSE_FAILURE;
return 0;
}
for (int i = 1; i < len; ++i) {
if (LIKELY(s[i] >= '0' && s[i] <= '9')) {
T digit = s[i] - '0';
val = val * 10 + digit;
} else {
if ((UNLIKELY(!IsAllWhitespace(s + i, len - i)))) {
*result = PARSE_FAILURE;
return 0;
}
*result = PARSE_SUCCESS;
return val;
}
}
*result = PARSE_SUCCESS;
return val;
}
static inline bool IsWhitespace(const char c) {
return c == ' ' || UNLIKELY(c == '\t' || c == '\n' || c == '\v' || c == '\f'
|| c == '\r');
}
};
template<>
inline int StringParser::StringParseTraits<int8_t>::max_ascii_len() { return 3; }
template<>
inline int StringParser::StringParseTraits<int16_t>::max_ascii_len() { return 5; }
template<>
inline int StringParser::StringParseTraits<int32_t>::max_ascii_len() { return 10; }
template<>
inline int StringParser::StringParseTraits<int64_t>::max_ascii_len() { return 19; }
// These functions are too large to benefit from inlining.
Decimal4Value StringToDecimal4(const char* s, int len, int type_precision,
int type_scale, bool round, StringParser::ParseResult* result);
Decimal8Value StringToDecimal8(const char* s, int len, int type_precision,
int type_scale, bool round, StringParser::ParseResult* result);
Decimal16Value StringToDecimal16(const char* s, int len, int type_precision,
int type_scale, bool round, StringParser::ParseResult* result);
}
#endif