diff --git a/server/functions/json.go b/server/functions/json.go index b2774c2e1c..2035c11186 100644 --- a/server/functions/json.go +++ b/server/functions/json.go @@ -106,30 +106,38 @@ var json_build_object = framework.Function1{ Return: pgtypes.Json, Parameters: [1]*pgtypes.DoltgresType{pgtypes.AnyArray}, Variadic: true, - Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val1 any) (any, error) { - inputArray := val1.([]any) - if len(inputArray)%2 != 0 { - return nil, sql.ErrInvalidArgumentNumber.New("json_build_object", "even number of arguments", len(inputArray)) + Callable: func(ctx *sql.Context, argTypes [2]*pgtypes.DoltgresType, val1 any) (any, error) { + json, err := buildJsonObject("json_build_object", argTypes, val1) + if err != nil { + return nil, err } - jsonObject := make(map[string]any) - var key string - for i, e := range inputArray { - if i%2 == 0 { - var ok bool - key, ok = e.(string) - if !ok { - // TODO: This isn't correct for every type we might use as a value. To get better type info to transform - // every value into its string format, we need to pass detailed arg type info for the vararg params (the - // unused param in the function call). - key = fmt.Sprintf("%v", e) - } - } else { - jsonObject[key] = e - key = "" + return string(json), nil + }, +} + +// buildJsonObject constructs a json object from the input array provided, which are alternating keys and values. +func buildJsonObject(fnName string, _ [2]*pgtypes.DoltgresType, val1 any) ([]byte, error) { + inputArray := val1.([]any) + if len(inputArray)%2 != 0 { + return nil, sql.ErrInvalidArgumentNumber.New(fnName, "even number of arguments", len(inputArray)) + } + jsonObject := make(map[string]any) + var key string + for i, e := range inputArray { + if i%2 == 0 { + var ok bool + key, ok = e.(string) + if !ok { + // TODO: This isn't correct for every type we might use as a value. To get better type info to transform + // every value into its string format, we need to pass detailed arg type info for the vararg params (the + // unused param in the function call). + key = fmt.Sprintf("%v", e) } + } else { + jsonObject[key] = e + key = "" } + } - json, err := json.Marshal(jsonObject) - return string(json), err - }, + return json.Marshal(jsonObject) } diff --git a/server/functions/jsonb.go b/server/functions/jsonb.go index 302c4cecb5..73d9a97c96 100644 --- a/server/functions/jsonb.go +++ b/server/functions/jsonb.go @@ -33,6 +33,9 @@ func initJsonB() { framework.RegisterFunction(jsonb_recv) framework.RegisterFunction(jsonb_send) framework.RegisterFunction(jsonb_cmp) + framework.RegisterFunction(jsonb_build_array) + framework.RegisterFunction(jsonb_build_object) + } // jsonb_in represents the PostgreSQL function of jsonb type IO input. @@ -108,3 +111,46 @@ var jsonb_cmp = framework.Function2{ return int32(pgtypes.JsonValueCompare(ab.Value, bb.Value)), nil }, } + +// jsonb_build_array represents the PostgreSQL function jsonb_build_array. +var jsonb_build_array = framework.Function1{ + Name: "jsonb_build_array", + Return: pgtypes.JsonB, + Parameters: [1]*pgtypes.DoltgresType{pgtypes.AnyArray}, + Variadic: true, + Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val1 any) (any, error) { + inputArray := val1.([]any) + json, err := json.Marshal(inputArray) + if err != nil { + return nil, err + } + + jsonDoc, err := pgtypes.UnmarshalToJsonDocument(json) + if err != nil { + return nil, err + } + + return jsonDoc, nil + }, +} + +// jsonb_build_object represents the PostgreSQL function jsonb_build_object. +var jsonb_build_object = framework.Function1{ + Name: "jsonb_build_object", + Return: pgtypes.JsonB, + Parameters: [1]*pgtypes.DoltgresType{pgtypes.AnyArray}, + Variadic: true, + Callable: func(ctx *sql.Context, argTypes [2]*pgtypes.DoltgresType, val1 any) (any, error) { + json, err := buildJsonObject("jsonb_build_object", argTypes, val1) + if err != nil { + return nil, err + } + + jsonDoc, err := pgtypes.UnmarshalToJsonDocument(json) + if err != nil { + return nil, err + } + + return jsonDoc, nil + }, +} diff --git a/testing/go/functions_test.go b/testing/go/functions_test.go index 00c3e768b2..49599a5f35 100644 --- a/testing/go/functions_test.go +++ b/testing/go/functions_test.go @@ -917,7 +917,7 @@ func TestSystemInformationFunctions(t *testing.T) { // TODO: ALTER SEQUENCE OWNED BY is not supported yet. When the sequence is created // explicitly, separate from the column, the owner must be udpated before // pg_get_serial_sequence() will identify it. - //`ALTER SEQUENCE t2_id_seq OWNED BY t2.id;`, + // `ALTER SEQUENCE t2_id_seq OWNED BY t2.id;`, }, Assertions: []ScriptTestAssertion{ { @@ -1004,6 +1004,46 @@ func TestJsonFunctions(t *testing.T) { }, }, }, + { + Name: "jsonb_build_array", + Assertions: []ScriptTestAssertion{ + { + Query: `SELECT jsonb_build_array(1, 2, 3);`, + Cols: []string{"jsonb_build_array"}, + Expected: []sql.Row{{`[1, 2, 3]`}}, + }, + { + Query: `SELECT jsonb_build_array(1, '2', 3);`, + Cols: []string{"jsonb_build_array"}, + Expected: []sql.Row{{`[1, "2", 3]`}}, + }, + { + Query: `SELECT jsonb_build_array();`, + Skip: true, // variadic functions can't handle 0 arguments right now + Cols: []string{"jsonb_build_array"}, + Expected: []sql.Row{{`[]`}}, + }, + }, + }, + { + Name: "jsonb_build_object", + Assertions: []ScriptTestAssertion{ + { + Query: `SELECT jsonb_build_object('a', 2, 'b', 4);`, + Cols: []string{"jsonb_build_object"}, + Expected: []sql.Row{{`{"a": 2, "b": 4}`}}, + }, + { + Query: `SELECT jsonb_build_object('a', 2, 'b');`, + ExpectedErr: "even number", + }, + { + Query: `SELECT jsonb_build_object(1, 2, 'b', 3);`, + Cols: []string{"jsonb_build_object"}, + Expected: []sql.Row{{`{"1": 2, "b": 3}`}}, + }, + }, + }, }) }