#pragma once #include "resource.h" #include "compile_path.h" #include #include #include #include #include #include namespace NJson2Udf { using namespace NKikimr; using namespace NUdf; using namespace NYql; using namespace NDom; using namespace NJsonPath; template class TSqlQuery: public TBoxedValue { public: explicit TSqlQuery(TSourcePosition pos) : Pos_(pos) { } static TStringRef Name(); static bool DeclareSignature( const TStringRef& name, TType* userType, IFunctionTypeInfoBuilder& builder, bool typesOnly) { Y_UNUSED(userType); if (name != Name()) { return false; } auto jsonType = builder.Resource(JSON_NODE_RESOURCE_NAME); auto optionalJsonType = builder.Optional()->Item(jsonType).Build(); TType* inputType = nullptr; if constexpr (InputType == EDataSlot::JsonDocument) { inputType = builder.SimpleType(); } else { inputType = jsonType; } auto inputOptionalType = builder.Optional()->Item(inputType).Build(); auto jsonPathType = builder.Resource(JSONPATH_RESOURCE_NAME); auto dictType = builder.Dict()->Key().Value(jsonType).Build(); /* Arguments: 0. Resource? or JsonDocument?. Input json 1. Resource. Jsonpath to execute on json 2. Dict>. Variables to pass into jsonpath 3. Bool. True - throw on empty result, false otherwise 4. Resource?. Default value to return on empty result. Ignored if 2d argument is true 5. Bool. True - throw on error, false - otherwise 6. Resource?. Default value to return on error. Ignored if 4th argument is true */ // we can't mark TSqlQuery as strict due to runtime throw policy setting // TODO: optimizer can mark SqlQuery as strict if 3th/5th arguments are literal booleans builder.Args() ->Add(inputOptionalType) .Add(jsonPathType) .Add(dictType) .Add() .Add(optionalJsonType) .Add() .Add(optionalJsonType) .Done() .Returns(optionalJsonType); if (!typesOnly) { builder.Implementation(new TSqlQuery(builder.GetSourcePosition())); } return true; } private: TUnboxedValue Run( const IValueBuilder* valueBuilder, const TUnboxedValuePod* args) const final { Y_UNUSED(valueBuilder); try { if (!args[0].HasValue()) { return TUnboxedValuePod(); } TValue jsonDom; if constexpr (InputType == EDataSlot::JsonDocument) { jsonDom = TValue(NBinaryJson::TBinaryJsonReader::Make(args[0].AsStringRef())->GetRootCursor()); } else { jsonDom = TValue(args[0]); } auto* jsonPathResource = static_cast(args[1].AsBoxed().Get()); const auto& jsonPath = *jsonPathResource->Get(); const bool throwOnEmpty = args[3].Get(); const auto emptyDefault = args[4]; const bool throwOnError = args[5].Get(); const auto errorDefault = args[6]; const auto variables = DictToVariables(args[2]); auto result = ExecuteJsonPath(jsonPath, jsonDom, variables, valueBuilder); const auto handleCase = [](TStringBuf message, bool throws, const TUnboxedValuePod& caseDefault) { if (throws) { ythrow yexception() << message; } return caseDefault; }; if (result.IsError()) { return handleCase(TStringBuilder() << "Error executing jsonpath:" << Endl << result.GetError() << Endl, throwOnError, errorDefault); } auto& nodes = result.GetNodes(); const bool isSingleStruct = nodes.size() == 1 && (nodes[0].Is(EValueType::Array) || nodes[0].Is(EValueType::Object)); if (Mode == EJsonQueryWrap::Wrap || (Mode == EJsonQueryWrap::ConditionalWrap && !isSingleStruct)) { TVector converted; converted.reserve(nodes.size()); for (auto& node : nodes) { converted.push_back(node.ConvertToUnboxedValue(valueBuilder)); } return MakeList(converted.data(), converted.size(), valueBuilder); } if (nodes.empty()) { return handleCase("Empty result", throwOnEmpty, emptyDefault); } // No wrapping is applicable and result is not empty. Result must be a single object or array if (nodes.size() > 1) { return handleCase("Result consists of multiple items", throwOnError, errorDefault); } if (!nodes[0].Is(EValueType::Array) && !nodes[0].Is(EValueType::Object)) { return handleCase("Result is neither object nor array", throwOnError, errorDefault); } return nodes[0].ConvertToUnboxedValue(valueBuilder); } catch (const std::exception& e) { UdfTerminate((TStringBuilder() << Pos_ << " " << e.what()).data()); } } TSourcePosition Pos_; }; template <> TStringRef TSqlQuery::Name() { return "SqlQuery"; } template <> TStringRef TSqlQuery::Name() { return "SqlQueryWrap"; } template <> TStringRef TSqlQuery::Name() { return "SqlQueryConditionalWrap"; } template <> TStringRef TSqlQuery::Name() { return "JsonDocumentSqlQuery"; } template <> TStringRef TSqlQuery::Name() { return "JsonDocumentSqlQueryWrap"; } template <> TStringRef TSqlQuery::Name() { return "JsonDocumentSqlQueryConditionalWrap"; } }