Skip to content

Commit

Permalink
sql/pgwire: support for decoding JSON[] OID by aliasing to JSONB[] OID
Browse files Browse the repository at this point in the history
fixes cockroachdb#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 cockroachdb#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.
  • Loading branch information
ZhouXing19 committed Jan 6, 2023
1 parent cbadd91 commit 01ac0df
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 21 deletions.
11 changes: 8 additions & 3 deletions pkg/sql/conn_executor_prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,16 @@ func (ex *connExecutor) execBind(
} else {
typ, ok := types.OidToType[t]
if !ok {
// These special cases for json, json[] and jsonb[] is here so we can
// support decoding parameters with oid=json/json[]/jsonb[] 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.JSONArray
} else if t == oid.T__jsonb {
typ = types.JSONBArray
} else {
var err error
typ, err = ex.planner.ResolveTypeByOID(ctx, t)
Expand Down
15 changes: 12 additions & 3 deletions pkg/sql/pgwire/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -971,13 +971,22 @@ func (c *conn) handleParse(
sqlTypeHints[i] = nil
continue
}
// This special case for json, json[] and jsonb[] is here so we can
// support decoding parameters with oid=json/json[]/jsonb[] 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.JSONArray
continue
}
if t == oid.T__jsonb {
sqlTypeHints[i] = types.JSONBArray
continue
}
v, ok := types.OidToType[t]
if !ok {
err := pgwirebase.NewProtocolViolationErrorf("unknown oid type: %v", t)
Expand Down
28 changes: 28 additions & 0 deletions pkg/sql/pgwire/pgwirebase/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
126 changes: 120 additions & 6 deletions pkg/sql/pgwire/testdata/pgtest/json_array
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
send
send crdb_only
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":"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], "ResultFormatCodes": [1], "Parameters": [{"text":""}]}
Execute
Sync
----

until
ErrorResponse
ReadyForQuery
----
{"Type":"ParseComplete"}
{"Type":"ErrorResponse","Code":"22P02"}
{"Type":"ReadyForQuery","TxStatus":"I"}

send
Parse {"Query": "SELECT $1::JSON[]"}
Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{}"}]}
Expand All @@ -27,6 +43,7 @@ ReadyForQuery
{"Type":"CommandComplete","CommandTag":"SELECT 1"}
{"Type":"ReadyForQuery","TxStatus":"I"}

# Test binary output encoding for JSON array.
send
Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{}"}]}
Execute
Expand All @@ -37,11 +54,40 @@ until
ReadyForQuery
----
{"Type":"BindComplete"}
{"Type":"DataRow","Values":[{"binary":"000000000000000000000072"}]}
{"Type":"DataRow","Values":[{"binary":"000000000000000000000eda"}]}
{"Type":"CommandComplete","CommandTag":"SELECT 1"}
{"Type":"ReadyForQuery","TxStatus":"I"}

send
Bind {"ParameterFormatCodes": [0], "ResultFormatCodes": [1], "Parameters": [{"text":"{\"{\\\"a\\\":{}}\"}"}]}
Execute
Sync
----

until
ReadyForQuery
----
{"Type":"BindComplete"}
{"Type":"DataRow","Values":[{"binary":"000000010000000000000eda00000001000000010000000a017b2261223a207b7d7d"}]}
{"Type":"CommandComplete","CommandTag":"SELECT 1"}
{"Type":"ReadyForQuery","TxStatus":"I"}

# Test text output encoding for JSON array.
send
Bind {"ParameterFormatCodes": [0], "Parameters": [{"text":"{\"{\\\"a\\\":{}}\"}"}]}
Execute
Sync
----

until
ReadyForQuery
----
{"Type":"BindComplete"}
{"Type":"DataRow","Values":[{"text":"{\"{\\\"a\\\": {}}\"}"}]}
{"Type":"CommandComplete","CommandTag":"SELECT 1"}
{"Type":"ReadyForQuery","TxStatus":"I"}

# Check if we can handle JSON parameters.
# Check that we can handle JSON parameters.
send
Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs": [199]}
Describe {"ObjectType": "S"}
Expand All @@ -61,10 +107,78 @@ ReadyForQuery
{"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"}

# Test with input in binary.
send
Parse {"Query": "SELECT $1::JSON[]", "ParameterOIDs":[199]}
Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"000000000000000000000eda"}]}
Execute
Sync
----

until
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"}

# 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"}
6 changes: 3 additions & 3 deletions pkg/sql/sem/builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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])
Expand Down
14 changes: 12 additions & 2 deletions pkg/sql/session_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,23 @@ func (p *planner) DeserializeSessionState(
placeholderTypes[i] = nil
continue
}
// These special cases for json, json[] and jsonb[] is here so we can
// support decoding parameters with oid=json/json[]/jsonb[] 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.JSONArray
continue
}
if t == oid.T__jsonb {
placeholderTypes[i] = types.JSONBArray
continue
}
v, ok := types.OidToType[t]
if !ok {
err := pgwirebase.NewProtocolViolationErrorf("unknown oid type: %v", t)
Expand Down
19 changes: 15 additions & 4 deletions pkg/sql/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,9 +650,13 @@ var (
AnyEnumArray = &T{InternalType: InternalType{
Family: ArrayFamily, ArrayContents: AnyEnum, Oid: oid.T_anyarray, Locale: &emptyLocale}}

// 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}}

// JSONArray is the type of an array value having JSON-typed elements.
JSONArray = &T{InternalType: InternalType{
Family: ArrayFamily, ArrayContents: Jsonb, Oid: oid.T__jsonb, Locale: &emptyLocale}}
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
Expand Down Expand Up @@ -1366,12 +1370,19 @@ func (t *T) WithoutTypeModifiers() *T {

typ, ok := OidToType[t.Oid()]
if !ok {
// These special cases for json, json[] and jsonb[] is here so we can
// support decoding parameters with oid=json/json[]/jsonb[] 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 JSONArray
}
if t.Oid() == oid.T__json {
return JSONBArray
}
panic(errors.AssertionFailedf("unexpected OID: %d", t.Oid()))
}
return typ
Expand Down

0 comments on commit 01ac0df

Please sign in to comment.