From c666c40a614bf32d48b17594fe6eaa3f6f8d2976 Mon Sep 17 00:00:00 2001 From: Jane Xing Date: Thu, 5 Jan 2023 01:07:14 -0600 Subject: [PATCH 1/3] sql: format json in array with bare string mode Previously, json in an array is always format with single quotes surrounding it. To be consistent with postgres, we now format them with bare string mode. I.e. CRDB ``` > SELECT ARRAY['{"a":{}}']::JSON[]; array ---------------------- {"'{\"a\": {}}'"} ``` PG: ``` > SELECT ARRAY['{"a":{}}']::JSON[]; array ---------------- {"{\"a\":{}}"} ``` Also added tests for json array in pgwire. Release note (bug fix): Fixed the formatting of JSON values inside of a SQL array so they don't have improper quoting. --- pkg/sql/logictest/testdata/logic_test/json | 2 +- pkg/sql/pgwire/testdata/pgtest/json_array | 70 ++++++++++++++++++++++ pkg/sql/sem/tree/pgwire_encode.go | 4 ++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 pkg/sql/pgwire/testdata/pgtest/json_array diff --git a/pkg/sql/logictest/testdata/logic_test/json b/pkg/sql/logictest/testdata/logic_test/json index 3a6396c33f00..da291dc6de93 100644 --- a/pkg/sql/logictest/testdata/logic_test/json +++ b/pkg/sql/logictest/testdata/logic_test/json @@ -209,7 +209,7 @@ SELECT * from foo where bar->'x' = '{}' query T SELECT array_agg(bar ORDER BY pk) FROM foo ---- -{"'{\"a\": \"b\"}'","'[1, 2, 3]'","\"hello\"",1.000,true,false,NULL,"'{\"x\": [1, 2, 3]}'","'{\"x\": {\"y\": \"z\"}}'"} +{"{\"a\": \"b\"}","[1, 2, 3]","\"hello\"",1.000,true,false,NULL,"{\"x\": [1, 2, 3]}","{\"x\": {\"y\": \"z\"}}"} statement ok DELETE FROM foo diff --git a/pkg/sql/pgwire/testdata/pgtest/json_array b/pkg/sql/pgwire/testdata/pgtest/json_array new file mode 100644 index 000000000000..5fea7918a046 --- /dev/null +++ b/pkg/sql/pgwire/testdata/pgtest/json_array @@ -0,0 +1,70 @@ +send +Query {"String": "SELECT ARRAY['{\"a\":{}}']::JSON[]"} +---- + +# JSON[] is implicitly treated as JSONB[]. +until crdb_only +ReadyForQuery +---- +{"Type":"RowDescription","Fields":[{"Name":"array","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\":{}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSON[]"} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{}"}]} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{}"}]} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"binary":"000000000000000000000072"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Check if we can handle JSON parameters. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs": [199]} +Describe {"ObjectType": "S"} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"{\\\"a\\\":{}}\"}"}]} +Execute +Sync +---- + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\":{}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# unknown oid type: 199 +until crdb_only +ErrorResponse +ReadyForQuery +---- +{"Type":"ErrorResponse","Code":"08P01"} +{"Type":"ReadyForQuery","TxStatus":"I"} diff --git a/pkg/sql/sem/tree/pgwire_encode.go b/pkg/sql/sem/tree/pgwire_encode.go index f2e74d350b0c..2a2978edcffc 100644 --- a/pkg/sql/sem/tree/pgwire_encode.go +++ b/pkg/sql/sem/tree/pgwire_encode.go @@ -151,6 +151,10 @@ func (d *DArray) pgwireFormat(ctx *FmtCtx) { floatTyp := d.ResolvedType().ArrayContents() b := PgwireFormatFloat(nil /*buf*/, fl, ctx.dataConversionConfig, floatTyp) ctx.WriteString(string(b)) + case *DJSON: + flags := ctx.flags | fmtRawStrings + s := AsStringWithFlags(v, flags, FmtDataConversionConfig(ctx.dataConversionConfig)) + pgwireFormatStringInArray(ctx, s) default: s := AsStringWithFlags(v, ctx.flags, FmtDataConversionConfig(ctx.dataConversionConfig)) pgwireFormatStringInArray(ctx, s) From ce510543184bd057ce768184c3a87a1f6b3bb5e8 Mon Sep 17 00:00:00 2001 From: Jane Xing Date: Thu, 5 Jan 2023 02:28:57 -0600 Subject: [PATCH 2/3] sql/pgwire: support for decoding JSON[] OID by aliasing to JSONB[] OID fixes #90839 Previously, with the parameter type oid (199) for JSON[], the pgwire parse message will fail to be executed with a `unknown oid type: 199`. We now aliasing it to the one for `JSONB[]`. This commit follows the changes in #88379. Release note (sql change): The pgwire protocol implementation can now accept arguments of the JSON[] type (oid=199). Previously, it could only accept JSONB[] (oid=3804). Internally, JSON[] and JSONB[] values are still identical, so this change only affects how the values are received over the wire protocol. --- pkg/sql/conn_executor_prepare.go | 9 +- pkg/sql/pgwire/conn.go | 11 +- pkg/sql/pgwire/pgwirebase/encoding.go | 28 ++ pkg/sql/pgwire/testdata/pgtest/json_array | 468 +++++++++++++++++++++- pkg/sql/sem/builtins/builtins.go | 6 +- pkg/sql/session_state.go | 10 +- pkg/sql/types/oid.go | 6 +- pkg/sql/types/types.go | 20 +- 8 files changed, 532 insertions(+), 26 deletions(-) diff --git a/pkg/sql/conn_executor_prepare.go b/pkg/sql/conn_executor_prepare.go index e2dd0119355e..b025d723622c 100644 --- a/pkg/sql/conn_executor_prepare.go +++ b/pkg/sql/conn_executor_prepare.go @@ -417,11 +417,14 @@ func (ex *connExecutor) execBind( } else { typ, ok := types.OidToType[t] if !ok { + // These special cases for json, json[] is here so we can + // support decoding parameters with oid=json/json[] without + // adding full support for these type. + // TODO(sql-exp): Remove this if we support JSON. if t == oid.T_json { - // This special case is here so we can support decoding parameters - // with oid=json without adding full support for the JSON type. - // TODO(sql-exp): Remove this if we support JSON. typ = types.Json + } else if t == oid.T__json { + typ = types.JSONArrayForDecodingOnly } else { var err error typ, err = ex.planner.ResolveTypeByOID(ctx, t) diff --git a/pkg/sql/pgwire/conn.go b/pkg/sql/pgwire/conn.go index c7b6b5903db9..98356bac3865 100644 --- a/pkg/sql/pgwire/conn.go +++ b/pkg/sql/pgwire/conn.go @@ -973,13 +973,18 @@ func (c *conn) handleParse( sqlTypeHints[i] = nil continue } + // This special case for json, json[] is here so we can support decoding + // parameters with oid=json/json[] without adding full support for these + // type. + // TODO(sql-exp): Remove this if we support JSON. if t == oid.T_json { - // This special case is here so we can support decoding parameters - // with oid=json without adding full support for the JSON type. - // TODO(sql-exp): Remove this if we support JSON. sqlTypeHints[i] = types.Json continue } + if t == oid.T__json { + sqlTypeHints[i] = types.JSONArrayForDecodingOnly + continue + } v, ok := types.OidToType[t] if !ok { err := pgwirebase.NewProtocolViolationErrorf("unknown oid type: %v", t) diff --git a/pkg/sql/pgwire/pgwirebase/encoding.go b/pkg/sql/pgwire/pgwirebase/encoding.go index fe5dd7b51e7b..d4e4d03f1dc4 100644 --- a/pkg/sql/pgwire/pgwirebase/encoding.go +++ b/pkg/sql/pgwire/pgwirebase/encoding.go @@ -503,6 +503,34 @@ func DecodeDatum( return nil, err } return tree.ParseDJSON(string(b)) + case oid.T__json, oid.T__jsonb: + var arr pgtype.JSONBArray + if err := arr.DecodeText(nil, b); err != nil { + return nil, tree.MakeParseError(string(b), typ, err) + } + if arr.Status != pgtype.Present { + return tree.DNull, nil + } + if err := validateArrayDimensions(len(arr.Dimensions), len(arr.Elements)); err != nil { + return nil, err + } + out := tree.NewDArray(types.Jsonb) + var d tree.Datum + var err error + for _, v := range arr.Elements { + if v.Status != pgtype.Present { + d = tree.DNull + } else { + d, err = tree.ParseDJSON(string(v.Bytes)) + if err != nil { + return nil, err + } + } + if err := out.Append(d); err != nil { + return nil, err + } + } + return out, nil case oid.T_tsquery: ret, err := tsearch.ParseTSQuery(string(b)) if err != nil { diff --git a/pkg/sql/pgwire/testdata/pgtest/json_array b/pkg/sql/pgwire/testdata/pgtest/json_array index 5fea7918a046..c2ce00acf1c9 100644 --- a/pkg/sql/pgwire/testdata/pgtest/json_array +++ b/pkg/sql/pgwire/testdata/pgtest/json_array @@ -1,4 +1,4 @@ -send +send crdb_only Query {"String": "SELECT ARRAY['{\"a\":{}}']::JSON[]"} ---- @@ -6,46 +6,138 @@ Query {"String": "SELECT ARRAY['{\"a\":{}}']::JSON[]"} until crdb_only ReadyForQuery ---- -{"Type":"RowDescription","Fields":[{"Name":"array","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} -{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\":{}}\"}"}]} +{"Type":"RowDescription","Fields":[{"Name":"array","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {}}\"}"}]} {"Type":"CommandComplete","CommandTag":"SELECT 1"} {"Type":"ReadyForQuery","TxStatus":"I"} + +# Array value must be of `{}` form. send Parse {"Query": "SELECT $1::JSON[]"} -Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{}"}]} +Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":""}]} Execute Sync ---- until +ErrorResponse +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ErrorResponse","Code":"22P02"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Test text output encoding for JSON array. +send crdb_only +Parse {"Query": "SELECT $1::JSON[]"} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +until crdb_only ReadyForQuery ---- {"Type":"ParseComplete"} {"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[3807]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} {"Type":"DataRow","Values":[{"text":"{}"}]} {"Type":"CommandComplete","CommandTag":"SELECT 1"} {"Type":"ReadyForQuery","TxStatus":"I"} +send crdb_only +Parse {"Query": "SELECT $1::JSON[]"} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"{\\\"a\\\": {}}\"}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[3807]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Test binary output encoding for JSON array text. send +Parse {"Query": "SELECT $1::JSON[]"} Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{}"}]} +Describe {"ObjectType": "S"} Execute Sync ---- -until +# CRDB currently only supports decoding JSON values. Encoding will always be in +# JSONB format. +until crdb_only ReadyForQuery ---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[3807]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"000000000000000000000eda"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} {"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} {"Type":"DataRow","Values":[{"binary":"000000000000000000000072"}]} {"Type":"CommandComplete","CommandTag":"SELECT 1"} {"Type":"ReadyForQuery","TxStatus":"I"} -# Check if we can handle JSON parameters. +send +Parse {"Query": "SELECT $1::JSON[]"} +Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{\"{\\\"a\\\": {}}\"}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +# CRDB currently only supports decoding JSON values. Encoding will always be in +# JSONB format. +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[3807]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"0000000100000000000000720000000100000001000000097b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Check that we can handle JSON parameters. +# Text to Text. send Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs": [199]} Describe {"ObjectType": "S"} -Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"{\\\"a\\\":{}}\"}"}]} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"{\\\"a\\\": {}}\"}"}]} Execute Sync ---- @@ -57,14 +149,372 @@ ReadyForQuery {"Type":"ParameterDescription","ParameterOIDs":[199]} {"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} {"Type":"BindComplete"} -{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\":{}}\"}"}]} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {}}\"}"}]} {"Type":"CommandComplete","CommandTag":"SELECT 1"} {"Type":"ReadyForQuery","TxStatus":"I"} -# unknown oid type: 199 +# CRDB currently only supports decoding JSON values. Encoding will always be in +# JSONB format. +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Text to binary. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +# CRDB currently only supports decoding JSON values. Encoding will always be in +# JSONB format. until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"000000000000000000000eda"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"000000000000000000000072"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{\"{\\\"a\\\": {}}\"}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +# CRDB currently only supports decoding JSON values. Encoding will always be in +# JSONB format. +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"0000000100000000000000720000000100000001000000097b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Binary to Text. +# `000000000000000000000eda` is the binary representation of `{}` in +# type JSONB[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"000000000000000000000eda"}]} +Execute +Sync +---- + +# PG returns 42804 (data type mismatch) while CRDB returns 08P01 +# (protocol violation), but both are acceptable. +until mapError=(42804, 08P01) ErrorResponse ReadyForQuery ---- +{"Type":"ParseComplete"} {"Type":"ErrorResponse","Code":"08P01"} {"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSONB[]", "ParameterOIDs":[3807]} +Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"000000000000000000000eda"}]} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# `000000000000000000000072` is the binary representation of `{}` in +# type JSON[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Describe {"ObjectType": "S"} +Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"000000000000000000000072"}]} +Execute +Sync +---- + +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# `000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d` +# is the binary representation of `{\"{\\\"a\\\": {}}\"}` in type JSONB[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +Execute +Sync +---- + +# PG returns 42804 (data type mismatch) while CRDB returns 08P01 +# (protocol violation), but both are acceptable. +until mapError=(42804, 08P01) +ErrorResponse +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ErrorResponse","Code":"08P01"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSONB[]", "ParameterOIDs":[3807]} +Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# `0000000100000000000000720000000100000001000000097b2261223a207b7d7d` +# is the binary representation of `{\"{\\\"a\\\": {}}\"}` in type JSON[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"0000000100000000000000720000000100000001000000097b2261223a207b7d7d"}]} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Binary to Binary. +# `000000000000000000000eda` is the binary representation of `{}` in +# type JSONB[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [1], "ResultFormatCodes": [1], "Parameters": [{"binary":"000000000000000000000eda"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +# PG returns 42804 (data type mismatch) while CRDB returns 08P01 +# (protocol violation), but both are acceptable. +until mapError=(42804, 08P01) +ErrorResponse +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ErrorResponse","Code":"08P01"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSONB[]", "ParameterOIDs":[3807]} +Bind {"ParameterFormatCodes": [1], "ResultFormatCodes": [1], "Parameters": [{"binary":"000000000000000000000eda"}]} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"binary":"000000000000000000000eda"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# `000000000000000000000072` is the binary representation of `{}` in +# type JSON[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Describe {"ObjectType": "S"} +Bind {"ParameterFormatCodes": [1], "ResultFormatCodes": [1], "Parameters": [{"binary":"000000000000000000000072"}]} +Execute +Sync +---- + +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"binary":"000000000000000000000072"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"binary":"000000000000000000000072"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# `000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d` +# is the binary representation of `{\"{\\\"a\\\": {}}\"}` in type JSONB[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [1], "ResultFormatCodes": [1], "Parameters": [{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +Execute +Sync +---- + +# PG returns 42804 (data type mismatch) while CRDB returns 08P01 +# (protocol violation), but both are acceptable. +until mapError=(42804, 08P01) +ErrorResponse +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ErrorResponse","Code":"08P01"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSONB[]", "ParameterOIDs":[3807]} +Bind {"ParameterFormatCodes": [1], "ResultFormatCodes": [1], "Parameters": [{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# `0000000100000000000000720000000100000001000000097b2261223a207b7d7d` +# is the binary representation of `{\"{\\\"a\\\": {}}\"}` in type JSON[]. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [1], "ResultFormatCodes": [1], "Parameters": [{"binary":"0000000100000000000000720000000100000001000000097b2261223a207b7d7d"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"0000000100000000000000720000000100000001000000097b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"0000000100000000000000720000000100000001000000097b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# Test with nested JSON. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs": [199]} +Describe {"ObjectType": "S"} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"{\\\"a\\\":{\\\"b\\\": \\\"c\\\"}}\"}"}]} +Execute +Sync +---- + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\":{\\\"b\\\": \\\"c\\\"}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {\\\"b\\\": \\\"c\\\"}}\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} diff --git a/pkg/sql/sem/builtins/builtins.go b/pkg/sql/sem/builtins/builtins.go index b6394461dcbf..3eec3a410e2a 100644 --- a/pkg/sql/sem/builtins/builtins.go +++ b/pkg/sql/sem/builtins/builtins.go @@ -4209,7 +4209,7 @@ value if you rely on the HLC for accuracy.`, ), "crdb_internal.merge_statement_stats": makeBuiltin(arrayProps(), tree.Overload{ - Types: tree.ParamTypes{{Name: "input", Typ: types.JSONArray}}, + Types: tree.ParamTypes{{Name: "input", Typ: types.JSONBArray}}, ReturnType: tree.FixedReturnType(types.Jsonb), Fn: func(_ context.Context, _ *eval.Context, args tree.Datums) (tree.Datum, error) { arr := tree.MustBeDArray(args[0]) @@ -4240,7 +4240,7 @@ value if you rely on the HLC for accuracy.`, ), "crdb_internal.merge_transaction_stats": makeBuiltin(arrayProps(), tree.Overload{ - Types: tree.ParamTypes{{Name: "input", Typ: types.JSONArray}}, + Types: tree.ParamTypes{{Name: "input", Typ: types.JSONBArray}}, ReturnType: tree.FixedReturnType(types.Jsonb), Fn: func(_ context.Context, _ *eval.Context, args tree.Datums) (tree.Datum, error) { arr := tree.MustBeDArray(args[0]) @@ -4274,7 +4274,7 @@ value if you rely on the HLC for accuracy.`, ), "crdb_internal.merge_stats_metadata": makeBuiltin(arrayProps(), tree.Overload{ - Types: tree.ParamTypes{{Name: "input", Typ: types.JSONArray}}, + Types: tree.ParamTypes{{Name: "input", Typ: types.JSONBArray}}, ReturnType: tree.FixedReturnType(types.Jsonb), Fn: func(_ context.Context, _ *eval.Context, args tree.Datums) (tree.Datum, error) { arr := tree.MustBeDArray(args[0]) diff --git a/pkg/sql/session_state.go b/pkg/sql/session_state.go index cb19540f1ee5..1f5ca6783974 100644 --- a/pkg/sql/session_state.go +++ b/pkg/sql/session_state.go @@ -175,13 +175,19 @@ func (p *planner) DeserializeSessionState( placeholderTypes[i] = nil continue } + // These special cases for json, json[] is here so we can + // support decoding parameters with oid=json/json[] without + // adding full support for these type. + // TODO(sql-exp): Remove this if we support JSON. if t == oid.T_json { - // This special case is here so we can support decoding parameters - // with oid=json without adding full support for the JSON type. // TODO(sql-exp): Remove this if we support JSON. placeholderTypes[i] = types.Json continue } + if t == oid.T__json { + placeholderTypes[i] = types.JSONArrayForDecodingOnly + continue + } v, ok := types.OidToType[t] if !ok { err := pgwirebase.NewProtocolViolationErrorf("unknown oid type: %v", t) diff --git a/pkg/sql/types/oid.go b/pkg/sql/types/oid.go index 3e95a35daa99..41336ec6b15c 100644 --- a/pkg/sql/types/oid.go +++ b/pkg/sql/types/oid.go @@ -238,7 +238,11 @@ func CalcArrayOid(elemTyp *T) oid.Oid { // Map the OID of the array element type to the corresponding array OID. // This should always be possible for all other OIDs (checked in oid.go // init method). - o = oidToArrayOid[o] + if o == oid.T_json { + o = oid.T__json + } else { + o = oidToArrayOid[o] + } if o == 0 { panic(errors.AssertionFailedf("oid %d couldn't be mapped to array oid", o)) } diff --git a/pkg/sql/types/types.go b/pkg/sql/types/types.go index 0a1d1947861e..e15ec2160470 100644 --- a/pkg/sql/types/types.go +++ b/pkg/sql/types/types.go @@ -650,10 +650,16 @@ var ( AnyEnumArray = &T{InternalType: InternalType{ Family: ArrayFamily, ArrayContents: AnyEnum, Oid: oid.T_anyarray, Locale: &emptyLocale}} - // JSONArray is the type of an array value having JSON-typed elements. - JSONArray = &T{InternalType: InternalType{ + // JSONBArray is the type of an array value having JSONB-typed elements. + JSONBArray = &T{InternalType: InternalType{ Family: ArrayFamily, ArrayContents: Jsonb, Oid: oid.T__jsonb, Locale: &emptyLocale}} + // JSONArrayForDecodingOnly is the type of an array value having JSON-typed elements. + // Note that this struct can only used for decoding an input as we don't fully + // support the json array yet. + JSONArrayForDecodingOnly = &T{InternalType: InternalType{ + Family: ArrayFamily, ArrayContents: Json, Oid: oid.T__json, Locale: &emptyLocale}} + // Int2Vector is a type-alias for an array of Int2 values with a different // OID (T_int2vector instead of T__int2). It is a special VECTOR type used // by Postgres in system tables. Int2vectors are 0-indexed, unlike normal arrays. @@ -1366,12 +1372,16 @@ func (t *T) WithoutTypeModifiers() *T { typ, ok := OidToType[t.Oid()] if !ok { + // These special cases for json, json[] is here so we can + // support decoding parameters with oid=json/json[] without + // adding full support for these type. + // TODO(sql-exp): Remove this if we support JSON. if t.Oid() == oid.T_json { - // This special case is here so we can support decoding parameters - // with oid=json without adding full support for the JSON type. - // TODO(sql-exp): Remove this if we support JSON. return Jsonb } + if t.Oid() == oid.T__json { + return JSONArrayForDecodingOnly + } panic(errors.AssertionFailedf("unexpected OID: %d", t.Oid())) } return typ From 78ffc003512a875399746aeb1be1aaf32ec50214 Mon Sep 17 00:00:00 2001 From: Jane Xing Date: Wed, 18 Jan 2023 09:50:49 -0600 Subject: [PATCH 3/3] pgwire: add tests for decoding json text with spaces between kv We noticed a bug that in crdb the text output for a json doesn't follow input spacing between key and value. We are adding tests for this and this bug is filed in https://github.com/cockroachdb/cockroach/issues/95434. Release note: None --- pkg/sql/pgwire/testdata/pgtest/json | 65 +++++++++++++++++++++++ pkg/sql/pgwire/testdata/pgtest/json_array | 62 +++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/pkg/sql/pgwire/testdata/pgtest/json b/pkg/sql/pgwire/testdata/pgtest/json index 3f2be93a4236..1b57ab932c7a 100644 --- a/pkg/sql/pgwire/testdata/pgtest/json +++ b/pkg/sql/pgwire/testdata/pgtest/json @@ -116,3 +116,68 @@ ReadyForQuery {"Type":"DataRow","Values":[{"binary":"017b226b6579223a202276616c227d"}]} {"Type":"CommandComplete","CommandTag":"SELECT 1"} {"Type":"ReadyForQuery","TxStatus":"I"} + +# Test formatting for spaces between key and value in a json. +# https://github.com/cockroachdb/cockroach/issues/95434. +send +Parse {"Query": "SELECT $1::JSON"} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"key\": \"val\"}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +# PG's format output follows the input. +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[114]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":114,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"{\"key\": \"val\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# CRDB has a fixed len of space between the key and value when formatting the json. +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[3802]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3802,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"{\"key\": \"val\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSON"} +Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"key\":\"val\"}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[114]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":114,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"{\"key\":\"val\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +# CRDB has a fixed len of space between the key and value when formatting the json. +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[3802]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3802,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"{\"key\": \"val\"}"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} diff --git a/pkg/sql/pgwire/testdata/pgtest/json_array b/pkg/sql/pgwire/testdata/pgtest/json_array index c2ce00acf1c9..28261f158527 100644 --- a/pkg/sql/pgwire/testdata/pgtest/json_array +++ b/pkg/sql/pgwire/testdata/pgtest/json_array @@ -518,3 +518,65 @@ ReadyForQuery {"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {\\\"b\\\": \\\"c\\\"}}\"}"}]} {"Type":"CommandComplete","CommandTag":"SELECT 1"} {"Type":"ReadyForQuery","TxStatus":"I"} + +# PG's binary stores the space between kv, while crdb does not. +# https://github.com/cockroachdb/cockroach/issues/95434. +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{\"{\\\"a\\\": {}}\"}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"00000001000000000000007200000001000000010000000e7b2261223a2020202020207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]} +Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{\"{\\\"a\\\":{}}\"}"}]} +Describe {"ObjectType": "S"} +Execute +Sync +---- + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"json","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":199,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"0000000100000000000000720000000100000001000000087b2261223a7b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +until crdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[199]} +{"Type":"RowDescription","Fields":[{"Name":"jsonb","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":3807,"DataTypeSize":-1,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"}