blob: 59dbe9b7bbeddad2a8bbe979f94d588b369930e8 [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.
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include "exec/json/json-parser.h"
#include "testutil/gtest-util.h"
#include "util/time.h"
using namespace std;
using namespace rapidjson;
namespace impala {
class JsonParserTest : public ::testing::TestWithParam<int> {
public:
JsonParserTest() {
random_shuffle(json_data_.begin(), json_data_.end());
stringstream data_stream, result_stream;
for (const auto& p : json_data_) {
data_stream << p.first << '\n';
if (p.second.empty()) continue;
result_stream << p.second << '\n';
}
data_ = data_stream.str();
result_ = result_stream.str();
}
virtual void SetUp() override {
data_pos_ = 0;
repeats_ = 0;
stream_size_ = GetParam();
}
void Reset(size_t repeats = 0) {
data_pos_ = 0;
repeats_ = repeats;
}
void NextBuffer(const char** begin, const char** end) {
EXPECT_EQ(*begin, *end);
*begin = *end = nullptr;
if (data_pos_ >= data_.size()) {
if (repeats_ == 0) return;
data_pos_ = 0;
--repeats_;
}
*begin = data_.data() + data_pos_;
size_t len = min(stream_size_, data_.size() - data_pos_);
*end = *begin + len;
data_pos_ += len;
}
const vector<string>& schema() const { return schema_; }
const string& result() const { return result_; }
enum JsonValueType {
TYPE_NULL,
TYPE_TRUE,
TYPE_FALSE,
TYPE_STRING,
TYPE_NUMBER,
TYPE_OBJECT,
TYPE_ARRAY,
TYPE_VALUE
};
void TestSkip(const char* v, JsonValueType t, ParseErrorCode e = kParseErrorNone) {
SimpleStream ss(v);
JsonSkipper<SimpleStream> js(ss);
bool res;
switch (t) {
case TYPE_NULL: res = js.SkipNull(); break;
case TYPE_TRUE: res = js.SkipTrue(); break;
case TYPE_FALSE: res = js.SkipFalse(); break;
case TYPE_STRING: res = js.SkipString(); break;
case TYPE_NUMBER: res = js.SkipNumber(); break;
case TYPE_OBJECT: res = js.SkipObject(); break;
case TYPE_ARRAY: res = js.SkipArray(); break;
case TYPE_VALUE: res = js.SkipValue(); break;
default: ASSERT_TRUE(false);
}
if (e == kParseErrorNone) {
EXPECT_TRUE(res) << v;
EXPECT_TRUE(ss.Eos());
} else {
EXPECT_FALSE(res) << v;
EXPECT_EQ(js.GetErrorCode(), e) << v;
}
}
private:
size_t repeats_ = 0;
size_t data_pos_ = 0;
size_t stream_size_;
string data_;
string result_;
vector<string> schema_ = {"name", "bool", "score", "address"};
vector<pair<string, string>> json_data_ = {
// Normal Json
{R"({"name": "Linda", "bool": true, "score": 76.3, "address": "Chicago"})",
"Linda, true, 76.3, Chicago, "},
{R"({"name": "Mike", "bool": null, "score": NaN, "address": "Dallas"})",
"Mike, null, NaN, Dallas, "},
{R"({"name": "Sara", "bool": false, "score": Inf, "address": "Seattle"})",
"Sara, false, Inf, Seattle, "},
// String with escape or special char.
{R"({"name": "Joe\nJoe", "bool": null, "score": 100, "address": "{New}\t{York}"})",
"Joe\nJoe, null, 100, {New}\t{York}, "},
{R"({"name": "$}~{$", "bool": false, "score": 95.2, "address": "\"{Los} \\Angeles"})",
"$}~{$, false, 95.2, \"{Los} \\Angeles, "},
{R"({"name": "A\"}{\"A", "bool": true, "score": 79.4, "address": "[]()[{}{}]"})",
"A\"}{\"A, true, 79.4, []()[{}{}], "},
// Column miss or out-of-order.
{R"({"name": "Grace", "bool": false, "score": 92.3})",
"Grace, false, 92.3, null, "},
{R"({"bool": false, "score": 90.5, "name": "Emily"})",
"Emily, false, 90.5, null, "},
{R"({"score": 87.6, "bool": false, "name": "David", "address": "Boston"})",
"David, false, 87.6, Boston, "},
// Column with complex type.
{R"({"name": "Bob", "bool": true, "score": 78.9, "complex": [1, {"a2": [4, 5, 6]}]})",
"Bob, true, 78.9, null, "},
{R"({"name": "Peter", "object": {"array": [1, 2, 3], "object": {"empty": []}}})",
"Peter, null, null, null, "},
{R"({"name": "Sophia", "array": [1, 2, 3, {"test": null}], "address": "San Diego"})",
"Sophia, null, null, San Diego, "},
// Exposed string, number, or array
{R"("{\"name\": \"Aisha\", \"bool\": true, \"score\": 86.1}")", ""},
{R"(-1234.56789)", ""},
{R"(["Pavel", 123e2, {"test": null}, {"a1": [1, 2, "{abc, [123]}"]}])", ""}
};
};
INSTANTIATE_TEST_SUITE_P(StreamSize, JsonParserTest, ::testing::Values(1, 16, 256));
TEST_P(JsonParserTest, BasicTest) {
SimpleJsonScanner js(schema(), [this](const char** begin, const char** end) {
this->NextBuffer(begin, end);
});
constexpr int max_rows = 10;
int num_rows = 0;
do {
EXPECT_OK(js.Scan(max_rows, &num_rows));
EXPECT_GE(num_rows, 0);
EXPECT_LE(num_rows, max_rows);
} while (num_rows);
EXPECT_EQ(result(), js.result());
}
TEST_P(JsonParserTest, JsonSkipperTest) {
// positive cases
TestSkip("null", TYPE_NULL);
TestSkip("true", TYPE_TRUE);
TestSkip("false", TYPE_FALSE);
TestSkip(R"("abc")", TYPE_STRING);
TestSkip(R"(" \n\t\r")", TYPE_STRING);
TestSkip(R"("\0\1\2")", TYPE_STRING);
TestSkip(R"("\u123\"\'\\")", TYPE_STRING);
TestSkip(R"("你好🙂")", TYPE_STRING);
TestSkip(R"("\u009f\u0099\u0082")", TYPE_STRING);
TestSkip("1.024", TYPE_NUMBER);
TestSkip("-9.9", TYPE_NUMBER);
TestSkip("2e10", TYPE_NUMBER);
TestSkip("-2e-10", TYPE_NUMBER);
TestSkip("Inf", TYPE_NUMBER);
TestSkip("-Infinity", TYPE_NUMBER);
TestSkip("NaN", TYPE_NUMBER);
TestSkip("{}", TYPE_OBJECT);
TestSkip(R"({"a":null, "b":[1,true,false]})", TYPE_OBJECT);
TestSkip(R"({"a":null, "b":{"c":"d"}})", TYPE_OBJECT);
TestSkip(R"({"a":null, "b":[{"k1":"v1"}, {"k2":"v2"}]})", TYPE_OBJECT);
TestSkip("[]", TYPE_ARRAY);
TestSkip(R"(["",true,false])", TYPE_ARRAY);
TestSkip(R"(["]",{"":[{},[{}]]}])", TYPE_ARRAY);
TestSkip(R"(["",{},[[[]]],{"a":[1,2],"":""}])", TYPE_ARRAY);
TestSkip("null", TYPE_VALUE);
TestSkip(R"("abc")", TYPE_VALUE);
TestSkip("1.024", TYPE_VALUE);
TestSkip("{}", TYPE_VALUE);
TestSkip("[]", TYPE_VALUE);
// negative cases
TestSkip("nuLL", TYPE_NULL, kParseErrorValueInvalid);
TestSkip("tRue", TYPE_TRUE, kParseErrorValueInvalid);
TestSkip("flase", TYPE_FALSE, kParseErrorValueInvalid);
TestSkip(R"("abc\")", TYPE_STRING, kParseErrorStringMissQuotationMark);
TestSkip(R"("你好🙂\")", TYPE_STRING, kParseErrorStringMissQuotationMark);
TestSkip(R"("\u009f\u0099\u00\")", TYPE_STRING, kParseErrorStringMissQuotationMark);
TestSkip("+1", TYPE_NUMBER, kParseErrorValueInvalid);
TestSkip(".123", TYPE_NUMBER, kParseErrorValueInvalid);
TestSkip("1.", TYPE_NUMBER, kParseErrorNumberMissFraction);
TestSkip("2e", TYPE_NUMBER, kParseErrorNumberMissExponent);
TestSkip("{1}", TYPE_OBJECT, kParseErrorObjectMissName);
TestSkip(R"({"a""b"})", TYPE_OBJECT, kParseErrorObjectMissColon);
TestSkip(R"({"a":})", TYPE_OBJECT, kParseErrorValueInvalid);
TestSkip(R"({"a":"b")", TYPE_OBJECT, kParseErrorObjectMissCommaOrCurlyBracket);
TestSkip(R"({"a":null, "b":{1,true,false}})", TYPE_OBJECT, kParseErrorObjectMissName);
TestSkip("[,false]", TYPE_ARRAY, kParseErrorValueInvalid);
TestSkip("[true,]", TYPE_ARRAY, kParseErrorValueInvalid);
TestSkip("[true,false", TYPE_ARRAY, kParseErrorArrayMissCommaOrSquareBracket);
TestSkip("[[1,2]", TYPE_ARRAY, kParseErrorArrayMissCommaOrSquareBracket);
TestSkip(R"(["],"a","b"])", TYPE_ARRAY, kParseErrorArrayMissCommaOrSquareBracket);
TestSkip("Null", TYPE_VALUE, kParseErrorValueInvalid);
TestSkip(R"({"abc\":1})", TYPE_VALUE, kParseErrorStringMissQuotationMark);
TestSkip("-2.e4", TYPE_VALUE, kParseErrorNumberMissFraction);
TestSkip(R"({"a":b})", TYPE_VALUE, kParseErrorValueInvalid);
TestSkip("[,]", TYPE_VALUE, kParseErrorValueInvalid);
}
TEST_P(JsonParserTest, CountJsonObjectsTest) {
SimpleJsonScanner js({}, [this](const char** begin, const char** end) {
this->NextBuffer(begin, end);
});
constexpr int max_rows = 1024;
int num_rows = 0, row_count = 0;;
int64_t scan_start_time = UnixMicros();
Reset(1000);
do {
EXPECT_OK(js.Scan(max_rows, &num_rows));
} while (num_rows);
int64_t count_start_time = UnixMicros();
Reset(1000);
do {
EXPECT_OK(js.Count(max_rows, &num_rows));
row_count += num_rows;
} while (num_rows);
int64_t end_time = UnixMicros();
EXPECT_EQ(row_count, js.row_count());
LOG(INFO) << "JSON Scan cost time in ms: "
<< static_cast<double>(count_start_time - scan_start_time) / 1000
<< ", JSON Count cost time in ms: "
<< static_cast<double>(end_time - count_start_time) / 1000;
}
}