#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_MAX_STRING_SIZE 0x00FFFFFFULL namespace DB { namespace ErrorCodes { extern const int CANNOT_PARSE_DATE; extern const int CANNOT_PARSE_DATETIME; extern const int CANNOT_READ_ARRAY_FROM_TEXT; } /// Helper functions for formatted input. inline char parseEscapeSequence(char c) { switch(c) { case 'a': return '\a'; case 'b': return '\b'; case 'f': return '\f'; case 'n': return '\n'; case 'r': return '\r'; case 't': return '\t'; case 'v': return '\v'; case '0': return '\0'; default: return c; } } inline char unhex(char c) { switch (c) { case '0' ... '9': return c - '0'; case 'a' ... 'f': return c - 'a' + 10; case 'A' ... 'F': return c - 'A' + 10; default: return 0; } } /// These functions are located in VarInt.h /// inline void throwReadAfterEOF() inline void readChar(char & x, ReadBuffer & buf) { if (!buf.eof()) { x = *buf.position(); ++buf.position(); } else throwReadAfterEOF(); } /// Read POD-type in native format template inline void readPODBinary(T & x, ReadBuffer & buf) { buf.readStrict(reinterpret_cast(&x), sizeof(x)); } template inline void readIntBinary(T & x, ReadBuffer & buf) { readPODBinary(x, buf); } template inline void readFloatBinary(T & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readStringBinary(std::string & s, ReadBuffer & buf, size_t MAX_STRING_SIZE = DEFAULT_MAX_STRING_SIZE) { size_t size = 0; readVarUInt(size, buf); if (size > MAX_STRING_SIZE) throw Poco::Exception("Too large string size."); s.resize(size); buf.readStrict(&s[0], size); } inline StringRef readStringBinaryInto(Arena & arena, ReadBuffer & buf) { size_t size = 0; readVarUInt(size, buf); char * data = arena.alloc(size); buf.readStrict(data, size); return StringRef(data, size); } template void readVectorBinary(std::vector & v, ReadBuffer & buf, size_t MAX_VECTOR_SIZE = DEFAULT_MAX_STRING_SIZE) { size_t size = 0; readVarUInt(size, buf); if (size > MAX_VECTOR_SIZE) throw Poco::Exception("Too large vector size."); v.resize(size); for (size_t i = 0; i < size; ++i) readBinary(v[i], buf); } void assertString(const char * s, ReadBuffer & buf); void assertEOF(ReadBuffer & buf); void assertChar(char symbol, ReadBuffer & buf); inline void assertString(const String & s, ReadBuffer & buf) { assertString(s.c_str(), buf); } bool checkString(const char * s, ReadBuffer & buf); inline bool checkString(const String & s, ReadBuffer & buf) { return checkString(s.c_str(), buf); } inline bool checkChar(char c, ReadBuffer & buf) { if (buf.eof() || *buf.position() != c) return false; ++buf.position(); return true; } inline void readBoolText(bool & x, ReadBuffer & buf) { char tmp = '0'; readChar(tmp, buf); x = tmp != '0'; } template ReturnType readIntTextImpl(T & x, ReadBuffer & buf) { static constexpr bool throw_exception = std::is_same::value; bool negative = false; x = 0; if (buf.eof()) { if (throw_exception) throwReadAfterEOF(); else return ReturnType(false); } while (!buf.eof()) { switch (*buf.position()) { case '+': break; case '-': if (std::is_signed::value) negative = true; else return ReturnType(false); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': x *= 10; x += *buf.position() - '0'; break; default: if (negative) x = -x; return ReturnType(true); } ++buf.position(); } if (negative) x = -x; return ReturnType(true); } template void readIntText(T & x, ReadBuffer & buf) { readIntTextImpl(x, buf); } template bool tryReadIntText(T & x, ReadBuffer & buf) { return readIntTextImpl(x, buf); } /** More efficient variant (about 1.5 times on real dataset). * Differs in following: * - for numbers starting with zero, parsed only zero; * - symbol '+' before number is not supported; * - symbols :;<=>? are parsed as some numbers. */ template void readIntTextUnsafe(T & x, ReadBuffer & buf) { bool negative = false; x = 0; auto on_error = [] { if (throw_on_error) throwReadAfterEOF(); }; if (unlikely(buf.eof())) return on_error(); if (std::is_signed::value && *buf.position() == '-') { ++buf.position(); negative = true; if (unlikely(buf.eof())) return on_error(); } if (*buf.position() == '0') /// There are many zeros in real datasets. { ++buf.position(); return; } while (!buf.eof()) { if ((*buf.position() & 0xF0) == 0x30) /// It makes sense to have this condition inside loop. { x *= 10; x += *buf.position() & 0x0F; ++buf.position(); } else break; } if (std::is_signed::value && negative) x = -x; } template void tryReadIntTextUnsafe(T & x, ReadBuffer & buf) { return readIntTextUnsafe(x, buf); } template bool exceptionPolicySelector(ExcepFun && excep_f, NoExcepFun && no_excep_f, Args &&... args) { if (throw_exception) { excep_f(std::forward(args)...); return true; } else return no_excep_f(std::forward(args)...); }; /// Returns true, iff parsed. bool parseInfinity(ReadBuffer & buf); bool parseNaN(ReadBuffer & buf); void assertInfinity(ReadBuffer & buf); void assertNaN(ReadBuffer & buf); /// Rough: not exactly nearest machine representable number is returned. /// Some garbage may be successfully parsed, examples: '.' parsed as 0; 123Inf parsed as inf. template ReturnType readFloatTextImpl(T & x, ReadBuffer & buf) { static constexpr bool throw_exception = std::is_same::value; bool negative = false; x = 0; bool after_point = false; double power_of_ten = 1; if (buf.eof()) { if (throw_exception) throwReadAfterEOF(); else return ReturnType(false); } while (!buf.eof()) { switch (*buf.position()) { case '+': break; case '-': negative = true; break; case point_symbol: after_point = true; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (after_point) { power_of_ten /= 10; x += (*buf.position() - '0') * power_of_ten; } else { x *= 10; x += *buf.position() - '0'; } break; case 'e': case 'E': { ++buf.position(); Int32 exponent = 0; bool res = exceptionPolicySelector(readIntText, tryReadIntText, exponent, buf); if (res) { x *= exp10(exponent); if (negative) x = -x; } return ReturnType(res); } case 'i': case 'I': { bool res = exceptionPolicySelector(assertInfinity, parseInfinity, buf); if (res) { x = std::numeric_limits::infinity(); if (negative) x = -x; } return ReturnType(res); } case 'n': case 'N': { bool res = exceptionPolicySelector(assertNaN, parseNaN, buf); if (res) { x = std::numeric_limits::quiet_NaN(); if (negative) x = -x; } return ReturnType(res); } default: { if (negative) x = -x; return ReturnType(true); } } ++buf.position(); } if (negative) x = -x; return ReturnType(true); } template inline bool tryReadFloatText(T & x, ReadBuffer & buf) { return readFloatTextImpl(x, buf); } template inline void readFloatText(T & x, ReadBuffer & buf) { readFloatTextImpl(x, buf); } /// rough; all until '\n' or '\t' void readString(String & s, ReadBuffer & buf); void readEscapedString(String & s, ReadBuffer & buf); void readQuotedString(String & s, ReadBuffer & buf); void readDoubleQuotedString(String & s, ReadBuffer & buf); void readJSONString(String & s, ReadBuffer & buf); void readBackQuotedString(String & s, ReadBuffer & buf); void readStringUntilEOF(String & s, ReadBuffer & buf); /** Read string in CSV format. * Parsing rules: * - string could be placed in quotes; quotes could be single: ' or double: "; * - or string could be unquoted - this is determined by first character; * - if string is unquoted, then it is read until next delimiter, * either until end of line (CR or LF), * or until end of stream; * but spaces and tabs at begin and end of unquoted string are consumed but ignored (note that this behaviour differs from RFC). * - if string is in quotes, then it will be read until closing quote, * but sequences of two consecutive quotes are parsed as single quote inside string; */ void readCSVString(String & s, ReadBuffer & buf, const char delimiter = ','); /// Read and append result to array of characters. template void readStringInto(Vector & s, ReadBuffer & buf); template void readEscapedStringInto(Vector & s, ReadBuffer & buf); template void readQuotedStringInto(Vector & s, ReadBuffer & buf); template void readDoubleQuotedStringInto(Vector & s, ReadBuffer & buf); template void readBackQuotedStringInto(Vector & s, ReadBuffer & buf); template void readStringUntilEOFInto(Vector & s, ReadBuffer & buf); template void readCSVStringInto(Vector & s, ReadBuffer & buf, const char delimiter = ','); template void readJSONStringInto(Vector & s, ReadBuffer & buf); /// This could be used as template parameter for functions above, if you want to just skip data. struct NullSink { void append(const char *, size_t) {}; void push_back(char) {}; }; /// In YYYY-MM-DD format inline void readDateText(DayNum_t & date, ReadBuffer & buf) { char s[10]; size_t size = buf.read(s, 10); if (10 != size) { s[size] = 0; throw Exception(std::string("Cannot parse date ") + s, ErrorCodes::CANNOT_PARSE_DATE); } UInt16 year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0'); UInt8 month = (s[5] - '0') * 10 + (s[6] - '0'); UInt8 day = (s[8] - '0') * 10 + (s[9] - '0'); date = DateLUT::instance().makeDayNum(year, month, day); } inline void readDateText(LocalDate & date, ReadBuffer & buf) { char s[10]; size_t size = buf.read(s, 10); if (10 != size) { s[size] = 0; throw Exception(std::string("Cannot parse date ") + s, ErrorCodes::CANNOT_PARSE_DATE); } date.year((s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0')); date.month((s[5] - '0') * 10 + (s[6] - '0')); date.day((s[8] - '0') * 10 + (s[9] - '0')); } template inline T parse(const char * data, size_t size); void readDateTimeTextFallback(time_t & datetime, ReadBuffer & buf); /** In YYYY-MM-DD hh:mm:ss format, according to current time zone. * As an exception, also supported parsing of unix timestamp in form of decimal number. */ inline void readDateTimeText(time_t & datetime, ReadBuffer & buf) { /** Read 10 characters, that could represent unix timestamp. * Only unix timestamp of 5-10 characters is supported. * Then look at 5th charater. If it is a number - treat whole as unix timestamp. * If it is not a number - then parse datetime in YYYY-MM-DD hh:mm:ss format. */ /// Optimistic path, when whole value are in buffer. const char * s = buf.position(); if (s + 19 < buf.buffer().end()) { if (s[4] < '0' || s[4] > '9') { UInt16 year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0'); UInt8 month = (s[5] - '0') * 10 + (s[6] - '0'); UInt8 day = (s[8] - '0') * 10 + (s[9] - '0'); UInt8 hour = (s[11] - '0') * 10 + (s[12] - '0'); UInt8 minute = (s[14] - '0') * 10 + (s[15] - '0'); UInt8 second = (s[17] - '0') * 10 + (s[18] - '0'); if (unlikely(year == 0)) datetime = 0; else datetime = DateLUT::instance().makeDateTime(year, month, day, hour, minute, second); buf.position() += 19; } else /// Why not readIntTextUnsafe? Because for needs of AdFox, parsing of unix timestamp with leading zeros is supported: 000...NNNN. readIntText(datetime, buf); } else readDateTimeTextFallback(datetime, buf); } inline void readDateTimeText(LocalDateTime & datetime, ReadBuffer & buf) { char s[19]; size_t size = buf.read(s, 19); if (19 != size) { s[size] = 0; throw Exception(std::string("Cannot parse datetime ") + s, ErrorCodes::CANNOT_PARSE_DATETIME); } datetime.year((s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0')); datetime.month((s[5] - '0') * 10 + (s[6] - '0')); datetime.day((s[8] - '0') * 10 + (s[9] - '0')); datetime.hour((s[11] - '0') * 10 + (s[12] - '0')); datetime.minute((s[14] - '0') * 10 + (s[15] - '0')); datetime.second((s[17] - '0') * 10 + (s[18] - '0')); } /// Generic methods to read value in native binary format. inline void readBinary(UInt8 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(UInt16 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(UInt32 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(UInt64 & x, ReadBuffer & buf) { readPODBinary(x, buf); } #ifdef __APPLE__ /** * On Linux x86_64 'int64_t' maps to "long", but on Apple x86_64 it maps to 'long long' * But on both platforms Int64 maps to 'long'. This is two different types * with the same size==8, so we need extra functions here */ inline void readBinary(uint64_t & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(int64_t & x, ReadBuffer & buf) { readPODBinary(x, buf); } #endif inline void readBinary(Int8 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(Int16 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(Int32 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(Int64 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(Float32 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(Float64 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(String & x, ReadBuffer & buf) { readStringBinary(x, buf); } inline void readBinary(bool & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(uint128 & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(VisitID_t & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(LocalDate & x, ReadBuffer & buf) { readPODBinary(x, buf); } inline void readBinary(LocalDateTime & x, ReadBuffer & buf) { readPODBinary(x, buf); } /// Generic methods to read value in text tab-separated format. inline void readText(UInt8 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(UInt16 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(UInt32 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(UInt64 & x, ReadBuffer & buf) { readIntText(x, buf); } #ifdef __APPLE__ inline void readText(uint64_t & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(int64_t & x, ReadBuffer & buf) { readIntText(x, buf); } #endif inline void readText(Int8 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(Int16 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(Int32 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(Int64 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(Float32 & x, ReadBuffer & buf) { readFloatText(x, buf); } inline void readText(Float64 & x, ReadBuffer & buf) { readFloatText(x, buf); } inline void readText(String & x, ReadBuffer & buf) { readEscapedString(x, buf); } inline void readText(bool & x, ReadBuffer & buf) { readBoolText(x, buf); } inline void readText(VisitID_t & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readText(LocalDate & x, ReadBuffer & buf) { readDateText(x, buf); } inline void readText(LocalDateTime & x, ReadBuffer & buf) { readDateTimeText(x, buf); } /// Generic methods to read value in text format, /// possibly in single quotes (only for data types that use quotes in VALUES format of INSERT statement in SQL). inline void readQuoted(UInt8 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(UInt16 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(UInt32 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(UInt64 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(Int8 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(Int16 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(Int32 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(Int64 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(Float32 & x, ReadBuffer & buf) { readFloatText(x, buf); } inline void readQuoted(Float64 & x, ReadBuffer & buf) { readFloatText(x, buf); } inline void readQuoted(String & x, ReadBuffer & buf) { readQuotedString(x, buf); } inline void readQuoted(bool & x, ReadBuffer & buf) { readBoolText(x, buf); } inline void readQuoted(VisitID_t & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readQuoted(LocalDate & x, ReadBuffer & buf) { assertChar('\'', buf); readDateText(x, buf); assertChar('\'', buf); } inline void readQuoted(LocalDateTime & x, ReadBuffer & buf) { assertChar('\'', buf); readDateTimeText(x, buf); assertChar('\'', buf); } /// Same as above, but in double quotes. inline void readDoubleQuoted(UInt8 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(UInt16 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(UInt32 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(UInt64 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(Int8 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(Int16 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(Int32 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(Int64 & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(Float32 & x, ReadBuffer & buf) { readFloatText(x, buf); } inline void readDoubleQuoted(Float64 & x, ReadBuffer & buf) { readFloatText(x, buf); } inline void readDoubleQuoted(String & x, ReadBuffer & buf) { readDoubleQuotedString(x, buf); } inline void readDoubleQuoted(bool & x, ReadBuffer & buf) { readBoolText(x, buf); } inline void readDoubleQuoted(VisitID_t & x, ReadBuffer & buf) { readIntText(x, buf); } inline void readDoubleQuoted(LocalDate & x, ReadBuffer & buf) { assertChar('"', buf); readDateText(x, buf); assertChar('"', buf); } inline void readDoubleQuoted(LocalDateTime & x, ReadBuffer & buf) { assertChar('"', buf); readDateTimeText(x, buf); assertChar('"', buf); } /// CSV, for numbers, dates, datetimes: quotes are optional, no special escaping rules. template inline void readCSVSimple(T & x, ReadBuffer & buf) { if (buf.eof()) throwReadAfterEOF(); char maybe_quote = *buf.position(); if (maybe_quote == '\'' || maybe_quote == '\"') ++buf.position(); readText(x, buf); if (maybe_quote == '\'' || maybe_quote == '\"') assertChar(maybe_quote, buf); } inline void readCSV(UInt8 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(UInt16 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(UInt32 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(UInt64 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(Int8 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(Int16 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(Int32 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(Int64 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(Float32 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(Float64 & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(String & x, ReadBuffer & buf, const char delimiter = ',') { readCSVString(x, buf, delimiter); } inline void readCSV(bool & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(VisitID_t & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(LocalDate & x, ReadBuffer & buf) { readCSVSimple(x, buf); } inline void readCSV(LocalDateTime & x, ReadBuffer & buf) { readCSVSimple(x, buf); } template void readBinary(std::vector & x, ReadBuffer & buf) { size_t size = 0; readVarUInt(size, buf); if (size > DEFAULT_MAX_STRING_SIZE) throw Poco::Exception("Too large vector size."); x.resize(size); for (size_t i = 0; i < size; ++i) readBinary(x[i], buf); } template void readQuoted(std::vector & x, ReadBuffer & buf) { bool first = true; assertChar('[', buf); while (!buf.eof() && *buf.position() != ']') { if (!first) { if (*buf.position() == ',') ++buf.position(); else throw Exception("Cannot read array from text", ErrorCodes::CANNOT_READ_ARRAY_FROM_TEXT); } first = false; x.push_back(T()); readQuoted(x.back(), buf); } assertChar(']', buf); } template void readDoubleQuoted(std::vector & x, ReadBuffer & buf) { bool first = true; assertChar('[', buf); while (!buf.eof() && *buf.position() != ']') { if (!first) { if (*buf.position() == ',') ++buf.position(); else throw Exception("Cannot read array from text", ErrorCodes::CANNOT_READ_ARRAY_FROM_TEXT); } first = false; x.push_back(T()); readDoubleQuoted(x.back(), buf); } assertChar(']', buf); } template void readText(std::vector & x, ReadBuffer & buf) { readQuoted(x, buf); } /// Skip whitespace characters. inline void skipWhitespaceIfAny(ReadBuffer & buf) { while (!buf.eof() && isWhitespaceASCII(*buf.position())) ++buf.position(); } /// Skips json value. If the value contains objects (i.e. {...} sequence), an exception will be thrown. void skipJSONFieldPlain(ReadBuffer & buf, const StringRef & name_of_filed); /** Read serialized exception. * During serialization/deserialization some information is lost * (type is cut to base class, 'message' replaced by 'displayText', and stack trace is appended to 'message') * Some additional message could be appended to exception (example: you could add information about from where it was received). */ void readException(Exception & e, ReadBuffer & buf, const String & additional_message = ""); void readAndThrowException(ReadBuffer & buf, const String & additional_message = ""); /** Helper function for implementation. */ template static inline const char * tryReadIntText(T & x, const char * pos, const char * end) { bool negative = false; x = 0; if (pos >= end) return pos; while (pos < end) { switch (*pos) { case '+': break; case '-': if (std::is_signed::value) negative = true; else return pos; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': x *= 10; x += *pos - '0'; break; default: if (negative) x = -x; return pos; } ++pos; } if (negative) x = -x; return pos; } /// Convenient methods for reading something from string in text format. template inline T parse(const char * data, size_t size) { T res; ReadBuffer buf(const_cast(data), size, 0); readText(res, buf); return res; } template inline T parse(const char * data) { return parse(data, strlen(data)); } template inline T parse(const String & s) { return parse(s.data(), s.size()); } /** Skip UTF-8 BOM if it is under cursor. * As BOM is usually located at start of stream, and buffer size is usually larger than three bytes, * the function expects, that all three bytes of BOM is fully in buffer (otherwise it don't skip anything). */ inline void skipBOMIfExists(ReadBuffer & buf) { if (!buf.eof() && buf.position() + 3 < buf.buffer().end() && buf.position()[0] == '\xEF' && buf.position()[1] == '\xBB' && buf.position()[2] == '\xBF') { buf.position() += 3; } } }