diff --git a/.github/config/muted_ya.txt b/.github/config/muted_ya.txt index 07eb091e9895..f00db93a44a5 100644 --- a/.github/config/muted_ya.txt +++ b/.github/config/muted_ya.txt @@ -51,8 +51,6 @@ ydb/core/viewer/ut Viewer.TabletMergingPacked ydb/library/actors/http/ut HttpProxy.TooLongHeader ydb/library/actors/http/ut sole* ydb/library/yql/providers/generic/connector/tests/datasource/ydb* * -ydb/library/yql/tests/sql/yt_native_file/part12/test.py test[pg_catalog-pg_description_pg_syntax-default.txt-Results] -ydb/library/yql/tests/sql/yt_native_file/part7/test.py test[pg_catalog-pg_proc-default.txt-Results] ydb/public/sdk/cpp/client/ydb_persqueue_core/ut RetryPolicy.TWriteSession_TestBrokenPolicy ydb/public/sdk/cpp/client/ydb_persqueue_core/ut [*/*] ydb/public/sdk/cpp/client/ydb_persqueue_public/ut RetryPolicy.TWriteSession_TestBrokenPolicy diff --git a/ydb/library/yql/parser/pg_catalog/catalog.cpp b/ydb/library/yql/parser/pg_catalog/catalog.cpp index 090586e70cd0..14893cc2e572 100644 --- a/ydb/library/yql/parser/pg_catalog/catalog.cpp +++ b/ydb/library/yql/parser/pg_catalog/catalog.cpp @@ -1868,7 +1868,10 @@ struct TCatalog : public IExtensionDDLBuilder { Conversions = ParseConversions(conversionData, ProcByName); Languages = ParseLanguages(languagesData); - if (auto exportDir = GetEnv("YQL_EXPORT_PG_FUNCTIONS_DIR")) { + if (GetEnv("YQL_ALLOW_ALL_PG_FUNCTIONS")) { + AllowAllFunctions = true; + } else if (auto exportDir = GetEnv("YQL_EXPORT_PG_FUNCTIONS_DIR")) { + AllowAllFunctions = true; ExportFile.ConstructInPlace(MakeTempName(exportDir.c_str(), "procs"), CreateAlways | RdWr); for (const auto& a : Aggregations) { const auto& desc = a.second; @@ -1943,6 +1946,67 @@ struct TCatalog : public IExtensionDDLBuilder { ProcByName[newDesc.Name].push_back(newDesc.ProcId); } + void PrepareType(ui32 extensionIndex, const TString& name) final { + Y_ENSURE(extensionIndex); + Y_ENSURE(!TypeByName.contains(name)); + TTypeDesc newDesc; + newDesc.Name = name; + newDesc.TypeId = 16000 + Types.size(); + newDesc.ExtensionIndex = extensionIndex; + newDesc.ArrayTypeId = newDesc.TypeId + 1; + newDesc.Category = 'U'; + Types[newDesc.TypeId] = newDesc; + TypeByName[newDesc.Name] = newDesc.TypeId; + TTypeDesc newArrayDesc = newDesc; + newArrayDesc.TypeId += 1; + newArrayDesc.Name = "_" + newArrayDesc.Name; + newArrayDesc.ElementTypeId = newDesc.TypeId; + newArrayDesc.ArrayTypeId = newArrayDesc.TypeId; + newArrayDesc.PassByValue = false; + newArrayDesc.TypeLen = -1; + newArrayDesc.SendFuncId = (*ProcByName.FindPtr("array_send"))[0]; + newArrayDesc.ReceiveFuncId = (*ProcByName.FindPtr("array_recv"))[0]; + newArrayDesc.InFuncId = (*ProcByName.FindPtr("array_in"))[0]; + newArrayDesc.OutFuncId = (*ProcByName.FindPtr("array_out"))[0]; + newArrayDesc.Category = 'A'; + Types[newArrayDesc.TypeId] = newArrayDesc; + TypeByName[newArrayDesc.Name] = newArrayDesc.TypeId; + } + + void UpdateType(const TTypeDesc& desc) final { + auto byIdPtr = Types.FindPtr(desc.TypeId); + Y_ENSURE(byIdPtr); + Y_ENSURE(byIdPtr->Name == desc.Name); + Y_ENSURE(byIdPtr->ArrayTypeId == desc.ArrayTypeId); + Y_ENSURE(byIdPtr->TypeId == desc.TypeId); + Y_ENSURE(byIdPtr->ExtensionIndex == desc.ExtensionIndex); + if (desc.InFuncId) { + AllowedProcs.insert(Procs.FindPtr(desc.InFuncId)->Name); + } + + if (desc.OutFuncId) { + AllowedProcs.insert(Procs.FindPtr(desc.OutFuncId)->Name); + } + + if (desc.SendFuncId) { + AllowedProcs.insert(Procs.FindPtr(desc.SendFuncId)->Name); + } + + if (desc.ReceiveFuncId) { + AllowedProcs.insert(Procs.FindPtr(desc.ReceiveFuncId)->Name); + } + + if (desc.TypeModInFuncId) { + AllowedProcs.insert(Procs.FindPtr(desc.TypeModInFuncId)->Name); + } + + if (desc.TypeModOutFuncId) { + AllowedProcs.insert(Procs.FindPtr(desc.TypeModOutFuncId)->Name); + } + + *byIdPtr = desc; + } + static const TCatalog& Instance() { return *Singleton(); } @@ -1981,6 +2045,7 @@ struct TCatalog : public IExtensionDDLBuilder { THashMap> StaticColumns; mutable TMaybe ExportFile; + bool AllowAllFunctions = false; TMutex ExportGuard; THashSet AllowedProcs; @@ -1998,7 +2063,7 @@ const TProcDesc& LookupProc(ui32 procId, const TVector& argTypeIds) { throw yexception() << "No such proc: " << procId; } - if (!catalog.ExportFile && !catalog.AllowedProcs.contains(procPtr->Name)) { + if (!catalog.AllowAllFunctions && !catalog.AllowedProcs.contains(procPtr->Name)) { throw yexception() << "No access to proc: " << procPtr->Name; } @@ -2022,7 +2087,7 @@ const TProcDesc& LookupProc(const TString& name, const TVector& argTypeIds for (const auto& id : *procIdPtr) { const auto& d = catalog.Procs.FindPtr(id); Y_ENSURE(d); - if (!catalog.ExportFile && !catalog.AllowedProcs.contains(d->Name)) { + if (!catalog.AllowAllFunctions && !catalog.AllowedProcs.contains(d->Name)) { throw yexception() << "No access to proc: " << d->Name; } @@ -2045,7 +2110,7 @@ const TProcDesc& LookupProc(ui32 procId) { throw yexception() << "No such proc: " << procId; } - if (!catalog.ExportFile && !catalog.AllowedProcs.contains(procPtr->Name)) { + if (!catalog.AllowAllFunctions && !catalog.AllowedProcs.contains(procPtr->Name)) { throw yexception() << "No access to proc: " << procPtr->Name; } @@ -2056,7 +2121,7 @@ const TProcDesc& LookupProc(ui32 procId) { void EnumProc(std::function f) { const auto& catalog = TCatalog::Instance(); for (const auto& x : catalog.Procs) { - if (catalog.ExportFile || catalog.AllowedProcs.contains(x.second.Name)) { + if (catalog.AllowAllFunctions || catalog.AllowedProcs.contains(x.second.Name)) { f(x.first, x.second); } } @@ -2673,7 +2738,7 @@ std::variant LookupProcWithCasts(const TStri const auto& d = catalog.Procs.FindPtr(id); Y_ENSURE(d); - if (!catalog.ExportFile && !catalog.AllowedProcs.contains(d->Name)) { + if (!catalog.AllowAllFunctions && !catalog.AllowedProcs.contains(d->Name)) { throw yexception() << "No access to proc: " << d->Name; } @@ -3257,9 +3322,9 @@ const TTableInfo& LookupStaticTable(const TTableInfoKey& tableKey) { return *tablePtr; } -bool IsExportFunctionsEnabled() { +bool AreAllFunctionsAllowed() { const auto& catalog = TCatalog::Instance(); - return catalog.ExportFile.Defined(); + return catalog.AllowAllFunctions; } void RegisterExtensions(const TVector& extensions, bool typesOnly, @@ -3267,6 +3332,8 @@ void RegisterExtensions(const TVector& extensions, bool typesOnl auto& catalog = TCatalog::MutableInstance(); with_lock (catalog.ExtensionsGuard) { Y_ENSURE(!catalog.ExtensionsInit); + auto savedAllowAllFunctions = catalog.AllowAllFunctions; + catalog.AllowAllFunctions = true; for (ui32 i = 0; i < extensions.size(); ++i) { auto e = extensions[i]; e.TypesOnly = e.TypesOnly && typesOnly; @@ -3284,12 +3351,13 @@ void RegisterExtensions(const TVector& extensions, bool typesOnl catalog.Extensions.push_back(e); TString sql = TFileInput(e.DDLPath).ReadAll();; - parser.Parse(sql, catalog); + parser.Parse(i + 1, sql, catalog); if (loader && !e.TypesOnly) { loader->Load(i + 1, e.Name, e.LibraryPath); } } + catalog.AllowAllFunctions = savedAllowAllFunctions; catalog.ExtensionsInit = true; } } diff --git a/ydb/library/yql/parser/pg_catalog/catalog.h b/ydb/library/yql/parser/pg_catalog/catalog.h index 8d326e79ee73..e96ce3394e58 100644 --- a/ydb/library/yql/parser/pg_catalog/catalog.h +++ b/ydb/library/yql/parser/pg_catalog/catalog.h @@ -129,6 +129,8 @@ struct TTypeDesc { // If TypType is 'c', typrelid is the OID of the class' entry in pg_class. ETypType TypType = ETypType::Base; + + ui32 ExtensionIndex = 0; }; enum class ECastMethod { @@ -357,8 +359,7 @@ const TVector& GetStaticTables(); const TTableInfo& LookupStaticTable(const TTableInfoKey& tableKey); const THashMap>& GetStaticColumns(); -void PrepareCatalog(); -bool IsExportFunctionsEnabled(); +bool AreAllFunctionsAllowed(); struct TExtensionDesc { TString Name; // postgis @@ -373,12 +374,16 @@ class IExtensionDDLBuilder { virtual ~IExtensionDDLBuilder() = default; virtual void CreateProc(const TProcDesc& desc) = 0; + + virtual void PrepareType(ui32 extensionIndex,const TString& name) = 0; + + virtual void UpdateType(const TTypeDesc& desc) = 0; }; class IExtensionDDLParser { public: virtual ~IExtensionDDLParser() = default; - virtual void Parse(const TString& sql, IExtensionDDLBuilder& builder) = 0; + virtual void Parse(ui32 extensionIndex, const TString& sql, IExtensionDDLBuilder& builder) = 0; }; class IExtensionLoader { diff --git a/ydb/library/yql/parser/pg_wrapper/ut/proc_ut.cpp b/ydb/library/yql/parser/pg_wrapper/ut/proc_ut.cpp index db4de8307a97..699458e688ed 100644 --- a/ydb/library/yql/parser/pg_wrapper/ut/proc_ut.cpp +++ b/ydb/library/yql/parser/pg_wrapper/ut/proc_ut.cpp @@ -17,7 +17,7 @@ namespace NYql { Y_UNIT_TEST_SUITE(TProcTests) { Y_UNIT_TEST(BuiltinsHasRuntimeFuncs) { - if (NPg::IsExportFunctionsEnabled()) { + if (NPg::AreAllFunctionsAllowed()) { return; } diff --git a/ydb/library/yql/sql/pg/pg_sql.cpp b/ydb/library/yql/sql/pg/pg_sql.cpp index 0b70e959157c..7c63697c1cc8 100644 --- a/ydb/library/yql/sql/pg/pg_sql.cpp +++ b/ydb/library/yql/sql/pg/pg_sql.cpp @@ -96,6 +96,11 @@ const char* StrVal(const ValUnion& val) { return strVal(&val.node); } +int BoolVal(const Node* node) { + Y_ENSURE(node->type == T_Boolean); + return boolVal(node); +} + int IntVal(const Node* node) { Y_ENSURE(node->type == T_Integer); return intVal(node); @@ -5156,8 +5161,9 @@ TVector PGToYqlStatements(const TString& query, const NSQ class TExtensionHandler : public IPGParseEvents { public: - TExtensionHandler(NYql::NPg::IExtensionDDLBuilder& builder) - : Builder(builder) + TExtensionHandler(ui32 extensionIndex, NYql::NPg::IExtensionDDLBuilder& builder) + : ExtensionIndex(extensionIndex) + , Builder(builder) {} void OnResult(const List* raw) final { @@ -5178,11 +5184,179 @@ class TExtensionHandler : public IPGParseEvents { switch (NodeTag(node)) { case T_CreateFunctionStmt: return ParseCreateFunctionStmt(CAST_NODE(CreateFunctionStmt, node)); + case T_DefineStmt: + return ParseDefineStmt(CAST_NODE(DefineStmt, node)); default: return false; } } + [[nodiscard]] + bool ParseDefineStmt(const DefineStmt* value) { + switch (value->kind) { + case OBJECT_TYPE: + return ParseDefineType(value); + default: + return false; + } + } + + [[nodiscard]] + bool ParseDefineType(const DefineStmt* value) { + if (ListLength(value->defnames) != 1) { + return false; + } + + auto nameNode = ListNodeNth(value->defnames, 0); + auto name = to_lower(TString(StrVal(nameNode))); + if (!NPg::HasType(name)) { + Builder.PrepareType(ExtensionIndex, name); + } + + NPg::TTypeDesc desc = NPg::LookupType(name); + + for (int i = 0; i < ListLength(value->definition); ++i) { + auto node = LIST_CAST_NTH(DefElem, value->definition, i); + TString defnameStr(node->defname); + if (defnameStr == "internallength") { + if (NodeTag(node->arg) == T_Integer) { + desc.TypeLen = IntVal(node->arg); + } else if (NodeTag(node->arg) == T_TypeName) { + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + if (value == "variable") { + desc.TypeLen = -1; + } else { + return false; + } + } else { + return false; + } + } else if (defnameStr == "alignment") { + if (NodeTag(node->arg) != T_TypeName) { + return false; + } + + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + if (value == "double") { + desc.TypeAlign = 'd'; + } else if (value == "int") { + desc.TypeAlign = 'i'; + } else if (value == "short") { + desc.TypeAlign = 's'; + } else if (value == "char") { + desc.TypeAlign = 'c'; + } else { + throw yexception() << "Unsupported alignment: " << value; + } + } else if (defnameStr == "input") { + if (NodeTag(node->arg) != T_TypeName) { + return false; + } + + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + try { + desc.InFuncId = NPg::LookupProc(value, {NPg::LookupType("cstring").TypeId}).ProcId; + } catch (const yexception&) { + desc.InFuncId = NPg::LookupProc(value, { + NPg::LookupType("cstring").TypeId, + NPg::LookupType("oid").TypeId, + NPg::LookupType("integer").TypeId + }).ProcId; + } + } else if (defnameStr == "output") { + if (NodeTag(node->arg) != T_TypeName) { + return false; + } + + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + desc.OutFuncId = NPg::LookupProc(value, {desc.TypeId}).ProcId; + } else if (defnameStr == "send") { + if (NodeTag(node->arg) != T_TypeName) { + return false; + } + + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + desc.SendFuncId = NPg::LookupProc(value, {desc.TypeId}).ProcId; + } else if (defnameStr == "receive") { + if (NodeTag(node->arg) != T_TypeName) { + return false; + } + + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + try { + desc.ReceiveFuncId = NPg::LookupProc(value, {NPg::LookupType("internal").TypeId}).ProcId; + } catch (const yexception&) { + desc.ReceiveFuncId = NPg::LookupProc(value, { + NPg::LookupType("internal").TypeId, + NPg::LookupType("oid").TypeId, + NPg::LookupType("integer").TypeId + }).ProcId; + } + } else if (defnameStr == "delimiter") { + if (NodeTag(node->arg) != T_String) { + return false; + } + + TString value(StrVal(node->arg)); + Y_ENSURE(value.size() == 1); + desc.TypeDelim = value[0]; + } else if (defnameStr == "typmod_in") { + if (NodeTag(node->arg) != T_TypeName) { + return false; + } + + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + desc.TypeModInFuncId = NPg::LookupProc(value, {NPg::LookupType("_cstring").TypeId}).ProcId; + } else if (defnameStr == "typmod_out") { + if (NodeTag(node->arg) != T_TypeName) { + return false; + } + + TString value; + if (!ParseTypeName(CAST_NODE_EXT(PG_TypeName, T_TypeName, node->arg), value)) { + return false; + } + + desc.TypeModInFuncId = NPg::LookupProc(value, {NPg::LookupType("int4").TypeId}).ProcId; + } + } + + if (desc.TypeLen >= 0 && desc.TypeLen <= 8) { + desc.PassByValue = true; + } + + Builder.UpdateType(desc); + return true; + } + [[nodiscard]] bool ParseCreateFunctionStmt(const CreateFunctionStmt* value) { NYql::NPg::TProcDesc desc; @@ -5199,14 +5373,13 @@ class TExtensionHandler : public IPGParseEvents { desc.Name = name; desc.IsStrict = false; if (value->returnType) { - if (ListLength(value->returnType->names) != 1) { + TString resultTypeStr; + if (!ParseTypeName(value->returnType, resultTypeStr)) { return false; } - auto resultTypeNode = ListNodeNth(value->returnType->names, 0); - auto resultTypeStr = TString(StrVal(resultTypeNode)); if (!NPg::HasType(resultTypeStr)) { - return false; + Builder.PrepareType(ExtensionIndex, resultTypeStr); } desc.ResultType = NPg::LookupType(resultTypeStr).TypeId; @@ -5225,17 +5398,18 @@ class TExtensionHandler : public IPGParseEvents { return false; } - auto extStr = to_lower(TString(StrVal(ListNodeNth(asList, 0)))); + auto extStr = TString(StrVal(ListNodeNth(asList, 0))); auto srcStr = asListLen > 1 ? - to_lower(TString(StrVal(ListNodeNth(asList, 1)))) : + TString(StrVal(ListNodeNth(asList, 1))) : name; - desc.ExtensionIndex = NPg::LookupExtensionByInstallName(extStr); + Y_ENSURE(ExtensionIndex == NPg::LookupExtensionByInstallName(extStr)); + desc.ExtensionIndex = ExtensionIndex; desc.Src = srcStr; } else if (pass == 0 && defnameStr == "strict") { - desc.IsStrict = CAST_NODE(Boolean, node->arg)->boolval; + desc.IsStrict = BoolVal(node->arg); } else if (pass == 0 && defnameStr == "language") { - auto langStr = TString(CAST_NODE(String, node->arg)->sval); + auto langStr = TString(StrVal(node->arg)); if (langStr == "c") { desc.Lang = NPg::LangC; } else { @@ -5249,7 +5423,7 @@ class TExtensionHandler : public IPGParseEvents { for (int i = 0; i < ListLength(value->parameters); ++i) { auto node = LIST_CAST_NTH(FunctionParameter, value->parameters, i); hasArgNames = hasArgNames || (node->name != nullptr); - if (node->mode == FUNC_PARAM_IN) { + if (node->mode == FUNC_PARAM_IN || node->mode == FUNC_PARAM_DEFAULT) { desc.InputArgNames.push_back(node->name ? node->name : ""); } else if (node->mode == FUNC_PARAM_OUT) { desc.OutputArgNames.push_back(node->name ? node->name : ""); @@ -5259,25 +5433,17 @@ class TExtensionHandler : public IPGParseEvents { return false; } - auto argTypeLen = ListLength(node->argType->names); - if (argTypeLen < 1 || argTypeLen > 2) { + TString argTypeStr; + if (!ParseTypeName(node->argType, argTypeStr)) { return false; } - if (argTypeLen == 2) { - auto schemaStr = to_lower(TString(StrVal(ListNodeNth(node->argType->names, 0)))); - if (schemaStr != "pg_catalog") { - return false; - } - } - - auto argTypeStr = to_lower(TString(StrVal(ListNodeNth(node->argType->names, argTypeLen - 1)))); if (!NPg::HasType(argTypeStr)) { - return false; + Builder.PrepareType(ExtensionIndex, argTypeStr); } auto argTypeId = NPg::LookupType(argTypeStr).TypeId; - if (node->mode == FUNC_PARAM_IN) { + if (node->mode == FUNC_PARAM_IN || node->mode == FUNC_PARAM_DEFAULT) { desc.ArgTypes.push_back(argTypeId); } else if (node->mode == FUNC_PARAM_VARIADIC) { desc.VariadicType = argTypeId; @@ -5297,14 +5463,36 @@ class TExtensionHandler : public IPGParseEvents { return true; } + bool ParseTypeName(const PG_TypeName* typeName, TString& value) { + auto len = ListLength(typeName->names); + if (len < 1 || len > 2) { + return false; + } + + if (len == 2) { + auto schemaStr = to_lower(TString(StrVal(ListNodeNth(typeName->names, 0)))); + if (schemaStr != "pg_catalog") { + return false; + } + } + + value = to_lower(TString(StrVal(ListNodeNth(typeName->names, len - 1)))); + if (ListLength(typeName->arrayBounds) && !value.StartsWith('_')) { + value = "_" + value; + } + + return true; + } + private: + const ui32 ExtensionIndex; NYql::NPg::IExtensionDDLBuilder& Builder; }; class TExtensionDDLParser : public NYql::NPg::IExtensionDDLParser { public: - void Parse(const TString& sql, NYql::NPg::IExtensionDDLBuilder& builder) final { - TExtensionHandler handler(builder); + void Parse(ui32 extensionIndex, const TString& sql, NYql::NPg::IExtensionDDLBuilder& builder) final { + TExtensionHandler handler(extensionIndex, builder); NYql::PGParse(sql, handler); } }; diff --git a/ydb/library/yql/tests/sql/yt_native_file/part12/canondata/result.json b/ydb/library/yql/tests/sql/yt_native_file/part12/canondata/result.json index e5012bad43bd..2eb3d0708909 100644 --- a/ydb/library/yql/tests/sql/yt_native_file/part12/canondata/result.json +++ b/ydb/library/yql/tests/sql/yt_native_file/part12/canondata/result.json @@ -2933,9 +2933,9 @@ ], "test.test[pg_catalog-pg_description_pg_syntax-default.txt-Results]": [ { - "checksum": "34e2e5aa77c7fa8af7344e4212ac6def", + "checksum": "ef973b40e478dfa6be2fef50d12a0076", "size": 2768, - "uri": "https://{canondata_backend}/1600758/2724aa196878a9cf43610b7a974a4782dfdbd8c3/resource.tar.gz#test.test_pg_catalog-pg_description_pg_syntax-default.txt-Results_/results.txt" + "uri": "https://{canondata_backend}/1946324/22a41345f3e8fa8e44c5ac24e1c1025ef337bff8/resource.tar.gz#test.test_pg_catalog-pg_description_pg_syntax-default.txt-Results_/results.txt" } ], "test.test[pg_catalog-pg_timezone_abbrevs-default.txt-Debug]": [ diff --git a/ydb/library/yql/tests/sql/yt_native_file/part7/canondata/result.json b/ydb/library/yql/tests/sql/yt_native_file/part7/canondata/result.json index 5d13fad815db..0617d7cdf05e 100644 --- a/ydb/library/yql/tests/sql/yt_native_file/part7/canondata/result.json +++ b/ydb/library/yql/tests/sql/yt_native_file/part7/canondata/result.json @@ -2340,9 +2340,9 @@ ], "test.test[pg_catalog-pg_proc-default.txt-Results]": [ { - "checksum": "3f25c2c5337ef7aa0f2add43d47a413f", - "size": 5118, - "uri": "https://{canondata_backend}/1931696/3d72549615f9286625b89d4e10351a4786a2ae5d/resource.tar.gz#test.test_pg_catalog-pg_proc-default.txt-Results_/results.txt" + "checksum": "e5bc0852aa5e315a747928f13728544c", + "size": 5106, + "uri": "https://{canondata_backend}/1881367/0f1f826fece81822e9ffe93cd993d45264445d77/resource.tar.gz#test.test_pg_catalog-pg_proc-default.txt-Results_/results.txt" } ], "test.test[pg_duplicated-order_by_with_duplicates-default.txt-Debug]": [