Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rGfAY-00GRZF-Ny for pgsql-hackers@arkaria.postgresql.org; Fri, 22 Dec 2023 13:02:26 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1rGf9W-006ey0-TZ for pgsql-hackers@arkaria.postgresql.org; Fri, 22 Dec 2023 13:01:22 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rGf9W-006exC-BE for pgsql-hackers@lists.postgresql.org; Fri, 22 Dec 2023 13:01:22 +0000 Received: from mail-ua1-x929.google.com ([2607:f8b0:4864:20::929]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1rGf9T-00BfPw-K8 for pgsql-hackers@postgresql.org; Fri, 22 Dec 2023 13:01:21 +0000 Received: by mail-ua1-x929.google.com with SMTP id a1e0cc1a2514c-7cb73c63bf6so508050241.2 for ; Fri, 22 Dec 2023 05:01:19 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1703250078; x=1703854878; darn=postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=k+OrXzsrxRk3cgUjJzzsL2abCo+QRhGuF0YS1HcHSNc=; b=FuMRdTsFd9VS6iEAu5eq9CK98SAott5KULsY/rVuWR0UeMdTH4g76zFp6x5EEp7xzD +MYzJ1hUqtrsfpETUpy8fG/3gX8wB2bc2T9dgPRq55nU5FudzW8KVBXvFSLATT67IxbA PjgcfDLl0X1Si/xEpp1ivqfqh+1OvtyPtn/MllaCiF9CT1pyHto8ygwdQPUQtQhEt8Ru yDPBEpAaX0aYWSTclycURwGz6rCcRdWVpmUajROuR+T7xO2HKhrcDP33Eb53APt7YwnD ra/DT9PYGjtw4u6LdabvJyJmnVfwpj16GPlkvqge4xEJvW+llhrWlBn4n6fF8mrdMUTg mWHA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1703250078; x=1703854878; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=k+OrXzsrxRk3cgUjJzzsL2abCo+QRhGuF0YS1HcHSNc=; b=KssK3FON4Z70UgRjWuRAeZyoup05n5KOSFFG6zluRvhJPEdRvYoaBnChHsm3gVpXpG ZuJE/X0iU7QiodJs8a77d5kE0n87SYC8Fk8o68zSr/l78DYz6qb6fCU0Fz1tZvUq16J9 8KiYDQsaKts4Y9R+ushmSfJCx0ZiGrW+U7JH3r2mXg3KChw6aMt69E4vB0i5hiN3oCvc 2oajPa0uH1Q3JRBzVAV828pi+65hpUDsdpo01lzw3+6NLUTQ3jCOSYSBkM5Xq1AHdP6H qZcKaASESQcIHt5HoU5vRkqx+FWbHxWd6jSfhoMLZxS+jA6ssJXpU3GdInikqGu3Lvjz 0zwA== X-Gm-Message-State: AOJu0YyWX1IMsZw2AijZsids9k+v/D0Wd0hZHgKByio7FH2udELhhno4 yEGErsyYhSmeKclJqP/7+hX+i92Y2VjvKCYvCMg= X-Google-Smtp-Source: AGHT+IGRigP/kpSPkoupuX7/4radv5yPWcmo9nnOajSLKmX757o83YRPNZifog6cDmWZsP9ZMktFV0/XaaBofzahbxs= X-Received: by 2002:a05:6102:20c4:b0:466:9944:59bf with SMTP id i4-20020a05610220c400b00466994459bfmr713704vsr.23.1703250077251; Fri, 22 Dec 2023 05:01:17 -0800 (PST) MIME-Version: 1.0 References: <202312071051.cpxujwos4tlj@alvherre.pgsql> <20231208173014.2qycwc6kjqf6zlck@awork3.anarazel.de> In-Reply-To: From: jian he Date: Fri, 22 Dec 2023 21:01:05 +0800 Message-ID: Subject: Re: remaining sql/json patches To: Amit Langote Cc: Andres Freund , Alvaro Herrera , Erik Rijkers , Andrew Dunstan , PostgreSQL-development Content-Type: multipart/mixed; boundary="000000000000d1973f060d18cfdf" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000d1973f060d18cfdf Content-Type: text/plain; charset="UTF-8" Hi v33-0007-SQL-JSON-query-functions.patch, commit message: This introduces the SQL/JSON functions for querying JSON data using jsonpath expressions. The functions are: should it be "These functions are" + + Returns true if the SQL/JSON path_expression + applied to the context_item using the + values yields any items. + The ON ERROR clause specifies what is returned if + an error occurs; the default is to return FALSE. + Note that if the path_expression + is strict, an error is generated if it yields no items. + I think the following description is more accurate. + Note that if the path_expression + is strict and the ON ERROR clause is ERROR, + an error is generated if it yields no items. + +/* + * transformJsonTable - + * Transform a raw JsonTable into TableFunc. + * + * Transform the document-generating expression, the row-generating expression, + * the column-generating expressions, and the default value expressions. + */ +ParseNamespaceItem * +transformJsonTable(ParseState *pstate, JsonTable *jt) +{ + JsonTableParseContext cxt; + TableFunc *tf = makeNode(TableFunc); + JsonFuncExpr *jfe = makeNode(JsonFuncExpr); + JsonExpr *je; + JsonTablePlan *plan = jt->plan; + char *rootPathName = jt->pathname; + char *rootPath; + bool is_lateral; + + if (jt->on_empty) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ON EMPTY not allowed in JSON_TABLE"), + parser_errposition(pstate, + exprLocation((Node *) jt->on_empty)))); This error may be slightly misleading? you can add ON EMPTY inside the COLUMNS part, like the following: SELECT * FROM (VALUES ('1'), ('"1"')) vals(js) LEFT OUTER JOIN JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' default 1 ON empty)) jt ON true; + + Each NESTED PATH clause can generate one or more + columns. Columns produced by NESTED PATHs at the + same level are considered to be siblings, + while a column produced by a NESTED PATH is + considered to be a child of the column produced by a + NESTED PATH or row expression at a higher level. + Sibling columns are always joined first. Once they are processed, + the resulting rows are joined to the parent row. + Does changing to the following make sense? + considered to be a child of the column produced by a + the resulting rows are joined to the parent row. seems like `format json_representation`, not listed in the documentation, but json_representation is "Parameters", do we need add a section to explain it? even though I think currently we can only do `FORMAT JSON`. SELECT * FROM JSON_TABLE(jsonb '123', '$' COLUMNS (item int PATH '$' empty on empty)) bar; ERROR: cannot cast jsonb array to type integer The error is the same as the output of the following: SELECT * FROM JSON_TABLE(jsonb '123', '$' COLUMNS (item int PATH '$' empty array on empty )) bar; but these two are different things? + /* FALLTHROUGH */ + case JTC_EXISTS: + case JTC_FORMATTED: + { + Node *je; + CaseTestExpr *param = makeNode(CaseTestExpr); + + param->collation = InvalidOid; + param->typeId = cxt->contextItemTypid; + param->typeMod = -1; + + if (rawc->wrapper != JSW_NONE && + rawc->quotes != JS_QUOTES_UNSPEC) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use WITH WRAPPER clause for formatted colunmns" + " without also specifying OMIT/KEEP QUOTES"), + parser_errposition(pstate, rawc->location))); typo, should be "formatted columns". I suspect people will be confused with the meaning of "formatted column". maybe we can replace this part:"cannot use WITH WRAPPER clause for formatted column" to "SQL/JSON WITH WRAPPER behavior must not be specified when FORMAT clause is used" SELECT * FROM JSON_TABLE(jsonb '"world"', '$' COLUMNS (item text FORMAT JSON PATH '$' with wrapper KEEP QUOTES)); ERROR: cannot use WITH WRAPPER clause for formatted colunmns without also specifying OMIT/KEEP QUOTES LINE 1: ...T * FROM JSON_TABLE(jsonb '"world"', '$' COLUMNS (item text ... ^ this error is misleading, since now I am using WITH WRAPPER clause for formatted columns and specified KEEP QUOTES. in parse_expr.c, we have errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"). +/* + * Fetch next row from a cross/union joined scan. + * + * Returns false at the end of a scan, true otherwise. + */ +static bool +JsonTablePlanNextRow(JsonTablePlanState * state) +{ + JsonTableJoinState *join; + + if (state->type == JSON_TABLE_SCAN_STATE) + return JsonTableScanNextRow((JsonTableScanState *) state); + + join = (JsonTableJoinState *) state; + if (join->advanceRight) + { + /* fetch next inner row */ + if (JsonTablePlanNextRow(join->right)) + return true; + + /* inner rows are exhausted */ + if (join->cross) + join->advanceRight = false; /* next outer row */ + else + return false; /* end of scan */ + } + + while (!join->advanceRight) + { + /* fetch next outer row */ + bool left = JsonTablePlanNextRow(join->left); + bool left = JsonTablePlanNextRow(join->left); JsonTablePlanNextRow function comment says "Returns false at the end of a scan, true otherwise.", so bool variable name as "left" seems not so good? It might help others understand the whole code by adding some comments on struct JsonTableScanState and struct JsonTableJoinState. since json_table patch is quite recursive, IMHO. I did some minor refactoring in parse_expr.c, since some code like transformJsonExprCommon is duplicated. --000000000000d1973f060d18cfdf Content-Type: application/octet-stream; name="v33-json_table.nocfbot" Content-Disposition: attachment; filename="v33-json_table.nocfbot" Content-Transfer-Encoding: base64 Content-ID: X-Attachment-Id: f_lqgn3e9d0 ZGlmZiAtLWdpdCBhL3NyYy9iYWNrZW5kL3BhcnNlci9wYXJzZV9leHByLmMgYi9zcmMvYmFja2Vu ZC9wYXJzZXIvcGFyc2VfZXhwci5jCmluZGV4IGU0NzRlMDY0Li4wOTk2MjIxMCAxMDA2NDQKLS0t IGEvc3JjL2JhY2tlbmQvcGFyc2VyL3BhcnNlX2V4cHIuYworKysgYi9zcmMvYmFja2VuZC9wYXJz ZXIvcGFyc2VfZXhwci5jCkBAIC00MjE5LDcgKzQyMTksNyBAQCB0cmFuc2Zvcm1Kc29uU2VyaWFs aXplRXhwcihQYXJzZVN0YXRlICpwc3RhdGUsIEpzb25TZXJpYWxpemVFeHByICpleHByKQogfQog CiAvKgotICogVHJhbnNmb3JtIEpTT05fVkFMVUUsIEpTT05fUVVFUlksIEpTT05fRVhJU1RTIGZ1 bmN0aW9ucyBpbnRvIGEgSnNvbkV4cHIgbm9kZS4KKyAqIFRyYW5zZm9ybSBKU09OX1ZBTFVFLCBK U09OX1FVRVJZLCBKU09OX0VYSVNUUywgSlNPTl9UQUJMRSBmdW5jdGlvbnMgaW50byBhIEpzb25F eHByIG5vZGUuCiAgKi8KIHN0YXRpYyBOb2RlICoKIHRyYW5zZm9ybUpzb25GdW5jRXhwcihQYXJz ZVN0YXRlICpwc3RhdGUsIEpzb25GdW5jRXhwciAqZnVuYykKQEAgLTQzNzIsMTMgKzQzNzIsMTEg QEAgdHJhbnNmb3JtSnNvbkZ1bmNFeHByKFBhcnNlU3RhdGUgKnBzdGF0ZSwgSnNvbkZ1bmNFeHBy ICpmdW5jKQogCQkJYnJlYWs7CiAKIAkJY2FzZSBKU09OX1RBQkxFX09QOgotCQkJZnVuY19uYW1l ID0gIkpTT05fVEFCTEUiOwotCi0JCQlqc2V4cHIgPSB0cmFuc2Zvcm1Kc29uRXhwckNvbW1vbihw c3RhdGUsIGZ1bmMsIGZ1bmNfbmFtZSk7Ci0JCQlqc2V4cHItPnJldHVybmluZyA9IG1ha2VOb2Rl KEpzb25SZXR1cm5pbmcpOwotCQkJanNleHByLT5yZXR1cm5pbmctPmZvcm1hdCA9IG1ha2VKc29u Rm9ybWF0KEpTX0ZPUk1BVF9ERUZBVUxULCBKU19FTkNfREVGQVVMVCwgLTEpOwotCQkJanNleHBy LT5yZXR1cm5pbmctPnR5cGlkID0gZXhwclR5cGUoanNleHByLT5mb3JtYXR0ZWRfZXhwcik7Ci0J CQlqc2V4cHItPnJldHVybmluZy0+dHlwbW9kID0gLTE7CisJCQlpZiAoIU9pZElzVmFsaWQoanNl eHByLT5yZXR1cm5pbmctPnR5cGlkKSkKKwkJCXsKKwkJCQlqc2V4cHItPnJldHVybmluZy0+dHlw aWQgPSBleHByVHlwZShqc2V4cHItPmZvcm1hdHRlZF9leHByKTsKKwkJCQlqc2V4cHItPnJldHVy bmluZy0+dHlwbW9kID0gLTE7CisJCQl9CiAJCQlqc2V4cHItPm9uX2Vycm9yID0gdHJhbnNmb3Jt SnNvbkJlaGF2aW9yKHBzdGF0ZSwgZnVuYy0+b25fZXJyb3IsCiAJCQkJCQkJCQkJCQkJIEpTT05f QkVIQVZJT1JfRU1QVFksCiAJCQkJCQkJCQkJCQkJIGpzZXhwci0+cmV0dXJuaW5nKTsKZGlmZiAt LWdpdCBhL3NyYy9iYWNrZW5kL3BhcnNlci9wYXJzZV9qc29udGFibGUuYyBiL3NyYy9iYWNrZW5k L3BhcnNlci9wYXJzZV9qc29udGFibGUuYwppbmRleCBlZTkxMjNjMy4uYTQ3ZjFjZTMgMTAwNjQ0 Ci0tLSBhL3NyYy9iYWNrZW5kL3BhcnNlci9wYXJzZV9qc29udGFibGUuYworKysgYi9zcmMvYmFj a2VuZC9wYXJzZXIvcGFyc2VfanNvbnRhYmxlLmMKQEAgLTcxLDE0ICs3MSwxMyBAQCB0cmFuc2Zv cm1Kc29uVGFibGVDb2x1bW4oSnNvblRhYmxlQ29sdW1uICpqdGMsIE5vZGUgKmNvbnRleHRJdGVt RXhwciwKIAkJCQkJCSBMaXN0ICpwYXNzaW5nQXJncywgYm9vbCBlcnJvck9uRXJyb3IpCiB7CiAJ SnNvbkZ1bmNFeHByICpqZmV4cHIgPSBtYWtlTm9kZShKc29uRnVuY0V4cHIpOwotCUpzb25PdXRw dXQgKm91dHB1dCA9IG1ha2VOb2RlKEpzb25PdXRwdXQpOwogCWNoYXIJICAgKnBhdGhzcGVjOwog CUpzb25Gb3JtYXQgKmRlZmF1bHRfZm9ybWF0OwogCiAJamZleHByLT5vcCA9CiAJCWp0Yy0+Y29s dHlwZSA9PSBKVENfUkVHVUxBUiA/IEpTT05fVkFMVUVfT1AgOgogCQlqdGMtPmNvbHR5cGUgPT0g SlRDX0VYSVNUUyA/IEpTT05fRVhJU1RTX09QIDogSlNPTl9RVUVSWV9PUDsKLQlqZmV4cHItPm91 dHB1dCA9IG91dHB1dDsKKwlqZmV4cHItPm91dHB1dCA9IG1ha2VOb2RlKEpzb25PdXRwdXQpOwog CWpmZXhwci0+b25fZW1wdHkgPSBqdGMtPm9uX2VtcHR5OwogCWpmZXhwci0+b25fZXJyb3IgPSBq dGMtPm9uX2Vycm9yOwogCWlmIChqZmV4cHItPm9uX2Vycm9yID09IE5VTEwgJiYgZXJyb3JPbkVy cm9yKQpAQCAtODgsOSArODcsOSBAQCB0cmFuc2Zvcm1Kc29uVGFibGVDb2x1bW4oSnNvblRhYmxl Q29sdW1uICpqdGMsIE5vZGUgKmNvbnRleHRJdGVtRXhwciwKIAlqZmV4cHItPndyYXBwZXIgPSBq dGMtPndyYXBwZXI7CiAJamZleHByLT5sb2NhdGlvbiA9IGp0Yy0+bG9jYXRpb247CiAKLQlvdXRw dXQtPnR5cGVOYW1lID0ganRjLT50eXBlTmFtZTsKLQlvdXRwdXQtPnJldHVybmluZyA9IG1ha2VO b2RlKEpzb25SZXR1cm5pbmcpOwotCW91dHB1dC0+cmV0dXJuaW5nLT5mb3JtYXQgPSBqdGMtPmZv cm1hdDsKKwlqZmV4cHItPm91dHB1dC0+dHlwZU5hbWUgPSBqdGMtPnR5cGVOYW1lOworCWpmZXhw ci0+b3V0cHV0LT5yZXR1cm5pbmcgPSBtYWtlTm9kZShKc29uUmV0dXJuaW5nKTsKKwlqZmV4cHIt Pm91dHB1dC0+cmV0dXJuaW5nLT5mb3JtYXQgPSBqdGMtPmZvcm1hdDsKIAogCWRlZmF1bHRfZm9y bWF0ID0gbWFrZUpzb25Gb3JtYXQoSlNfRk9STUFUX0RFRkFVTFQsIEpTX0VOQ19ERUZBVUxULCAt MSk7CiAKQEAgLTQ5Myw3ICs0OTIsNyBAQCBhcHBlbmRKc29uVGFibGVDb2x1bW5zKEpzb25UYWJs ZVBhcnNlQ29udGV4dCAqIGN4dCwgTGlzdCAqY29sdW1ucykKIAkJCQkJCQkJCSIgd2l0aG91dCBh bHNvIHNwZWNpZnlpbmcgRk9STUFUIGNsYXVzZSIpLAogCQkJCQkJCSBwYXJzZXJfZXJycG9zaXRp b24ocHN0YXRlLCByYXdjLT5sb2NhdGlvbikpKTsKIAotCQkJCS8qIEZBTExUSFJPVUdIICovCisJ CQkJLyogRkFMTCBUSFJPVUdIICovCiAJCQljYXNlIEpUQ19FWElTVFM6CiAJCQljYXNlIEpUQ19G T1JNQVRURUQ6CiAJCQkJewpAQCAtNzM1LDcgKzczNCw3IEBAIHRyYW5zZm9ybUpzb25UYWJsZShQ YXJzZVN0YXRlICpwc3RhdGUsIEpzb25UYWJsZSAqanQpCiAJCWNhc3ROb2RlKEFfQ29uc3QsIGp0 LT5wYXRoc3BlYyktPnZhbC5ub2RlLnR5cGUgIT0gVF9TdHJpbmcpCiAJCWVyZXBvcnQoRVJST1Is CiAJCQkJKGVycmNvZGUoRVJSQ09ERV9GRUFUVVJFX05PVF9TVVBQT1JURUQpLAotCQkJCSBlcnJt c2coIm9ubHkgc3RyaW5nIGNvbnN0YW50cyBzdXBwb3J0ZWQgaW4gSlNPTl9UQUJMRSBwYXRoIHNw ZWNpZmljYXRpb24iKSwKKwkJCQkgZXJybXNnKCJvbmx5IHN0cmluZyBhcmUgY29uc3RhbnRzIHN1 cHBvcnRlZCBpbiB0aGUgSlNPTl9UQUJMRSBwYXRoIHNwZWNpZmljYXRpb24iKSwKIAkJCQkgcGFy c2VyX2VycnBvc2l0aW9uKHBzdGF0ZSwKIAkJCQkJCQkJCWV4cHJMb2NhdGlvbihqdC0+cGF0aHNw ZWMpKSkpOwogCmRpZmYgLS1naXQgYS9zcmMvYmFja2VuZC91dGlscy9hZHQvanNvbnBhdGhfZXhl Yy5jIGIvc3JjL2JhY2tlbmQvdXRpbHMvYWR0L2pzb25wYXRoX2V4ZWMuYwppbmRleCA0OWJlOWMw ZS4uZDgyMGQ0NTcgMTAwNjQ0Ci0tLSBhL3NyYy9iYWNrZW5kL3V0aWxzL2FkdC9qc29ucGF0aF9l eGVjLmMKKysrIGIvc3JjL2JhY2tlbmQvdXRpbHMvYWR0L2pzb25wYXRoX2V4ZWMuYwpAQCAtMzUy MSw2ICszNTIxLDcgQEAgc3RhdGljIGJvb2wKIEpzb25UYWJsZVBsYW5OZXh0Um93KEpzb25UYWJs ZVBsYW5TdGF0ZSAqIHN0YXRlKQogewogCUpzb25UYWJsZUpvaW5TdGF0ZSAqam9pbjsKKwlib29s CQlsZWZ0OwogCiAJaWYgKHN0YXRlLT50eXBlID09IEpTT05fVEFCTEVfU0NBTl9TVEFURSkKIAkJ cmV0dXJuIEpzb25UYWJsZVNjYW5OZXh0Um93KChKc29uVGFibGVTY2FuU3RhdGUgKikgc3RhdGUp OwpAQCAtMzU0Miw3ICszNTQzLDcgQEAgSnNvblRhYmxlUGxhbk5leHRSb3coSnNvblRhYmxlUGxh blN0YXRlICogc3RhdGUpCiAJd2hpbGUgKCFqb2luLT5hZHZhbmNlUmlnaHQpCiAJewogCQkvKiBm ZXRjaCBuZXh0IG91dGVyIHJvdyAqLwotCQlib29sCQlsZWZ0ID0gSnNvblRhYmxlUGxhbk5leHRS b3coam9pbi0+bGVmdCk7CisJCWxlZnQgPSBKc29uVGFibGVQbGFuTmV4dFJvdyhqb2luLT5sZWZ0 KTsKIAogCQlpZiAoam9pbi0+Y3Jvc3MpCiAJCXsKZGlmZiAtLWdpdCBhL3NyYy90ZXN0L3JlZ3Jl c3MvZXhwZWN0ZWQvanNvbmJfc3FsanNvbi5vdXQgYi9zcmMvdGVzdC9yZWdyZXNzL2V4cGVjdGVk L2pzb25iX3NxbGpzb24ub3V0CmluZGV4IGVmNTJlNGNlLi4xYjdkNTViNyAxMDA2NDQKLS0tIGEv c3JjL3Rlc3QvcmVncmVzcy9leHBlY3RlZC9qc29uYl9zcWxqc29uLm91dAorKysgYi9zcmMvdGVz dC9yZWdyZXNzL2V4cGVjdGVkL2pzb25iX3NxbGpzb24ub3V0CkBAIC0yMjAzLDYgKzIyMDMsNiBA QCBTRUxFQ1QgSlNPTl9RVUVSWShqc29uYiAneyJhIjogMTIzfScsICdlcnJvcicgfHwgJyAnIHx8 ICdlcnJvcicpOwogRVJST1I6ICBzeW50YXggZXJyb3IgYXQgb3IgbmVhciAiICIgb2YganNvbnBh dGggaW5wdXQKIC0tIFNob3VsZCBmYWlsIChub3Qgc3VwcG9ydGVkKQogU0VMRUNUICogRlJPTSBK U09OX1RBQkxFKGpzb25iICd7ImEiOiAxMjN9JywgJyQnIHx8ICcuJyB8fCAnYScgQ09MVU1OUyAo Zm9vIGludCkpOwotRVJST1I6ICBvbmx5IHN0cmluZyBjb25zdGFudHMgc3VwcG9ydGVkIGluIEpT T05fVEFCTEUgcGF0aCBzcGVjaWZpY2F0aW9uCitFUlJPUjogIG9ubHkgc3RyaW5nIGFyZSBjb25z dGFudHMgc3VwcG9ydGVkIGluIHRoZSBKU09OX1RBQkxFIHBhdGggc3BlY2lmaWNhdGlvbgogTElO RSAxOiBTRUxFQ1QgKiBGUk9NIEpTT05fVEFCTEUoanNvbmIgJ3siYSI6IDEyM30nLCAnJCcgfHwg Jy4nIHx8ICcuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgXgo= --000000000000d1973f060d18cfdf--