blob: efe26e8644095ac4f8ff724619bf3341cff7361a [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.
#pragma once
#include <functional>
#include <sstream>
#include <vector>
#include <unordered_map>
#include "common/compiler-util.h"
#include "common/status.h"
#include "rapidjson/error/en.h"
#include "rapidjson/rapidjson.h"
#include "rapidjson/reader.h"
namespace impala {
/// A template class to assist in parsing JSON, using the member function Parse() to parse
/// the JSON text and convert it to a row format defined by the schema. The JSON text
/// consists of multiple JSON objects, each of which is parsed into one row of data.
///
/// Scanner is a class defined for input and output for this class, and it needs to
/// implement the following member functions:
///
/// Returns a boolean value to indicate whether Parse() should end parsing and return.
/// It is called once after parsing each object.
/// bool BreakParse();
///
/// Provides input for the parser, with 'begin' and 'end' being the start and end
/// positions of the next buffer to be parsed.
/// void GetNextBuffer(const char** begin, const char** end);
///
/// Handles errors. This function is called when the following functions encounter an
/// error (return false). If this function returns OK, parsing continues; otherwise, the
/// error status is returned by the Parse().
/// Status HandleError(rapidjson::ParseErrorCode error, size_t offset);
///
/// The following functions materialize output tuples. Functions with void return type
/// must succeed. Functions with bool return type return true on succeed, and return false
/// to stop parsing the whole scan range.
///
/// Called when starting to parse a new object, initializes a new row of data.
/// void InitRow();
///
/// Called when finishing parsing an object, submits a row of data.
/// void SubmitRow();
///
/// Called when encountering a null value during parsing. Index is the index of the key
/// for this value in the schema, and so on for the other following functions.
/// void AddNull(int index);
/// bool AddBool(int index, bool value);
/// bool AddString(int index, const char* str, uint32_t len);
/// bool AddNumber(int index, const char* str, uint32_t len);
///
/// This parser is implemented based on the SAX-style API provided by Rapidjson.
/// This class provides event handler functions for the rapidjson::Reader to achieve JSON
/// parsing. See more details in:
/// https://rapidjson.org/md_doc_sax.html
/// https://rapidjson.org/classrapidjson_1_1_handler.html
template <class Scanner>
class JsonParser {
public:
/// A stream of characters used to wrap the buffer, wrapping the buffer into a format
/// acceptable by RapidJson.
class CharStream {
public:
typedef char Ch;
CharStream(JsonParser* parser) : parser_(parser) { }
/// Determines whether the stream has ended. After the current buffer is parsed,
/// GetNextBuffer is called to request more data from Scanner. It is only considered
/// the end of the stream when Scanner cannot provide more data.
ALWAYS_INLINE bool Eos() {
if (LIKELY(current_ != end_)) return false;
tell_ = Tell();
parser_->GetNextBuffer(&current_, &end_);
begin_ = current_;
return current_ == end_;
}
ALWAYS_INLINE Ch Peek() {
return UNLIKELY(Eos()) ? '\0' : *current_;
}
ALWAYS_INLINE Ch Take() {
return UNLIKELY(Eos()) ? '\0' : *current_++;
}
ALWAYS_INLINE size_t Tell() const {
return static_cast<size_t>(current_ - begin_) + tell_;
}
/// The following functions are only required for the output stream, so we don't need
/// to implement them here. However, to avoid compilation errors, we must explicitly
/// indicate them as not available.
Ch* PutBegin() { CHECK(false); return 0; }
void Put(Ch) { CHECK(false); }
size_t PutEnd(Ch*) { CHECK(false); return 0; }
void Flush() { CHECK(false); }
private:
JsonParser* parser_;
const Ch* current_ = nullptr;
const Ch* begin_ = nullptr;
const Ch* end_ = nullptr;
size_t tell_ = 0;
};
JsonParser(const std::vector<std::string>& schema, Scanner* scanner);
void ResetParser();
/// A debug function that checks whether the parser is tidy. If it is not, it means that
/// unexpected errors may have occurred in the parser.
bool IsTidy();
/// Consume char stream to find the start of the first tuple in this scan range. Return
/// true if found, This function works under the premise that there is only one JSON
/// object per line in the JSON file and there are no newline in the JSON object.
bool MoveToNextJson();
/// Using callback provided by Scanner to parses JSON data and converts it to row
/// format. Returns in the following cases:
/// 1. Maximum parsing row limit max_rows is reached.
/// 2. No more data needs to be parsed (end of stream is reached).
/// 3. An error is encountered when converting to a row, or a parsing error (caused by
/// invalid JSON format, etc.), and Scanner returns an error status after handling
/// the error.
/// 4. Scanner's BreakParse() indicates the need to end parsing.
/// Please note that 'max_rows' and 'num_rows' actually represent the number of
/// top-level JSON values processed by the parser, meaning that if there are top-level
/// arrays, strings, or other JSON values in the JSON data, they will also be included
/// in the count.
Status Parse(int max_rows, int* num_rows);
/// Parse the JSON data and directly count how many top-level JSON objects (excluding
/// nested ones) there are without performing specific data copying and conversion. It
/// behaves similarly to Parse() but is faster, suitable for zero slots scans such as
/// count(*). Different from Parse(), here 'max_rows' and 'num_rows' only count the
/// top-level JSON objects and do not include other top-level JSON values.
Status CountJsonObjects(int max_rows, int* num_rows);
private:
friend class rapidjson::GenericReader<rapidjson::UTF8<>, rapidjson::UTF8<>>;
inline void FinishRow() {
DCHECK(row_initialized_);
for (int i = 0; i < num_fields_; ++i) {
if (UNLIKELY(!field_found_[i])) {
scanner_->AddNull(i);
}
}
scanner_->SubmitRow();
}
inline void GetNextBuffer(const char** begin, const char** end) {
scanner_->GetNextBuffer(begin, end);
}
inline bool IsRequiredField() {
return current_field_idx_ != -1 && object_depth_ == 1 && array_depth_ == 0;
}
/// The following functions are event handlers provided for Rapidjson SAX. When parsing
/// a JSON, the corresponding handlers will be called upon encountering the
/// corresponding element. The main processing flow for a row of data is as follows:
/// 1. Call StartObject() at the beginning of the JSON object to initialize a new row.
/// 2. Call Key() upon encountering a key to find its index of the row in the schema and
/// update current_field_idx_.
/// 3. Call the corresponding type processing function upon encountering a value to add
/// the value to the position pointed to by current_field_idx_ in the row.
/// 4. Call EndObject() upon reaching the end of the JSON object. Add null values for
/// fields not found in the schema, and submit this row.
bool Key(const char* str, uint32_t len, bool copy);
bool StartObject();
bool EndObject(uint32_t mem_count);
bool StartArray();
bool EndArray(uint32_t mem_count);
bool Null();
bool Bool(bool boolean);
bool RawNumber(const char* str, uint32_t len, bool copy);
bool String(const char* str, uint32_t len, bool copy);
/// We used the kParseNumbersAsStringsFlag flag for parsing, which output numerical type
/// values as strings (by calling RawNumber). Therefore, the following handler functions
/// will never be called and no need to be implemented. However, to avoid compilation
/// errors, we still need to explicitly indicate them as not available.
bool Int(int i) { CHECK(false); return false; }
bool Uint(unsigned i) { CHECK(false); return false; }
bool Int64(int64_t i) { CHECK(false); return false; }
bool Uint64(uint64_t i) { CHECK(false); return false; }
bool Double(double d) { CHECK(false); return false; }
/// The number of fields in the schema.
const size_t num_fields_;
/// Scanner pointer used for invoking callback functions.
Scanner* scanner_;
/// Character stream that wraps the JSON data buffer.
CharStream stream_;
/// Mapping of field names to field positions, generated based on the schema and used to
/// locate field positions when assembling rows.
std::unordered_map<std::string, int> field_indexs_;
/// RapidJson's SAX-style JSON parser.
rapidjson::Reader reader_;
/// This is mainly used to determine if we have an unfinished row when an error occurs.
bool row_initialized_;
/// Counter used to record the nesting depth of the current JSON array or object during
/// parsing.
int array_depth_;
int object_depth_;
/// Used to record the current field's position in the row during parsing.
/// Updated by Key() based on 'field_indexs_', consumed and reset by other processors.
/// -1 indicates a not required field (not in the schema).
int current_field_idx_;
/// Used to record which fields have been found in the current row during parsing.
std::vector<char> field_found_;
};
/// A util class used to assist in parsing JSON. When conducting zero slots scans, no
/// actual data from the JSON is needed, only the number of JSON objects. This class is
/// essentially a simplified version of a rapidjson parser (rapidjson::GenericReader),
/// removing specific data parsing and copying operations, allowing for faster parsing of
/// the number of JSON objects.
/// The class retains the ability to recognize malformed JSON and provide specific error
/// codes like rapidjson's parser. However, as it skips specific data parsing, it cannot
/// identify string encoding errors or numeric overflow errors. Nonetheless, these data
/// errors do not affect the counting of JSON objects, and ignoring them is acceptable.
/// Please refer to the following link for code about rapidjson::GenericReader:
/// https://github.com/Tencent/rapidjson/blob/5ec44fb/include/rapidjson/reader.h#L539
template<class Stream>
class JsonSkipper {
public:
JsonSkipper(Stream& stream) : s_(stream) { }
/// Consume the stream until skipping a complete outermost JSON object, return false and
/// log the corresponding error code if an error occurs.
bool SkipNextObject();
bool HasError() { return code_ != rapidjson::kParseErrorNone; }
rapidjson::ParseErrorCode GetErrorCode() { return code_; }
private:
friend class JsonParserTest;
/// This function attempts to consume a character from the stream, if the next character
/// matches the 'expect', take out it and return true, otherwise return false.
inline bool Consume(char expect) {
if (LIKELY(s_.Peek() == expect)) {
s_.Take();
return true;
}
return false;
}
inline void SkipWhitespace() {
char c;
while ((c = s_.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') s_.Take();
}
/// The following function is used to skip a specific JSON value. It maintains logic
/// consistent with rapidjson, consuming the stream and returning true upon successfully
/// skipping the specified value, or returning false and setting the respective error
/// code if an error is encountered.
/// See more details about valid JSON values in: https://rapidjson.org/md_doc_sax.html
bool SkipNull();
bool SkipTrue();
bool SkipFalse();
bool SkipString();
bool SkipNumber();
bool SkipObject();
bool SkipArray();
bool SkipValue();
Stream& s_;
rapidjson::ParseErrorCode code_ = rapidjson::kParseErrorNone;
};
/// A simple c_str wrapper for testing JsonSkipper.
class SimpleStream {
public:
SimpleStream(const char* str) : current_(str) { }
bool Eos() { return *current_ == '\0'; }
char Peek() { return *current_; }
char Take() { return Eos() ? '\0' : *current_++; }
private:
const char* current_ = nullptr;
};
/// A simple class for testing JsonParser.
class SimpleJsonScanner {
public:
using GetBufferFunc = std::function<void(const char**, const char**)>;
SimpleJsonScanner(const std::vector<std::string>& schema, GetBufferFunc get_buffer)
: row_count_(0), parser_(schema, this), get_buffer_(std::move(get_buffer)) {
parser_.ResetParser();
current_row_.resize(schema.size());
}
Status Scan(int max_row, int* num_rows) {
*num_rows = 0;
if (!parser_.IsTidy()) return Status("Parser is not tidy");
RETURN_IF_ERROR(parser_.Parse(max_row, num_rows));
return Status::OK();
}
Status Count(int max_row, int* num_rows) {
*num_rows = 0;
RETURN_IF_ERROR(parser_.CountJsonObjects(max_row, num_rows));
return Status::OK();
}
std::string result() const { return result_.str(); }
size_t row_count() const { return row_count_; }
private:
friend class JsonParser<SimpleJsonScanner>;
Status HandleError(rapidjson::ParseErrorCode error, size_t offset) {
return Status::OK();
}
bool BreakParse() {
return false;
}
void GetNextBuffer(const char** begin, const char** end) {
get_buffer_(begin, end);
}
void InitRow() { }
void SubmitRow() {
for (const auto& s : current_row_) result_ << s << ", ";
result_ << '\n';
++row_count_;
}
void AddNull(int index) {
current_row_[index] = "null";
}
bool AddBool(int index, bool b) {
current_row_[index] = (b ? "true" : "false");
return true;
}
bool AddString(int index, const char* b, uint32_t len) {
current_row_[index] = string(b, len);
return true;
}
bool AddNumber(int index, const char* b, uint32_t len) {
current_row_[index] = string(b, len);
return true;
}
std::vector<std::string> current_row_;
std::stringstream result_;
size_t row_count_;
JsonParser<SimpleJsonScanner> parser_;
GetBufferFunc get_buffer_;
};
} // namespace impala