12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064 |
- #include "executor.h"
- #include <yql/essentials/minikql/jsonpath/parser/parse_double.h>
- #include <yql/essentials/core/issue/protos/issue_id.pb.h>
- #include <yql/essentials/minikql/dom/node.h>
- #include <util/generic/scope.h>
- #include <util/generic/maybe.h>
- #include <util/system/compiler.h>
- #include <cmath>
- namespace NYql::NJsonPath {
- using namespace NJson;
- using namespace NUdf;
- using namespace NDom;
- namespace {
- bool IsObjectOrArray(const TValue& value) {
- return value.IsArray() || value.IsObject();
- }
- TIssue MakeError(TPosition pos, TIssueCode code, const TStringBuf message) {
- TIssue error(pos, message);
- error.SetCode(code, TSeverityIds::S_ERROR);
- return error;
- }
- TIssue MakeError(const TJsonPathItem& item, TIssueCode code, const TStringBuf message) {
- return MakeError(item.Pos, code, message);
- }
- }
- TResult::TResult(TJsonNodes&& nodes)
- : Result(std::move(nodes))
- {
- }
- TResult::TResult(const TJsonNodes& nodes)
- : Result(nodes)
- {
- }
- TResult::TResult(TIssue&& issue)
- : Result(std::move(issue))
- {
- }
- const TJsonNodes& TResult::GetNodes() const {
- return std::get<TJsonNodes>(Result);
- }
- TJsonNodes& TResult::GetNodes() {
- return std::get<TJsonNodes>(Result);
- }
- const TIssue& TResult::GetError() const {
- return std::get<TIssue>(Result);
- }
- bool TResult::IsError() const {
- return std::holds_alternative<TIssue>(Result);
- }
- TExecutor::TExecutor(
- const TJsonPathPtr path,
- const TJsonNodes& input,
- const TVariablesMap& variables,
- const IValueBuilder* valueBuilder)
- : Reader(path)
- , Input(input)
- , Variables(variables)
- , ValueBuilder(valueBuilder)
- {
- }
- bool TExecutor::IsZero(double value) {
- return -EPSILON <= value && value <= EPSILON;
- }
- bool TExecutor::IsLess(double a, double b) {
- return (b - a) > EPSILON;
- }
- bool TExecutor::IsGreater(double a, double b) {
- return (a - b) > EPSILON;
- }
- bool TExecutor::IsEqual(double a, double b) {
- return IsZero(a - b);
- }
- bool TExecutor::IsStrict() const {
- return Reader.GetMode() == EJsonPathMode::Strict;
- }
- bool TExecutor::IsLax() const {
- return Reader.GetMode() == EJsonPathMode::Lax;
- }
- TResult TExecutor::Execute() {
- return Execute(Reader.ReadFirst());
- }
- TResult TExecutor::Execute(const TJsonPathItem& item) {
- switch (item.Type) {
- case EJsonPathItemType::MemberAccess:
- return MemberAccess(item);
- case EJsonPathItemType::WildcardMemberAccess:
- return WildcardMemberAccess(item);
- case EJsonPathItemType::ContextObject:
- return ContextObject();
- case EJsonPathItemType::Variable:
- return Variable(item);
- case EJsonPathItemType::NumberLiteral:
- return NumberLiteral(item);
- case EJsonPathItemType::ArrayAccess:
- return ArrayAccess(item);
- case EJsonPathItemType::WildcardArrayAccess:
- return WildcardArrayAccess(item);
- case EJsonPathItemType::LastArrayIndex:
- return LastArrayIndex(item);
- case EJsonPathItemType::UnaryMinus:
- case EJsonPathItemType::UnaryPlus:
- return UnaryArithmeticOp(item);
- case EJsonPathItemType::BinaryAdd:
- case EJsonPathItemType::BinarySubstract:
- case EJsonPathItemType::BinaryMultiply:
- case EJsonPathItemType::BinaryDivide:
- case EJsonPathItemType::BinaryModulo:
- return BinaryArithmeticOp(item);
- case EJsonPathItemType::BinaryAnd:
- case EJsonPathItemType::BinaryOr:
- return BinaryLogicalOp(item);
- case EJsonPathItemType::UnaryNot:
- return UnaryLogicalOp(item);
- case EJsonPathItemType::BooleanLiteral:
- return BooleanLiteral(item);
- case EJsonPathItemType::NullLiteral:
- return NullLiteral();
- case EJsonPathItemType::StringLiteral:
- return StringLiteral(item);
- case EJsonPathItemType::FilterObject:
- return FilterObject(item);
- case EJsonPathItemType::FilterPredicate:
- return FilterPredicate(item);
- case EJsonPathItemType::BinaryLess:
- case EJsonPathItemType::BinaryLessEqual:
- case EJsonPathItemType::BinaryGreater:
- case EJsonPathItemType::BinaryGreaterEqual:
- case EJsonPathItemType::BinaryEqual:
- case EJsonPathItemType::BinaryNotEqual:
- return CompareOp(item);
- case EJsonPathItemType::AbsMethod:
- case EJsonPathItemType::FloorMethod:
- case EJsonPathItemType::CeilingMethod:
- return NumericMethod(item);
- case EJsonPathItemType::DoubleMethod:
- return DoubleMethod(item);
- case EJsonPathItemType::TypeMethod:
- return TypeMethod(item);
- case EJsonPathItemType::SizeMethod:
- return SizeMethod(item);
- case EJsonPathItemType::KeyValueMethod:
- return KeyValueMethod(item);
- case EJsonPathItemType::StartsWithPredicate:
- return StartsWithPredicate(item);
- case EJsonPathItemType::IsUnknownPredicate:
- return IsUnknownPredicate(item);
- case EJsonPathItemType::ExistsPredicate:
- return ExistsPredicate(item);
- case EJsonPathItemType::LikeRegexPredicate:
- return LikeRegexPredicate(item);
- }
- }
- TResult TExecutor::ContextObject() {
- return Input;
- }
- TResult TExecutor::Variable(const TJsonPathItem& item) {
- const auto it = Variables.find(item.GetString());
- if (it == Variables.end()) {
- return MakeError(item, TIssuesIds::JSONPATH_UNDEFINED_VARIABLE, TStringBuilder() << "Undefined variable '" << item.GetString() << "'");
- }
- return TJsonNodes({it->second});
- }
- TResult TExecutor::LastArrayIndex(const TJsonPathItem& item) {
- if (ArraySubscriptSource.empty()) {
- return MakeError(item, TIssuesIds::JSONPATH_LAST_OUTSIDE_OF_ARRAY_SUBSCRIPT, "'last' is only allowed inside array subscripts");
- }
- const auto& array = ArraySubscriptSource.top();
- const i64 arraySize = array.GetSize();
- // NOTE: For empty arrays `last` equals `-1`. This is intended, PostgreSQL 12 has the same behaviour
- return TJsonNodes({TValue(MakeDouble(static_cast<double>(arraySize - 1)))});
- }
- TResult TExecutor::NumberLiteral(const TJsonPathItem& item) {
- return TJsonNodes({TValue(MakeDouble(item.GetNumber()))});
- }
- TResult TExecutor::MemberAccess(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : OptionalUnwrapArrays(input.GetNodes())) {
- if (!node.IsObject()) {
- if (IsStrict()) {
- return MakeError(item, TIssuesIds::JSONPATH_EXPECTED_OBJECT, "Expected object");
- } else {
- continue;
- }
- }
- if (const auto payload = node.Lookup(item.GetString())) {
- result.push_back(*payload);
- continue;
- }
- if (IsStrict()) {
- return MakeError(item, TIssuesIds::JSONPATH_MEMBER_NOT_FOUND, "Member not found");
- }
- }
- return std::move(result);
- }
- TResult TExecutor::WildcardMemberAccess(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : OptionalUnwrapArrays(input.GetNodes())) {
- if (!node.IsObject()) {
- if (IsStrict()) {
- return MakeError(item, TIssuesIds::JSONPATH_EXPECTED_OBJECT, "Expected object");
- } else {
- continue;
- }
- }
- TValue key;
- TValue value;
- auto it = node.GetObjectIterator();
- while (it.Next(key, value)) {
- result.push_back(value);
- }
- }
- return std::move(result);
- }
- TMaybe<TIssue> TExecutor::EnsureSingleSubscript(TPosition pos, const TJsonNodes& index, i64& result) {
- if (index.size() != 1) {
- return MakeError(pos, TIssuesIds::JSONPATH_INVALID_ARRAY_INDEX, "Expected single number item for array index");
- }
- const auto& indexValue = index[0];
- if (!indexValue.IsNumber()) {
- return MakeError(pos, TIssuesIds::JSONPATH_INVALID_ARRAY_INDEX, "Array index must be number");
- }
- result = static_cast<i64>(std::floor(indexValue.GetNumber()));
- return Nothing();
- }
- TMaybe<TIssue> TExecutor::EnsureArraySubscripts(const TJsonPathItem& item, TVector<TArraySubscript>& result) {
- for (const auto& subscript : item.GetSubscripts()) {
- const auto& fromItem = Reader.ReadFromSubscript(subscript);
- const auto fromResult = Execute(fromItem);
- if (fromResult.IsError()) {
- return fromResult.GetError();
- }
- i64 fromIndex = 0;
- TMaybe<TIssue> error = EnsureSingleSubscript(fromItem.Pos, fromResult.GetNodes(), fromIndex);
- if (error) {
- return error;
- }
- if (!subscript.IsRange()) {
- result.emplace_back(fromIndex, fromItem.Pos);
- continue;
- }
- const auto& toItem = Reader.ReadToSubscript(subscript);
- const auto toResult = Execute(toItem);
- if (toResult.IsError()) {
- return toResult.GetError();
- }
- i64 toIndex = 0;
- error = EnsureSingleSubscript(toItem.Pos, toResult.GetNodes(), toIndex);
- if (error) {
- return error;
- }
- result.emplace_back(fromIndex, fromItem.Pos, toIndex, toItem.Pos);
- }
- return Nothing();
- }
- TResult TExecutor::ArrayAccess(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : OptionalArrayWrapNodes(input.GetNodes())) {
- if (!node.IsArray()) {
- return MakeError(item, TIssuesIds::JSONPATH_EXPECTED_ARRAY, "Expected array");
- }
- ArraySubscriptSource.push(node);
- Y_DEFER {
- ArraySubscriptSource.pop();
- };
- // Check for "hard" errors in array subscripts. These are forbidden even in lax mode
- // NOTE: We intentionally execute subscripts expressions for each array in the input
- // because they can contain `last` keyword which value is different for each array
- TVector<TArraySubscript> subscripts;
- TMaybe<TIssue> error = EnsureArraySubscripts(item, subscripts);
- if (error) {
- return std::move(*error);
- }
- const ui64 arraySize = node.GetSize();
- for (const auto& idx : subscripts) {
- // Check bounds for first subscript
- if (idx.GetFrom() < 0 || idx.GetFrom() >= static_cast<i64>(arraySize)) {
- if (IsStrict()) {
- return MakeError(idx.GetFromPos(), TIssuesIds::JSONPATH_ARRAY_INDEX_OUT_OF_BOUNDS, "Array index out of bounds");
- } else {
- continue;
- }
- }
- // If there is no second subcripts, just return corresponding array element
- if (!idx.IsRange()) {
- result.push_back(node.GetElement(idx.GetFrom()));
- continue;
- }
- // Check bounds for second subscript
- if (idx.GetTo() < 0 || idx.GetTo() >= static_cast<i64>(arraySize)) {
- if (IsStrict()) {
- return MakeError(idx.GetToPos(), TIssuesIds::JSONPATH_ARRAY_INDEX_OUT_OF_BOUNDS, "Array index out of bounds");
- } else {
- continue;
- }
- }
- // In strict mode invalid ranges are forbidden
- if (idx.GetFrom() > idx.GetTo() && IsStrict()) {
- return MakeError(idx.GetFromPos(), TIssuesIds::JSONPATH_INVALID_ARRAY_INDEX_RANGE, "Range lower bound is greater than upper bound");
- }
- for (i64 i = idx.GetFrom(); i <= idx.GetTo(); i++) {
- result.push_back(node.GetElement(i));
- }
- }
- }
- return std::move(result);
- }
- TResult TExecutor::WildcardArrayAccess(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : OptionalArrayWrapNodes(input.GetNodes())) {
- if (!node.IsArray()) {
- return MakeError(item, TIssuesIds::JSONPATH_EXPECTED_ARRAY, "Expected array");
- }
- auto it = node.GetArrayIterator();
- TValue value;
- while (it.Next(value)) {
- result.push_back(value);
- }
- }
- return std::move(result);
- }
- TResult TExecutor::UnaryArithmeticOp(const TJsonPathItem& item) {
- const auto& operandItem = Reader.ReadInput(item);
- const auto operandsResult = Execute(operandItem);
- if (operandsResult.IsError()) {
- return operandsResult;
- }
- const auto& operands = operandsResult.GetNodes();
- TJsonNodes result;
- result.reserve(operands.size());
- for (const auto& operand : operands) {
- if (!operand.IsNumber()) {
- return MakeError(
- operandItem, TIssuesIds::JSONPATH_INVALID_UNARY_OPERATION_ARGUMENT_TYPE,
- TStringBuilder() << "Unsupported type for unary operations"
- );
- }
- if (item.Type == EJsonPathItemType::UnaryPlus) {
- result.push_back(operand);
- continue;
- }
- const auto value = operand.GetNumber();
- result.push_back(TValue(MakeDouble(-value)));
- }
- return std::move(result);
- }
- TMaybe<TIssue> TExecutor::EnsureBinaryArithmeticOpArgument(TPosition pos, const TJsonNodes& nodes, double& result) {
- if (nodes.size() != 1) {
- return MakeError(pos, TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT, "Expected exactly 1 item as an operand for binary operation");
- }
- const auto& value = nodes[0];
- if (!value.IsNumber()) {
- return MakeError(
- pos, TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT_TYPE,
- TStringBuilder() << "Unsupported type for binary operations"
- );
- }
- result = value.GetNumber();
- return Nothing();
- }
- TResult TExecutor::BinaryArithmeticOp(const TJsonPathItem& item) {
- const auto& leftItem = Reader.ReadLeftOperand(item);
- const auto leftResult = Execute(leftItem);
- if (leftResult.IsError()) {
- return leftResult;
- }
- double left = 0;
- TMaybe<TIssue> error = EnsureBinaryArithmeticOpArgument(leftItem.Pos, leftResult.GetNodes(), left);
- if (error) {
- return std::move(*error);
- }
- const auto& rightItem = Reader.ReadRightOperand(item);
- const auto rightResult = Execute(rightItem);
- if (rightResult.IsError()) {
- return rightResult;
- }
- double right = 0;
- error = EnsureBinaryArithmeticOpArgument(rightItem.Pos, rightResult.GetNodes(), right);
- if (error) {
- return std::move(*error);
- }
- double result = 0;
- switch (item.Type) {
- case EJsonPathItemType::BinaryAdd:
- result = left + right;
- break;
- case EJsonPathItemType::BinarySubstract:
- result = left - right;
- break;
- case EJsonPathItemType::BinaryMultiply:
- result = left * right;
- break;
- case EJsonPathItemType::BinaryDivide:
- if (IsZero(right)) {
- return MakeError(rightItem, TIssuesIds::JSONPATH_DIVISION_BY_ZERO, "Division by zero");
- }
- result = left / right;
- break;
- case EJsonPathItemType::BinaryModulo:
- if (IsZero(right)) {
- return MakeError(rightItem, TIssuesIds::JSONPATH_DIVISION_BY_ZERO, "Division by zero");
- }
- result = std::fmod(left, right);
- break;
- default:
- YQL_ENSURE(false, "Expected binary arithmetic operation");
- }
- if (Y_UNLIKELY(std::isinf(result))) {
- return MakeError(item, TIssuesIds::JSONPATH_BINARY_OPERATION_RESULT_INFINITY, "Binary operation result is infinity");
- }
- return TJsonNodes({TValue(MakeDouble(result))});
- }
- TMaybe<TIssue> TExecutor::EnsureLogicalOpArgument(TPosition pos, const TJsonNodes& nodes, TMaybe<bool>& result) {
- if (nodes.size() != 1) {
- return MakeError(pos, TIssuesIds::JSONPATH_INVALID_LOGICAL_OPERATION_ARGUMENT, "Expected exactly 1 item as an operand for logical operation");
- }
- const auto& value = nodes[0];
- if (value.IsNull()) {
- result = Nothing();
- } else if (value.IsBool()) {
- result = value.GetBool();
- } else {
- return MakeError(pos, TIssuesIds::JSONPATH_INVALID_LOGICAL_OPERATION_ARGUMENT, "Unsupported type for logical operation");
- }
- return Nothing();
- }
- TResult TExecutor::BinaryLogicalOp(const TJsonPathItem& item) {
- const auto& leftItem = Reader.ReadLeftOperand(item);
- const auto leftResult = Execute(leftItem);
- if (leftResult.IsError()) {
- return leftResult;
- }
- TMaybe<bool> left;
- TMaybe<TIssue> error = EnsureLogicalOpArgument(leftItem.Pos, leftResult.GetNodes(), left);
- if (error) {
- return std::move(*error);
- }
- const auto& rightItem = Reader.ReadRightOperand(item);
- const auto rightResult = Execute(rightItem);
- if (rightResult.IsError()) {
- return rightResult;
- }
- TMaybe<bool> right;
- error = EnsureLogicalOpArgument(rightItem.Pos, rightResult.GetNodes(), right);
- if (error) {
- return std::move(*error);
- }
- switch (item.Type) {
- case EJsonPathItemType::BinaryAnd: {
- /*
- AND truth table (taken from SQL JSON standard)
- | && | true | false | null |
- | ----- | ----- | ----- | ----- |
- | true | true | false | null |
- | false | false | false | false |
- | null | null | false | null |
- */
- if (left.Defined() && right.Defined()) {
- return TJsonNodes({TValue(MakeBool(*left && *right))});
- }
- const bool falseVsNull = !left.GetOrElse(true) && !right.Defined();
- const bool nullVsFalse = !right.GetOrElse(true) && !left.Defined();
- if (falseVsNull || nullVsFalse) {
- return TJsonNodes({TValue(MakeBool(false))});
- }
- return TJsonNodes({TValue(MakeEntity())});
- }
- case EJsonPathItemType::BinaryOr: {
- /*
- OR truth table (taken from SQL JSON standard)
- | || | true | false | null |
- | ----- | ----- | ----- | ----- |
- | true | true | true | true |
- | false | true | false | null |
- | null | true | null | null |
- */
- if (left.Defined() && right.Defined()) {
- return TJsonNodes({TValue(MakeBool(*left || *right))});
- }
- const bool trueVsNull = left.GetOrElse(false) && !right.Defined();
- const bool nullVsTrue = right.GetOrElse(false) && !left.Defined();
- if (trueVsNull || nullVsTrue) {
- return TJsonNodes({TValue(MakeBool(true))});
- }
- return TJsonNodes({TValue(MakeEntity())});
- }
- default:
- YQL_ENSURE(false, "Expected binary logical operation");
- }
- }
- TResult TExecutor::UnaryLogicalOp(const TJsonPathItem& item) {
- /*
- NOT truth table (taken from SQL JSON standard)
- | x | !x |
- | ----- | ----- |
- | true | false |
- | false | true |
- | null | null |
- */
- const auto& operandItem = Reader.ReadInput(item);
- const auto operandResult = Execute(operandItem);
- if (operandResult.IsError()) {
- return operandResult;
- }
- TMaybe<bool> operand;
- TMaybe<TIssue> error = EnsureLogicalOpArgument(operandItem.Pos, operandResult.GetNodes(), operand);
- if (error) {
- return std::move(*error);
- }
- if (!operand.Defined()) {
- return TJsonNodes({TValue(MakeEntity())});
- }
- return TJsonNodes({TValue(MakeBool(!(*operand)))});
- }
- TResult TExecutor::BooleanLiteral(const TJsonPathItem& item) {
- return TJsonNodes({TValue(MakeBool(item.GetBoolean()))});
- }
- TResult TExecutor::NullLiteral() {
- return TJsonNodes({TValue(MakeEntity())});
- }
- TResult TExecutor::StringLiteral(const TJsonPathItem& item) {
- return TJsonNodes({TValue(MakeString(item.GetString(), ValueBuilder))});
- }
- TMaybe<bool> TExecutor::CompareValues(const TValue& left, const TValue& right, EJsonPathItemType operation) {
- if (IsObjectOrArray(left) || IsObjectOrArray(right)) {
- // Comparisons of objects and arrays are prohibited
- return Nothing();
- }
- if (left.IsNull() && right.IsNull()) {
- // null == null is true, but all other comparisons are false
- return operation == EJsonPathItemType::BinaryEqual;
- }
- if (left.IsNull() || right.IsNull()) {
- // All operations between null and non-null are false
- return false;
- }
- auto doCompare = [&operation](const auto& left, const auto& right) {
- switch (operation) {
- case EJsonPathItemType::BinaryEqual:
- return left == right;
- case EJsonPathItemType::BinaryNotEqual:
- return left != right;
- case EJsonPathItemType::BinaryLess:
- return left < right;
- case EJsonPathItemType::BinaryLessEqual:
- return left <= right;
- case EJsonPathItemType::BinaryGreater:
- return left > right;
- case EJsonPathItemType::BinaryGreaterEqual:
- return left >= right;
- default:
- YQL_ENSURE(false, "Expected compare operation");
- }
- };
- if (left.IsBool() && right.IsBool()) {
- return doCompare(left.GetBool(), right.GetBool());
- } else if (left.IsString() && right.IsString()) {
- // NOTE: Strings are compared as byte arrays.
- // YQL does the same thing for UTF-8 strings and according to SQL/JSON
- // standard JsonPath must use the same semantics.
- //
- // However this is not correct in logical meaning. Let us consider strings:
- // - U+00e9 (LATIN SMALL LETTER E WITH ACUTE), 'é'
- // - U+0065 (LATIN SMALL LETTER E) U+0301 (COMBINING ACUTE ACCENT), `é`
- // Even though these two strings are different byte sequences, they are identical
- // from UTF-8 perspective.
- return doCompare(left.GetString(), right.GetString());
- }
- if (!left.IsNumber() || !right.IsNumber()) {
- return Nothing();
- }
- const auto leftNumber = left.GetNumber();
- const auto rightNumber = right.GetNumber();
- switch (operation) {
- case EJsonPathItemType::BinaryEqual:
- return IsEqual(leftNumber, rightNumber);
- case EJsonPathItemType::BinaryNotEqual:
- return !IsEqual(leftNumber, rightNumber);
- case EJsonPathItemType::BinaryLess:
- return IsLess(leftNumber, rightNumber);
- case EJsonPathItemType::BinaryLessEqual:
- return !IsGreater(leftNumber, rightNumber);
- case EJsonPathItemType::BinaryGreater:
- return IsGreater(leftNumber, rightNumber);
- case EJsonPathItemType::BinaryGreaterEqual:
- return !IsLess(leftNumber, rightNumber);
- default:
- YQL_ENSURE(false, "Expected compare operation");
- }
- }
- TResult TExecutor::CompareOp(const TJsonPathItem& item) {
- const auto& leftItem = Reader.ReadLeftOperand(item);
- const auto leftResult = Execute(leftItem);
- if (leftResult.IsError()) {
- return TJsonNodes({TValue(MakeEntity())});
- }
- const auto& rightItem = Reader.ReadRightOperand(item);
- const auto rightResult = Execute(rightItem);
- if (rightResult.IsError()) {
- return TJsonNodes({TValue(MakeEntity())});
- }
- const auto leftNodes = OptionalUnwrapArrays(leftResult.GetNodes());
- const auto rightNodes = OptionalUnwrapArrays(rightResult.GetNodes());
- bool error = false;
- bool found = false;
- for (const auto& left : leftNodes) {
- for (const auto& right : rightNodes) {
- const auto result = CompareValues(left, right, item.Type);
- if (!result.Defined()) {
- error = true;
- } else {
- found |= *result;
- }
- if (IsLax() && (error || found)) {
- break;
- }
- }
- if (IsLax() && (error || found)) {
- break;
- }
- }
- if (error) {
- return TJsonNodes({TValue(MakeEntity())});
- }
- return TJsonNodes({TValue(MakeBool(found))});
- }
- TResult TExecutor::FilterObject(const TJsonPathItem& item) {
- if (CurrentFilterObject.empty()) {
- return MakeError(item, TIssuesIds::JSONPATH_FILTER_OBJECT_OUTSIDE_OF_FILTER, "'@' is only allowed inside filters");
- }
- return TJsonNodes({CurrentFilterObject.top()});
- }
- TResult TExecutor::FilterPredicate(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- const auto& predicateItem = Reader.ReadFilterPredicate(item);
- TJsonNodes result;
- for (const auto& node : OptionalUnwrapArrays(input.GetNodes())) {
- CurrentFilterObject.push(node);
- Y_DEFER {
- CurrentFilterObject.pop();
- };
- const auto predicateResult = Execute(predicateItem);
- if (predicateResult.IsError()) {
- continue;
- }
- const auto& predicateNodes = predicateResult.GetNodes();
- if (predicateNodes.size() != 1) {
- continue;
- }
- const auto& value = predicateNodes[0];
- if (value.IsBool() && value.GetBool()) {
- result.push_back(node);
- continue;
- }
- }
- return std::move(result);
- }
- TResult TExecutor::NumericMethod(const TJsonPathItem& item) {
- const auto& input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : OptionalUnwrapArrays(input.GetNodes())) {
- if (!node.IsNumber()) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_NUMERIC_METHOD_ARGUMENT, "Unsupported type for numeric method");
- }
- double applied = node.GetNumber();
- switch (item.Type) {
- case EJsonPathItemType::AbsMethod:
- applied = std::fabs(applied);
- break;
- case EJsonPathItemType::FloorMethod:
- applied = std::floor(applied);
- break;
- case EJsonPathItemType::CeilingMethod:
- applied = std::ceil(applied);
- break;
- default:
- YQL_ENSURE(false, "Expected numeric method");
- }
- result.push_back(TValue(MakeDouble(applied)));
- }
- return std::move(result);
- }
- TResult TExecutor::DoubleMethod(const TJsonPathItem& item) {
- const auto& input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : OptionalUnwrapArrays(input.GetNodes())) {
- if (!node.IsString()) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_DOUBLE_METHOD_ARGUMENT, "Unsupported type for double() method");
- }
- const double parsed = ParseDouble(node.GetString());
- if (std::isnan(parsed)) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_NUMBER_STRING, "Error parsing number from string");
- }
- if (std::isinf(parsed)) {
- return MakeError(item, TIssuesIds::JSONPATH_INFINITE_NUMBER_STRING, "Parsed number is infinity");
- }
- result.push_back(TValue(MakeDouble(parsed)));
- }
- return std::move(result);
- }
- TResult TExecutor::TypeMethod(const TJsonPathItem& item) {
- const auto& input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : input.GetNodes()) {
- TStringBuf type;
- switch (node.GetType()) {
- case EValueType::Null:
- type = "null";
- break;
- case EValueType::Bool:
- type = "boolean";
- break;
- case EValueType::Number:
- type = "number";
- break;
- case EValueType::String:
- type = "string";
- break;
- case EValueType::Array:
- type = "array";
- break;
- case EValueType::Object:
- type = "object";
- break;
- }
- result.push_back(TValue(MakeString(type, ValueBuilder)));
- }
- return std::move(result);
- }
- TResult TExecutor::SizeMethod(const TJsonPathItem& item) {
- const auto& input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : input.GetNodes()) {
- ui64 size = 1;
- if (node.IsArray()) {
- size = node.GetSize();
- }
- result.push_back(TValue(MakeDouble(static_cast<double>(size))));
- }
- return std::move(result);
- }
- TResult TExecutor::KeyValueMethod(const TJsonPathItem& item) {
- const auto& input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- TJsonNodes result;
- TPair row[2];
- TPair& nameEntry = row[0];
- TPair& valueEntry = row[1];
- for (const auto& node : OptionalUnwrapArrays(input.GetNodes())) {
- if (!node.IsObject()) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_KEYVALUE_METHOD_ARGUMENT, "Unsupported type for keyvalue() method");
- }
- TValue key;
- TValue value;
- auto it = node.GetObjectIterator();
- while (it.Next(key, value)) {
- nameEntry.first = MakeString("name", ValueBuilder);
- nameEntry.second = key.ConvertToUnboxedValue(ValueBuilder);
- valueEntry.first = MakeString("value", ValueBuilder);
- valueEntry.second = value.ConvertToUnboxedValue(ValueBuilder);
- result.push_back(TValue(MakeDict(row, 2)));
- }
- }
- return std::move(result);
- }
- TResult TExecutor::StartsWithPredicate(const TJsonPathItem& item) {
- const auto& input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- const auto& inputNodes = input.GetNodes();
- if (inputNodes.size() != 1) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_STARTS_WITH_ARGUMENT, "Expected exactly 1 item as input argument for starts with predicate");
- }
- const auto& inputString = inputNodes[0];
- if (!inputString.IsString()) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_STARTS_WITH_ARGUMENT, "Type of input argument for starts with predicate must be string");
- }
- const auto prefix = Execute(Reader.ReadPrefix(item));
- if (prefix.IsError()) {
- return prefix;
- }
- bool error = false;
- bool found = false;
- for (const auto& node : prefix.GetNodes()) {
- if (node.IsString()) {
- found |= inputString.GetString().StartsWith(node.GetString());
- } else {
- error = true;
- }
- if (IsLax() && (found || error)) {
- break;
- }
- }
- if (error) {
- return TJsonNodes({TValue(MakeEntity())});
- }
- return TJsonNodes({TValue(MakeBool(found))});
- }
- TResult TExecutor::IsUnknownPredicate(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- const auto& nodes = input.GetNodes();
- if (nodes.size() != 1) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_IS_UNKNOWN_ARGUMENT, "Expected exactly 1 item as an argument for is unknown predicate");
- }
- const auto& node = nodes[0];
- if (node.IsNull()) {
- return TJsonNodes({TValue(MakeBool(true))});
- }
- if (!node.IsBool()) {
- return MakeError(item, TIssuesIds::JSONPATH_INVALID_IS_UNKNOWN_ARGUMENT, "is unknown predicate supports only bool and null types for its argument");
- }
- return TJsonNodes({TValue(MakeBool(false))});
- }
- TResult TExecutor::ExistsPredicate(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return TJsonNodes({TValue(MakeEntity())});
- }
- const auto& nodes = input.GetNodes();
- return TJsonNodes({TValue(MakeBool(!nodes.empty()))});
- }
- TResult TExecutor::LikeRegexPredicate(const TJsonPathItem& item) {
- const auto input = Execute(Reader.ReadInput(item));
- if (input.IsError()) {
- return input;
- }
- const auto& regex = item.GetRegex();
- bool error = false;
- bool found = false;
- for (const auto& node : OptionalUnwrapArrays(input.GetNodes())) {
- if (node.IsString()) {
- found |= regex->Matches(node.GetString());
- } else {
- error = true;
- }
- if (IsLax() && (found || error)) {
- break;
- }
- }
- if (error) {
- return TJsonNodes({TValue(MakeEntity())});
- }
- return TJsonNodes({TValue(MakeBool(found))});
- }
- TJsonNodes TExecutor::OptionalUnwrapArrays(const TJsonNodes& input) {
- if (IsStrict()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : input) {
- if (!node.IsArray()) {
- result.push_back(node);
- continue;
- }
- auto it = node.GetArrayIterator();
- TValue value;
- while (it.Next(value)) {
- result.push_back(value);
- }
- }
- return result;
- }
- TJsonNodes TExecutor::OptionalArrayWrapNodes(const TJsonNodes& input) {
- if (IsStrict()) {
- return input;
- }
- TJsonNodes result;
- for (const auto& node : input) {
- if (node.IsArray()) {
- result.push_back(node);
- continue;
- }
- TUnboxedValue nodeCopy(node.ConvertToUnboxedValue(ValueBuilder));
- result.push_back(TValue(MakeList(&nodeCopy, 1, ValueBuilder)));
- }
- return result;
- }
- }
|