summaryrefslogtreecommitdiffstats
path: root/yql/essentials/minikql/jsonpath/ut/common_ut.cpp
diff options
context:
space:
mode:
authorvvvv <[email protected]>2024-11-06 23:54:28 +0300
committervvvv <[email protected]>2024-11-07 00:04:25 +0300
commitcf2a23963ac10add28c50cc114fbf48953eca5aa (patch)
tree174b849b8ecfa96b0c8e4409ab3287721a9210c8 /yql/essentials/minikql/jsonpath/ut/common_ut.cpp
parent3a3113a2bf5a7fab32bde414932082b264c559fc (diff)
Prepare move yql/minikql YQL-19206
types,jsonpath,dom commit_hash:6b54be5968b6a30b6d97fe3a1611574bcefc749e
Diffstat (limited to 'yql/essentials/minikql/jsonpath/ut/common_ut.cpp')
-rw-r--r--yql/essentials/minikql/jsonpath/ut/common_ut.cpp972
1 files changed, 972 insertions, 0 deletions
diff --git a/yql/essentials/minikql/jsonpath/ut/common_ut.cpp b/yql/essentials/minikql/jsonpath/ut/common_ut.cpp
new file mode 100644
index 00000000000..a32389a7689
--- /dev/null
+++ b/yql/essentials/minikql/jsonpath/ut/common_ut.cpp
@@ -0,0 +1,972 @@
+#include "test_base.h"
+
+#include <util/string/builder.h>
+
+#include <cmath>
+
+class TJsonPathCommonTest : public TJsonPathTestBase {
+public:
+ TJsonPathCommonTest()
+ : TJsonPathTestBase()
+ {
+ }
+
+ UNIT_TEST_SUITE(TJsonPathCommonTest);
+ UNIT_TEST(TestPrimary);
+ UNIT_TEST(TestMemberAccess);
+ UNIT_TEST(TestWildcardMemberAccess);
+ UNIT_TEST(TestArrayAccess);
+ UNIT_TEST(TestLastArrayIndex);
+ UNIT_TEST(TestLastArrayIndexInvalid);
+ UNIT_TEST(TestNonIntegerArrayIndex);
+ UNIT_TEST(TestWildcardArrayAccess);
+ UNIT_TEST(TestUnaryOperations);
+ UNIT_TEST(TestUnaryOperationsErrors);
+ UNIT_TEST(TestBinaryArithmeticOperations);
+ UNIT_TEST(TestBinaryArithmeticOperationsErrors);
+ UNIT_TEST(TestParseErrors);
+ UNIT_TEST(TestVariables);
+ UNIT_TEST(TestDivisionByZero);
+ UNIT_TEST(TestInfinityResult);
+ UNIT_TEST(TestLogicalOperations);
+ UNIT_TEST(TestCompareOperations);
+ UNIT_TEST(TestFilter);
+ UNIT_TEST(TestFilterInvalid);
+ UNIT_TEST(TestNumericMethods);
+ UNIT_TEST(TestNumericMethodsErrors);
+ UNIT_TEST(TestDoubleMethod);
+ UNIT_TEST(TestDoubleMethodErrors);
+ UNIT_TEST(TestTypeMethod);
+ UNIT_TEST(TestSizeMethod);
+ UNIT_TEST(TestKeyValueMethod);
+ UNIT_TEST(TestKeyValueMethodErrors);
+ UNIT_TEST(TestStartsWithPredicate);
+ UNIT_TEST(TestStartsWithPredicateErrors);
+ UNIT_TEST(TestExistsPredicate);
+ UNIT_TEST(TestIsUnknownPredicate);
+ UNIT_TEST(TestLikeRegexPredicate);
+ UNIT_TEST_SUITE_END();
+
+ void TestPrimary() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ // Context object $ must return whole JSON when used alone
+ {R"({"key": 123})", "$", {R"({"key":123})"}},
+ {R"([1, 2, 3])", "$", {R"([1,2,3])"}},
+ {"1.234", "$", {"1.234"}},
+ {R"("some string")", "$", {R"("some string")"}},
+
+ // Literal must not depend on input
+ {R"({"key": 123})", "123", {"123"}},
+ {R"([1, 2, 3])", "123", {"123"}},
+ {"1.234", "123", {"123"}},
+ {R"("some string")", "123", {"123"}},
+
+ // Check various ways to define number literal
+ {"1", "123.4", {"123.4"}},
+ {"1", "0.567", {"0.567"}},
+
+ {"1", "1234e-1", {"123.4"}},
+ {"1", "567e-3", {"0.567"}},
+ {"1", "123.4e-1", {"12.34"}},
+
+ {"1", "123e3", {"123000"}},
+ {"1", "123e+3", {"123000"}},
+ {"1", "1.23e+1", {"12.3"}},
+ {"1", "1.23e1", {"12.3"}},
+
+ {"1", "12e0", {"12"}},
+ {"1", "12.3e0", {"12.3"}},
+
+ {"1", "0", {"0"}},
+ {"1", "0.0", {"0"}},
+ {"1", "0.0e0", {"0"}},
+
+ // Check boolean and null literals
+ {"1", "null", {"null"}},
+ {"1", "false", {"false"}},
+ {"1", "true", {"true"}},
+
+ // Check string literals
+ {"1", "\"string\"", {"\"string\""}},
+ {"1", "\" space another space \"", {"\" space another space \""}},
+ {"1", "\"привет\"", {"\"привет\""}},
+ // NOTE: escaping is added by library/cpp/json
+ {"1", "\"\r\n\t\"", {"\"\\r\\n\\t\""}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestMemberAccess() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"({"key": 123, "another_key": 456})", "$.key", {"123"}},
+ {R"({"key": 123, "_another_28_key_$_": 456})", "$._another_28_key_$_", {"456"}},
+ {R"({"key": 123, "another_key": 456})", " $.another_key ", {"456"}},
+
+ {R"({"key": 123, "another_key": 456})", "$.key", {"123"}},
+ {R"({"k\"ey": 123, "another_key": 456})", "$.\"k\\\"ey\"", {"123"}},
+ {R"({"k\"ey": 123, "another_key": 456})", "$.'k\\\"ey'", {"123"}},
+
+ {R"({"key": 123, "another_key": 456})", "$.'key'", {"123"}},
+ {R"({"key": 123, "_another_28_key_$_": 456})", "$.'_another_28_key_$_'", {"456"}},
+ {R"({"key": 123, "another_key": 456})", " $.'another_key' ", {"456"}},
+
+ {R"({"key": 123, "another_key": 456})", "$.\"key\"", {"123"}},
+ {R"({"key": 123, "_another_28_key_$_": 456})", "$.\"_another_28_key_$_\"", {"456"}},
+ {R"({"key": 123, "another_key": 456})", " $.\"another_key\" ", {"456"}},
+
+ {R"({"key": 123, "another key": 456})", "$.'another key'", {"456"}},
+ {R"({"key": 123, "another key": 456})", "$.\"another key\"", {"456"}},
+
+ {R"({"key": 123, "прием отбой": 456})", "$.'прием отбой'", {"456"}},
+ {R"({"key": 123, "прием отбой": 456})", "$.\"прием отбой\"", {"456"}},
+
+ {R"({"key": {"another": 456}})", "$.key.another", {"456"}},
+ {R"({"key": {"another key": 456}})", "$.'key'.\"another key\"", {"456"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestWildcardMemberAccess() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"({
+ "first": 12,
+ "second": 72
+ })", "$.*", {"12", "72"}},
+ {R"({
+ "friends": {
+ "Nik": {"age": 18},
+ "Kate": {"age": 72}
+ }
+ })", "$.friends.*.age", {"72", "18"}},
+ {R"({
+ "friends": {
+ "Nik": {"age": 18},
+ "Kate": {"age": 72}
+ }
+ })", "$.*.*.*", {"72", "18"}},
+ {R"({})", "$.*.key", {}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestArrayAccess() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"([1, 2, 3])", "$[0]", {"1"}},
+ {R"([1, 2, 3, 4, 5, 6])", "$[0 to 2]", {"1", "2", "3"}},
+ {R"([1, 2, 3, 4, 5, 6])", "$[5, 0 to 2, 0, 0, 3 to 5, 2]", {"6", "1", "2", "3", "1", "1", "4", "5", "6", "3"}},
+ {R"({
+ "friends": [
+ {"name": "Nik", "age": 18},
+ {"name": "Kate", "age": 72},
+ {"name": "Foma", "age": 50},
+ {"name": "Jora", "age": 60}
+ ]
+ })", "$.friends[1 to 3, 0].age", {"72", "50", "60", "18"}},
+ {R"({
+ "range": {
+ "from": 1,
+ "to": 2
+ },
+ "friends": [
+ {"name": "Nik", "age": 18},
+ {"name": "Kate", "age": 72},
+ {"name": "Foma", "age": 50},
+ {"name": "Jora", "age": 60}
+ ]
+ })", "$.friends[$.range.from to $.range.to].age", {"72", "50"}},
+ {R"({
+ "range": {
+ "from": [1, 3, 4],
+ "to": {"key1": 1, "key2": 2, "key3": 3}
+ },
+ "friends": [
+ {"name": "Nik", "age": 18},
+ {"name": "Kate", "age": 72},
+ {"name": "Foma", "age": 50},
+ {"name": "Jora", "age": 60}
+ ]
+ })", "$.friends[$.range.from[1] to $.range.to.key3].age", {"60"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestLastArrayIndex() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"([1, 2, 3])", "$[last]", {"3"}},
+ {R"([1, 2, 3])", "$[1 to last]", {"2", "3"}},
+ {R"([1, 2, 3])", "$[last to last]", {"3"}},
+ {R"([1, 2, 3, 5, 6])", "$[1, last, last, 0, 2 to last, 3]", {"2", "6", "6", "1", "3", "5", "6", "5"}},
+ {R"([
+ [1, 2, 3, 4],
+ [5, 6, 7, 8]
+ ])", "$[*][last]", {"4", "8"}},
+ {R"({
+ "ranges": [
+ {"from": 1, "to": 3},
+ {"from": 0, "to": 1}
+ ],
+ "friends": [
+ {"name": "Nik", "age": 18},
+ {"name": "Kate", "age": 72},
+ {"name": "Foma", "age": 50},
+ {"name": "Jora", "age": 60}
+ ]
+ })", "$.friends[last, $.ranges[last].from to $.ranges[last].to, 2 to last].age", {"60", "18", "72", "50", "60"}},
+ {R"({
+ "ranges": [
+ {"from": 1.23, "to": 3.75},
+ {"from": 0.58, "to": 1.00001}
+ ],
+ "friends": [
+ {"name": "Nik", "age": 18},
+ {"name": "Kate", "age": 72},
+ {"name": "Foma", "age": 50},
+ {"name": "Jora", "age": 60}
+ ]
+ })", "$.friends[last, $.ranges[last].from to $.ranges[last].to, 2 to last].age", {"60", "18", "72", "50", "60"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestLastArrayIndexInvalid() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {R"({})", "last", C(TIssuesIds::JSONPATH_LAST_OUTSIDE_OF_ARRAY_SUBSCRIPT)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestNonIntegerArrayIndex() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {R"({
+ "range": {
+ "from": [1, 3, 4],
+ "to": {"key1": 1, "key2": 2, "key3": 3}
+ },
+ "friends": [1, 2, 3]
+ })", "$.friends[$.range.from[*] to $.range.to.*]", C(TIssuesIds::JSONPATH_INVALID_ARRAY_INDEX)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestWildcardArrayAccess() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"([1, 2, 3])", "$[*]", {"1", "2", "3"}},
+ {R"([[1], [2], [3, 4, 5]])", "$[*][*]", {"1", "2", "3", "4", "5"}},
+ {R"({
+ "friends": [
+ {"name": "Nik", "age": 18},
+ {"name": "Kate", "age": 72},
+ {"name": "Foma", "age": 50},
+ {"name": "Jora", "age": 60}
+ ]
+ })", "$.friends[*].age", {"18", "72", "50", "60"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestUnaryOperations() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"([])", "-3", {"-3"}},
+ {R"([])", "+3", {"3"}},
+ {R"(-1)", "-$", {"1"}},
+ {R"(-1)", "+$", {"-1"}},
+ {R"({
+ "range": {
+ "from": -1,
+ "to": -2
+ },
+ "array": [1, 2, 3, 4]
+ })", "$.array[-$.range.from to -$.range.to]", {"2", "3"}},
+ {R"({
+ "range": {
+ "from": 1,
+ "to": -2
+ },
+ "array": [1, 2, 3, 4]
+ })", "$.array[+$.range.from to -$.range.to]", {"2", "3"}},
+ {R"({
+ "range": {
+ "from": -1,
+ "to": 2
+ },
+ "array": [1, 2, 3, 4]
+ })", "$.array[-$.range.from to +$.range.to]", {"2", "3"}},
+ {R"({
+ "range": {
+ "from": 1,
+ "to": 2
+ },
+ "array": [1, 2, 3, 4]
+ })", "$.array[+$.range.from to +$.range.to]", {"2", "3"}},
+ {R"([1, 2, 3])", "-$[*]", {"-1", "-2", "-3"}},
+ {"30000000000000000000000000", "-$", {"-3e+25"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestUnaryOperationsErrors() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {R"({})", "-$", C(TIssuesIds::JSONPATH_INVALID_UNARY_OPERATION_ARGUMENT_TYPE)},
+ {R"([1, 2, [], 4])", "-$[*]", C(TIssuesIds::JSONPATH_INVALID_UNARY_OPERATION_ARGUMENT_TYPE)},
+ {R"([1, 2, {}, 4])", "-$[*]", C(TIssuesIds::JSONPATH_INVALID_UNARY_OPERATION_ARGUMENT_TYPE)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestBinaryArithmeticOperations() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {"[]", "1 + 2", {"3"}},
+ {"[]", "1 - 2", {"-1"}},
+ {"[]", "10 * 5", {"50"}},
+ {"[]", "10 / 5", {"2"}},
+ {"[]", "13 % 5", {"3"}},
+
+ {"[]", "20 * 2 + 5", {"45"}},
+ {"[]", "20 / 2 + 5", {"15"}},
+ {"[]", "20 % 2 + 5", {"5"}},
+
+ {"[]", "20 * (2 + 5)", {"140"}},
+ {"[]", "20 / (2 + 3)", {"4"}},
+ {"[]", "20 % (2 + 5)", {"6"}},
+
+ {"[]", "5 / 2", {"2.5"}},
+ {"[5.24 , 2.62]", "$[0] / $[1]", {"2"}},
+ {"[5.24, 2.62]", "$[0] % $[1]", {"0"}},
+ {"[3.753, 2.35]", "$[0] % $[1]", {"1.403"}},
+
+ {"[]", "- 1 + 1", {"0"}},
+ {"[]", "+ 1 + 1", {"2"}},
+
+ {"[1, 2, 3, 4]", "$[last, last-1, last-2, last-3]", {"4", "3", "2", "1"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestBinaryArithmeticOperationsErrors() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {"[1, 2, 3]", "$[*] + 1", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT)},
+ {"[1, 2, 3]", "1 + $[*]", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT)},
+ {"[1, 2, 3]", "$[*] + $[*]", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT)},
+
+ {"[1, 2, 3]", "$ + 1", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT_TYPE)},
+ {"[1, 2, 3]", "1 + $", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT_TYPE)},
+ {"[1, 2, 3]", "$ + $", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT_TYPE)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestParseErrors() {
+ const TVector<TString> testCases = {
+ "strict",
+ "strict smth.key",
+ "strict $.",
+ "strict $.$key",
+ "strict $.28key",
+ "strict $.ke^y",
+ "strict $.привет",
+ "strict $._пока_28_ключ_$_",
+ " strict $.пока ",
+ "lax",
+ "lax smth.key",
+ "lax $.",
+ "lax $.$key",
+ "lax $.28key",
+ "lax $.ke^y",
+ "lax $.привет",
+ "lax $._пока_28_ключ_$_",
+ " lax $.пока ",
+ "12.",
+ "12..3",
+ "12.3e",
+ "12.3e++1",
+ "12.3e--1",
+ "1e100000000000000000000000000000000",
+ "true || false",
+ "1 && (true == true)",
+ "!true",
+ "$[*] ? (@.active) . id",
+ "!(1 > 2).type()",
+ "(null) is unknown",
+ "(12 * 12) is unknown",
+ R"($ like_regex "[[[")",
+ R"($ like_regex "[0-9]+" flag "x")",
+ "$.first fjrfrfq fqijrhfqiwrjhfqrf qrfqr",
+ };
+
+ for (const auto& testCase : testCases) {
+ RunParseErrorTestCase(testCase);
+ }
+ }
+
+ void TestVariables() {
+ TVector<TVariablesTestCase> testCases = {
+ {"123", {{"var", "456"}}, "$ + $var", {"579"}},
+ {"123", {{"var", "456"}}, "$var", {"456"}},
+ {"123", {{"var", R"({"key": [1, 2, 3, 4, 5]})"}}, "$var.key[2 to last]", {"3", "4", "5"}},
+ {"123", {{"to", "1"}, {"strict", "2"}}, "$to + $strict", {"3"}},
+ };
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunVariablesTestCase(testCase.Json, testCase.Variables, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestDivisionByZero() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {"0", "1 / $", C(TIssuesIds::JSONPATH_DIVISION_BY_ZERO)},
+ {"0.00000000000000000001", "1 / $", C(TIssuesIds::JSONPATH_DIVISION_BY_ZERO)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestInfinityResult() {
+ const double step = 1000000000;
+ double current = step;
+ TStringBuilder literal;
+ TStringBuilder query;
+ literal << '"' << step;
+ query << step;
+ while (!std::isinf(current)) {
+ query << " * " << step;
+ literal << "000000000";
+ current *= step;
+ }
+ literal << '"';
+
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {"0", TString(query), C(TIssuesIds::JSONPATH_BINARY_OPERATION_RESULT_INFINITY)},
+ {TString(literal), "$.double()", C(TIssuesIds::JSONPATH_INFINITE_NUMBER_STRING)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestLogicalOperations() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ // JsonPath does not allow to use boolean literals in boolean operators.
+ // Here we use their replacements:
+ // 1. "(1 < true)" for "null"
+ // 2. "(true == true)" for "true"
+ // 3. "(true != true)" for "false"
+ {"1", "(1 < true) || (1 < true)", {"null"}},
+ {"1", "(1 < true) || (true != true)", {"null"}},
+ {"1", "(1 < true) || (true == true)", {"true"}},
+ {"1", "(true != true) || (1 < true)", {"null"}},
+ {"1", "(true != true) || (true != true)", {"false"}},
+ {"1", "(true != true) || (true == true)", {"true"}},
+ {"1", "(true == true) || (1 < true)", {"true"}},
+ {"1", "(true == true) || (true != true)", {"true"}},
+ {"1", "(true == true) || (true == true)", {"true"}},
+
+ {"1", "(1 < true) && (1 < true)", {"null"}},
+ {"1", "(1 < true) && (true != true)", {"false"}},
+ {"1", "(1 < true) && (true == true)", {"null"}},
+ {"1", "(true != true) && (1 < true)", {"false"}},
+ {"1", "(true != true) && (true != true)", {"false"}},
+ {"1", "(true != true) && (true == true)", {"false"}},
+ {"1", "(true == true) && (1 < true)", {"null"}},
+ {"1", "(true == true) && (true != true)", {"false"}},
+ {"1", "(true == true) && (true == true)", {"true"}},
+
+ {"1", "(true != true) && (true != true) || (true == true)", {"true"}},
+ {"1", "(true != true) && ((true != true) || (true == true))", {"false"}},
+ {"1", "(true != true) || (true != true) || (true == true)", {"true"}},
+ {"1", "(true == true) && (true == true) && (true == true) && (true != true)", {"false"}},
+
+ {"1", "!(1 < true)", {"null"}},
+ {"1", "!(true != true)", {"true"}},
+ {"1", "!(true == true)", {"false"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestCompareOperations() {
+ const TVector<TString> operations = {"==", "<", "<=", ">", ">=", "!=", "<>"};
+ // All compare operations between null and non-null operands are false
+ for (const auto& op : operations) {
+ RunTestCase("1", TStringBuilder() << "null " << op << " 1", {"false"});
+ RunTestCase("1", TStringBuilder() << "1 " << op << " null", {"false"});
+ }
+
+ // If one of the operands is not scalar, comparison results to null
+ for (const auto& op : operations) {
+ RunTestCase("[[]]", TStringBuilder() << "$ " << op << " 1", {"null"});
+ RunTestCase("[[]]", TStringBuilder() << "1 " << op << " $", {"null"});
+ RunTestCase("[[]]", TStringBuilder() << "$ " << op << " $", {"null"});
+
+ RunTestCase("{}", TStringBuilder() << "$ " << op << " 1", {"null"});
+ RunTestCase("{}", TStringBuilder() << "1 " << op << " $", {"null"});
+ RunTestCase("{}", TStringBuilder() << "$ " << op << " $", {"null"});
+ }
+
+ // If both operands are null, only == is true
+ for (const auto& op : operations) {
+ const TString result = op == "==" ? "true" : "false";
+ RunTestCase("1", TStringBuilder() << "null " << op << " null", {result});
+ }
+
+ const TVector<TMultiOutputTestCase> testCases = {
+ // Check comparison of numbers
+ {"1", "1.23 < 4.56", {"true"}},
+ {"1", "1.23 > 4.56", {"false"}},
+ {"1", "1.23 <= 4.56", {"true"}},
+ {"1", "1.23 >= 4.56", {"false"}},
+ {"1", "1.23 == 1.23", {"true"}},
+ {"1", "1.23 != 1.23", {"false"}},
+ {"1", "1.23 <> 4.56", {"true"}},
+ {"1", "1.00000000000000000001 == 1.00000000000000000002", {"true"}},
+
+ // Check numbers of different kinds (int64 vs double)
+ {"1", "1 < 2.33", {"true"}},
+ {"1", "1 > 4.56", {"false"}},
+ {"1", "1 <= 4.56", {"true"}},
+ {"1", "1 >= 4.56", {"false"}},
+ {"1", "1 == 1.23", {"false"}},
+ {"1", "1 != 1.23", {"true"}},
+ {"1", "1 <> 4.56", {"true"}},
+
+ // Check comparison of strings
+ {"1", R"("abc" < "def")", {"true"}},
+ {"1", R"("abc" > "def")", {"false"}},
+ {"1", R"("abc" <= "def")", {"true"}},
+ {"1", R"("abc" >= "def")", {"false"}},
+ {"1", R"("abc" == "abc")", {"true"}},
+ {"1", R"("abc" != "abc")", {"false"}},
+ {"1", R"("abc" <> "def")", {"true"}},
+
+ // Check comparison of UTF8 strings
+ // First string is U+00e9 (LATIN SMALL LETTER E WITH ACUTE), "é"
+ // Second string is U+0065 (LATIN SMALL LETTER E) U+0301 (COMBINING ACUTE ACCENT), "é"
+ {"1", R"("é" < "é")", {"false"}},
+ {"1", R"("é" > "é")", {"true"}},
+ {"1", R"("привет" == "привет")", {"true"}},
+
+ // Check cross-product comparison
+ {R"({
+ "left": [1],
+ "right": [4, 5, 6]
+ })", "$.left[*] < $.right[*]", {"true"}},
+ {R"({
+ "left": [4, 5, 6],
+ "right": [1]
+ })", "$.left[*] < $.right[*]", {"false"}},
+ {R"({
+ "left": [1, 2, 3],
+ "right": [4, 5, 6]
+ })", "$.left[*] < $.right[*]", {"true"}},
+ {R"({
+ "left": [10, 30, 40],
+ "right": [1, 2, 15]
+ })", "$.left[*] < $.right[*]", {"true"}},
+ {R"({
+ "left": [10, 30, 40],
+ "right": [1, 2, 3]
+ })", "$.left[*] < $.right[*]", {"false"}},
+
+ // Check incomparable types
+ {"1", "1 < true", {"null"}},
+ {"1", R"(true <> "def")", {"null"}},
+
+ // Check error in arguments
+ {R"({
+ "array": [1, 2, 3, 4, 5],
+ "invalid_index": {
+ "key": 1
+ }
+ })", "$.array[$.invalid_index] < 3", {"null"}},
+ {R"({
+ "array": [1, 2, 3, 4, 5],
+ "invalid_index": {
+ "key": 1
+ }
+ })", "5 >= $.array[$.invalid_index]", {"null"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestFilter() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {"[1, 2, 3]", "$[*] ? (@ > 2)", {"3"}},
+ {R"([
+ {"age": 18},
+ {"age": 25},
+ {"age": 50},
+ {"age": 5}
+ ])", "$[*] ? (@.age >= 18)", {R"({"age":18})", R"({"age":25})", R"({"age":50})"}},
+ {R"([
+ {"age": 18},
+ {"age": 25},
+ {"age": 50},
+ {"age": 5}
+ ])", "$[*] ? (@.age >= 18) ? (@.age <= 30)", {R"({"age":18})", R"({"age":25})"}},
+ {R"([
+ {"age": 18},
+ {"age": 25},
+ {"age": 50},
+ {"age": 5}
+ ])", "$[*] ? (@.age >= 18) ? (@.age <= 30) . age", {"18", "25"}},
+ {R"([
+ {"age": 18},
+ {"age": 25},
+ {"age": 50},
+ {"age": 5}
+ ])", "$[*] ? (@.age >= 18 && @.age <= 30) . age", {"18", "25"}},
+ {R"([
+ {"age": 18},
+ {"age": 25},
+ {"age": 50},
+ {"age": 5}
+ ])", "$[*] ? (@.age >= 18 || @.age <= 30) . age", {"18", "25", "50", "5"}},
+ {R"([
+ {
+ "id": 1,
+ "is_valid": false,
+ "days_till_doom": 11,
+ "age_estimation": 4
+ },
+ {
+ "id": 2,
+ "is_valid": true,
+ "days_till_doom": 5,
+ "age_estimation": 3
+ },
+ {
+ "id": 3,
+ "is_valid": true,
+ "days_till_doom": 20,
+ "age_estimation": 10
+ },
+ {
+ "id": 4,
+ "is_valid": true,
+ "days_till_doom": 30,
+ "age_estimation": 2
+ }
+ ])", "$[*] ? (@.is_valid == true && @.days_till_doom > 10 && 2 * @.age_estimation <= 12).id", {"4"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestFilterInvalid() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {R"({})", "@", C(TIssuesIds::JSONPATH_FILTER_OBJECT_OUTSIDE_OF_FILTER)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestNumericMethods() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {"[-1.23, 4.56, 3, 0]", "$[*].abs()", {"1.23", "4.56", "3", "0"}},
+ {"[-1.23, 4.56, 3, 0]", "$[*].floor()", {"-2", "4", "3", "0"}},
+ {"[-1.23, 4.56, 3, 0]", "$[*].ceiling()", {"-1", "5", "3", "0"}},
+ {"-123.45", "$.ceiling().abs().floor()", {"123"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestNumericMethodsErrors() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {R"(["1", true, null])", "$[*].abs()", C(TIssuesIds::JSONPATH_INVALID_NUMERIC_METHOD_ARGUMENT)},
+ {R"(["1", true, null])", "$[*].floor()", C(TIssuesIds::JSONPATH_INVALID_NUMERIC_METHOD_ARGUMENT)},
+ {R"(["1", true, null])", "$[*].ceiling()", C(TIssuesIds::JSONPATH_INVALID_NUMERIC_METHOD_ARGUMENT)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestDoubleMethod() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"([
+ "123", "123.4", "0.567", "1234e-1", "567e-3", "123.4e-1",
+ "123e3", "123e+3", "1.23e+1", "1.23e1",
+ "12e0", "12.3e0", "0", "0.0", "0.0e0"
+ ])", "$[*].double()", {
+ "123", "123.4", "0.567", "123.4", "0.567", "12.34",
+ "123000", "123000", "12.3", "12.3",
+ "12", "12.3", "0", "0", "0",
+ }},
+ {R"("-123.45e1")", "$.double().abs().floor()", {"1234"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestDoubleMethodErrors() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {R"(["1", true, null])", "$[*].double()", C(TIssuesIds::JSONPATH_INVALID_DOUBLE_METHOD_ARGUMENT)},
+ {R"("hi stranger")", "$.double()", C(TIssuesIds::JSONPATH_INVALID_NUMBER_STRING)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestTypeMethod() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {"null", "$.type()", {"\"null\""}},
+ {"true", "$.type()", {"\"boolean\""}},
+ {"false", "$.type()", {"\"boolean\""}},
+ {"1", "$.type()", {"\"number\""}},
+ {"-1", "$.type()", {"\"number\""}},
+ {"4.56", "$.type()", {"\"number\""}},
+ {"-4.56", "$.type()", {"\"number\""}},
+ {"\"some string\"", "$.type()", {"\"string\""}},
+ {"[]", "$.type()", {"\"array\""}},
+ {"[1, 2, 3, 4]", "$.type()", {"\"array\""}},
+ {"{}", "$.type()", {"\"object\""}},
+ {"{\"key\": 123}", "$.type()", {"\"object\""}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestSizeMethod() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {"null", "$.size()", {"1"}},
+ {"true", "$.size()", {"1"}},
+ {"false", "$.size()", {"1"}},
+ {"1", "$.size()", {"1"}},
+ {"-1", "$.size()", {"1"}},
+ {"4.56", "$.size()", {"1"}},
+ {"-4.56", "$.size()", {"1"}},
+ {"\"some string\"", "$.size()", {"1"}},
+ {"[]", "$.size()", {"0"}},
+ {"[1, 2, 3, 4]", "$.size()", {"4"}},
+ {"{}", "$.size()", {"1"}},
+ {"{\"key\": 123}", "$.size()", {"1"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestKeyValueMethod() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"({
+ "one": 1,
+ "two": 2,
+ "three": 3
+ })", "$.keyvalue()", {
+ R"({"name":"one","value":1})",
+ R"({"name":"three","value":3})",
+ R"({"name":"two","value":2})",
+ }},
+ {R"({
+ "one": "string",
+ "two": [1, 2, 3, 4],
+ "three": [4, 5]
+ })", R"($.keyvalue() ? (@.value.type() == "array" && @.value.size() > 2).name)", {"\"two\""}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestKeyValueMethodErrors() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {"\"string\"", "$.keyvalue()", C(TIssuesIds::JSONPATH_INVALID_KEYVALUE_METHOD_ARGUMENT)},
+ {"[1, 2, 3, 4]", "$.keyvalue()", C(TIssuesIds::JSONPATH_INVALID_KEYVALUE_METHOD_ARGUMENT)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestStartsWithPredicate() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {"1", R"("some string" starts with "some")", {"true"}},
+ {"1", R"("some string" starts with "string")", {"false"}},
+ {R"(["some string", "string"])", R"($[*] ? (@ starts with "string"))", {"\"string\""}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestStartsWithPredicateErrors() {
+ const TVector<TRuntimeErrorTestCase> testCases = {
+ {R"(["first", "second"])", R"($[*] starts with "first")", C(TIssuesIds::JSONPATH_INVALID_STARTS_WITH_ARGUMENT)},
+ {"1", R"(1 starts with "string")", C(TIssuesIds::JSONPATH_INVALID_STARTS_WITH_ARGUMENT)},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
+ }
+ }
+ }
+
+ void TestExistsPredicate() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"({
+ "key": 123
+ })", "exists ($.key)", {"true"}},
+ {"\"string\"", "exists ($ * 2)", {"null"}},
+ {R"(["some string", 2])", "$[*] ? (exists (@ * 2))", {"2"}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestIsUnknownPredicate() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {"1", "(1 < true) is unknown", {"true"}},
+ {"1", "(true == true) is unknown", {"false"}},
+ {"1", "(true == false) is unknown", {"false"}},
+ {R"(["some string", -20])", "$[*] ? ((1 < @) is unknown)", {"\"some string\""}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+
+ void TestLikeRegexPredicate() {
+ const TVector<TMultiOutputTestCase> testCases = {
+ {R"(["string", "123", "456"])", R"($[*] like_regex "[0-9]+")", {"true"}},
+ {R"(["string", "another string"])", R"($[*] like_regex "[0-9]+")", {"false"}},
+
+ // Case insensitive flag
+ {R"("AbCd")", R"($ like_regex "abcd")", {"false"}},
+ {R"("AbCd")", R"($ like_regex "abcd" flag "i")", {"true"}},
+
+ {R"(["string", "123", "456"])", R"($[*] ? (@ like_regex "[0-9]+"))", {"\"123\"", "\"456\""}},
+ };
+
+ for (const auto& testCase : testCases) {
+ for (const auto mode : ALL_MODES) {
+ RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
+ }
+ }
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TJsonPathCommonTest);