Files
2026-07-13 12:32:25 +08:00

252 lines
7.9 KiB
Go

// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package functions
import (
"fmt"
"unsafe"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/goccy/go-json"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
"github.com/dolthub/doltgresql/utils"
)
// jsonWrapperToFormattedString converts a sql.JSONWrapper to a formatted JSON string with spaces (JSONB format).
func jsonWrapperToFormattedString(ctx *sql.Context, val sql.JSONWrapper) (string, error) {
v, err := val.ToInterface(ctx)
if err != nil {
return "", err
}
return types.JSONDocument{Val: v}.JSONString()
}
// initJson registers the functions to the catalog.
func initJson() {
framework.RegisterFunction(json_in)
framework.RegisterFunction(json_out)
framework.RegisterFunction(json_recv)
framework.RegisterFunction(json_send)
framework.RegisterFunction(json_build_array)
framework.RegisterFunction(json_build_object)
}
// json_in represents the PostgreSQL function of json type IO input.
var json_in = framework.Function1{
Name: "json_in",
Return: pgtypes.Json,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Cstring},
Strict: true,
Callable: json_in_callable,
}
func json_in_callable(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
input := val.(string)
var jsonVal any
err := json.Unmarshal(unsafe.Slice(unsafe.StringData(input), len(input)), &jsonVal)
if err != nil {
if len(input) > 10 {
input = input[:10] + "..."
}
return nil, pgtypes.ErrInvalidSyntaxForType.New("json", input)
}
return types.JSONDocument{Val: jsonVal}, nil
}
// json_out represents the PostgreSQL function of json type IO output.
var json_out = framework.Function1{
Name: "json_out",
Return: pgtypes.Cstring,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Json},
Strict: true,
Callable: json_out_callable,
}
func json_out_callable(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
// Large values stored with the old ExtendedAdaptiveEnc encoding are returned as a
// *val.ExtendedValueWrapper (sql.AnyWrapper). Unwrap to get the underlying JSONDocument.
if wrapper, ok := val.(sql.AnyWrapper); ok {
unwrapped, err := wrapper.UnwrapAny(ctx)
if err != nil {
return nil, err
}
val = unwrapped
}
switch v := val.(type) {
case string:
return v, nil
case types.JSONBytes:
bytes, err := v.GetBytes(ctx)
if err != nil {
return nil, err
}
return string(bytes), err
case sql.JSONWrapper:
// JSON type is stored as binary JSON (same as JSONB), so output is normalized with spaces
return jsonWrapperToFormattedString(ctx, v)
default:
return nil, fmt.Errorf("unexpected type for json_out: %T", val)
}
}
// JsonOutCallable is exported so that tests outside this package can exercise the json output
// function directly without going through the global function registry.
var JsonOutCallable = json_out_callable
// JsonbOutCallable is exported so that tests outside this package can exercise the jsonb output
// function directly without going through the global function registry.
var JsonbOutCallable = jsonb_out_callable
// jsonb_out_callable formats a JSONB value for output. JSONB normalizes JSON with spaces after ':' and ','.
func jsonb_out_callable(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
// Large values stored with the old ExtendedAdaptiveEnc encoding are returned as a
// *val.ExtendedValueWrapper (sql.AnyWrapper). Unwrap to get the underlying JSONDocument.
if wrapper, ok := val.(sql.AnyWrapper); ok {
unwrapped, err := wrapper.UnwrapAny(ctx)
if err != nil {
return nil, err
}
val = unwrapped
}
switch v := val.(type) {
case string:
// Parse and reformat the string as proper JSONB (normalized with spaces)
doc, err := pgtypes.JsonB.IoInput(ctx, v)
if err != nil {
return nil, err
}
if doc == nil {
return nil, nil
}
return jsonWrapperToFormattedString(ctx, doc.(sql.JSONWrapper))
case sql.JSONWrapper:
return jsonWrapperToFormattedString(ctx, v)
default:
return nil, fmt.Errorf("unexpected type for jsonb_out: %T", val)
}
}
// json_recv represents the PostgreSQL function of json type IO receive.
var json_recv = framework.Function1{
Name: "json_recv",
Return: pgtypes.Json,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Internal},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
data := val.([]byte)
if data == nil {
return nil, nil
}
return json_in_callable(ctx, [2]*pgtypes.DoltgresType{}, string(data))
},
}
// json_send represents the PostgreSQL function of json type IO send.
var json_send = framework.Function1{
Name: "json_send",
Return: pgtypes.Bytea,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Json},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
if wrapper, ok := val.(sql.AnyWrapper); ok {
var err error
val, err = wrapper.UnwrapAny(ctx)
if err != nil {
return nil, err
}
if val == nil {
return nil, nil
}
}
writer := utils.NewWireWriter()
var jsonStr string
switch v := val.(type) {
case string:
jsonStr = v
case sql.JSONWrapper:
var err error
jsonStr, err = jsonWrapperToFormattedString(ctx, v)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unexpected type for json_send: %T", val)
}
writer.WriteString(jsonStr)
return writer.BufferData(), nil
},
}
// json_build_array represents the PostgreSQL function json_build_array.
var json_build_array = framework.Function1{
Name: "json_build_array",
Return: pgtypes.Json,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.AnyArray},
Variadic: true,
Callable: json_build_array_callable,
}
func json_build_array_callable(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val1 any) (any, error) {
inputArray := val1.([]any)
return types.JSONDocument{Val: inputArray}, nil
}
// json_build_object represents the PostgreSQL function json_build_object.
var json_build_object = framework.Function1{
Name: "json_build_object",
Return: pgtypes.Json,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.AnyArray},
Variadic: true,
Callable: json_build_object_callable,
}
func json_build_object_callable(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
}
return 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) (types.JSONDocument, error) {
inputArray := val1.([]any)
if len(inputArray)%2 != 0 {
return types.JSONDocument{}, 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 = ""
}
}
return types.JSONDocument{Val: jsonObject}, nil
}