Files
dolthub--doltgresql/server/functions/binary/json.go
T
2026-07-13 12:32:25 +08:00

850 lines
27 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 binary
import (
"fmt"
"strconv"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// makeObjectKeyPath builds a JSON path that selects the given top-level
// object key, quoting and escaping the key so any characters are allowed.
func makeObjectKeyPath(key string) string {
return "$" + objectKeyPathSegment(key)
}
// objectKeyPathSegment builds a single object-key step (e.g. `."key"`), quoting
// and escaping the key so any characters are allowed. Used both on its own and
// when composing a multi-step JSON path.
func objectKeyPathSegment(key string) string {
return `."` + strings.ReplaceAll(key, `"`, `\"`) + `"`
}
// makeArrayIndexPath builds a JSON path that selects the given top-level
// array element by non-negative index.
func makeArrayIndexPath(idx int) string {
return "$" + arrayIndexPathSegment(idx)
}
// arrayIndexPathSegment builds a single array-index step (e.g. `[3]`). Used both
// on its own and when composing a multi-step JSON path.
func arrayIndexPathSegment(idx int) string {
return "[" + strconv.Itoa(idx) + "]"
}
// These functions can be gathered using the following query from a Postgres 15 instance:
// SELECT * FROM pg_operator o WHERE o.oprname = <OPERATOR> ORDER BY o.oprcode::varchar;
// Replace <OPERATOR> with the desired JSON operator
// initJSON registers the functions to the catalog.
func initJSON() {
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractJson, json_array_element)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractJson, jsonb_array_element)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractJson, json_object_field)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractJson, jsonb_object_field)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractText, json_array_element_text)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractText, jsonb_array_element_text)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractText, json_object_field_text)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractText, jsonb_object_field_text)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractPathJson, json_extract_path)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractPathJson, jsonb_extract_path)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractPathText, json_extract_path_text)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONExtractPathText, jsonb_extract_path_text)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONContainsRight, jsonb_contains)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONContainsLeft, jsonb_contained)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONTopLevel, jsonb_exists)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONTopLevelAny, jsonb_exists_any)
framework.RegisterBinaryFunction(framework.Operator_BinaryJSONTopLevelAll, jsonb_exists_all)
framework.RegisterBinaryFunction(framework.Operator_BinaryMinus, jsonb_delete_text)
framework.RegisterBinaryFunction(framework.Operator_BinaryMinus, jsonb_delete_text_array)
framework.RegisterBinaryFunction(framework.Operator_BinaryMinus, jsonb_delete_int32)
}
// toJSONWrapper converts a JSON value (either string or sql.JSONWrapper) to a sql.JSONWrapper.
func toJSONWrapper(ctx *sql.Context, val any) (sql.JSONWrapper, error) {
switch v := val.(type) {
case sql.JSONWrapper:
return v, nil
case sql.StringWrapper:
s, err := v.Unwrap(ctx)
if err != nil {
return nil, err
}
doc, err := pgtypes.JsonB.IoInput(ctx, s)
if err != nil {
return nil, err
}
if doc == nil {
return nil, nil
}
w, ok := doc.(sql.JSONWrapper)
if !ok {
return nil, fmt.Errorf("unexpected type from IoInput: %T", doc)
}
return w, nil
case string:
doc, err := pgtypes.JsonB.IoInput(ctx, v)
if err != nil {
return nil, err
}
if doc == nil {
return nil, nil
}
w, ok := doc.(sql.JSONWrapper)
if !ok {
return nil, fmt.Errorf("unexpected type from IoInput: %T", doc)
}
return w, nil
default:
return nil, fmt.Errorf("unexpected type for JSON operation: %T", val)
}
}
// jsonWrapperElementToText converts a JSON element (sql.JSONWrapper) to its text representation.
// For string values, it returns the raw string without quotes. For other types, it returns the JSON representation.
func jsonWrapperElementToText(ctx *sql.Context, wrapper sql.JSONWrapper) (string, error) {
v, err := wrapper.ToInterface(ctx)
if err != nil {
return "", err
}
if v == nil {
return "", nil
}
if s, ok := v.(string); ok {
return s, nil
}
return types.JSONDocument{Val: v}.JSONString()
}
// json_array_element represents the PostgreSQL function of the same name, taking the same parameters.
var json_array_element = framework.Function2{
Name: "json_array_element",
Return: pgtypes.Json,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Json, pgtypes.Int32},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
newVal, err := toJSONWrapper(ctx, val1)
if err != nil {
return nil, err
}
if newVal == nil {
return nil, nil
}
var unusedTypes [3]*pgtypes.DoltgresType
retVal, err := jsonb_array_element_callable(ctx, unusedTypes, newVal, val2)
if err != nil {
return nil, err
}
if retVal == nil {
return nil, nil
}
return pgtypes.JsonB.IoOutput(ctx, retVal)
},
}
// jsonb_array_element represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_array_element = framework.Function2{
Name: "jsonb_array_element",
Return: pgtypes.JsonB,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Int32},
Strict: true,
Callable: jsonb_array_element_callable,
}
func jsonb_array_element_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
wrapper, ok := val1.(sql.JSONWrapper)
if !ok {
return nil, nil
}
idx := int(val2.(int32))
// Fast path: for a ComparableJSON wrapper backed by an indexed JSON
// array, use Lookup to fetch the element without materializing the
// entire array. Negative indices need the array length, so they fall
// through to the materialized path even on indexed values.
if comparable, ok := wrapper.(types.ComparableJSON); ok {
jt, err := comparable.JsonType(ctx)
if err != nil {
return nil, err
}
if jt != "ARRAY" {
return nil, nil
}
if idx >= 0 {
result, err := types.LookupJSONValue(ctx, wrapper, makeArrayIndexPath(idx))
if err != nil {
return nil, err
}
if result == nil {
return nil, nil
}
return result, nil
}
}
// Materialized fallback: covers wrappers that don't implement
// ComparableJSON (e.g. literal jsonb values) and the negative-index
// case on ComparableJSON arrays.
v, err := wrapper.ToInterface(ctx)
if err != nil {
return nil, err
}
array, ok := v.([]interface{})
if !ok {
return nil, nil
}
if idx < 0 {
idx += len(array)
}
if idx < 0 || idx >= len(array) {
return nil, nil
}
return types.JSONDocument{Val: array[idx]}, nil
}
// json_object_field represents the PostgreSQL function of the same name, taking the same parameters.
var json_object_field = framework.Function2{
Name: "json_object_field",
Return: pgtypes.Json,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Json, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
// TODO: make a bespoke implementation that preserves whitespace
newVal, err := toJSONWrapper(ctx, val1)
if err != nil {
return nil, err
}
if newVal == nil {
return nil, nil
}
var unusedTypes [3]*pgtypes.DoltgresType
retVal, err := jsonb_object_field.Callable(ctx, unusedTypes, newVal, val2)
if err != nil {
return nil, err
}
if retVal == nil {
return nil, nil
}
return pgtypes.JsonB.IoOutput(ctx, retVal)
},
}
// jsonb_object_field represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_object_field = framework.Function2{
Name: "jsonb_object_field",
Return: pgtypes.JsonB,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
wrapper, ok := val1.(sql.JSONWrapper)
if !ok {
return nil, nil
}
key := val2.(string)
// Fast path: for a ComparableJSON wrapper backed by an indexed JSON
// object, use Lookup to fetch the value without materializing the
// entire document.
if isObj, err := isComparableJsonObject(ctx, wrapper); err != nil {
return nil, err
} else if isObj {
result, err := types.LookupJSONValue(ctx, wrapper, makeObjectKeyPath(key))
if err != nil {
return nil, err
}
if result == nil {
return nil, nil
}
return result, nil
}
// Materialized fallback: covers wrappers that don't implement
// ComparableJSON (e.g. literal jsonb values), where the embedded
// jsonpath library has trouble with edge cases like keys that
// contain escaped quotes, or array operands with text paths.
v, err := wrapper.ToInterface(ctx)
if err != nil {
return nil, err
}
obj, ok := v.(map[string]any)
if !ok {
return nil, nil
}
val, ok := obj[key]
if !ok {
return nil, nil
}
return types.JSONDocument{Val: val}, nil
},
}
// json_array_element_text represents the PostgreSQL function of the same name, taking the same parameters.
var json_array_element_text = framework.Function2{
Name: "json_array_element_text",
Return: pgtypes.Text,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Json, pgtypes.Int32},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
// TODO: make a bespoke implementation that preserves whitespace
newVal, err := toJSONWrapper(ctx, val1)
if err != nil {
return nil, err
}
if newVal == nil {
return nil, nil
}
var unusedTypes [3]*pgtypes.DoltgresType
return jsonb_array_element_text.Callable(ctx, unusedTypes, newVal, val2)
},
}
// jsonb_array_element_text represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_array_element_text = framework.Function2{
Name: "jsonb_array_element_text",
Return: pgtypes.Text,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Int32},
Strict: true,
Callable: func(ctx *sql.Context, dt [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
elem, err := jsonb_array_element_callable(ctx, dt, val1, val2)
if err != nil || elem == nil {
return nil, err
}
wrapper, ok := elem.(sql.JSONWrapper)
if !ok {
return nil, nil
}
return jsonWrapperElementToText(ctx, wrapper)
},
}
// json_object_field_text represents the PostgreSQL function of the same name, taking the same parameters.
var json_object_field_text = framework.Function2{
Name: "json_object_field_text",
Return: pgtypes.Text,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Json, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
// TODO: make a bespoke implementation that preserves whitespace
newVal, err := toJSONWrapper(ctx, val1)
if err != nil {
return nil, err
}
if newVal == nil {
return nil, nil
}
var unusedTypes [3]*pgtypes.DoltgresType
return jsonb_object_field_text.Callable(ctx, unusedTypes, newVal, val2)
},
}
// jsonb_object_field_text represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_object_field_text = framework.Function2{
Name: "jsonb_object_field_text",
Return: pgtypes.Text,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, dt [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
elem, err := jsonb_object_field.Callable(ctx, dt, val1, val2)
if err != nil || elem == nil {
return nil, err
}
wrapper, ok := elem.(sql.JSONWrapper)
if !ok {
return nil, nil
}
return jsonWrapperElementToText(ctx, wrapper)
},
}
// json_extract_path represents the PostgreSQL function of the same name, taking the same parameters.
var json_extract_path = framework.Function2{
Name: "json_extract_path",
Return: pgtypes.Json,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Json, pgtypes.TextArray},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
// TODO: make a bespoke implementation that preserves whitespace
newVal, err := toJSONWrapper(ctx, val1)
if err != nil {
return nil, err
}
if newVal == nil {
return nil, nil
}
var unusedTypes [3]*pgtypes.DoltgresType
retVal, err := jsonb_extract_path_callable(ctx, unusedTypes, newVal, val2)
if err != nil {
return nil, err
}
if retVal == nil {
return nil, nil
}
return pgtypes.JsonB.IoOutput(ctx, retVal)
},
}
// jsonb_extract_path represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_extract_path = framework.Function2{
Name: "jsonb_extract_path",
Return: pgtypes.JsonB,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.TextArray},
Strict: true,
Callable: jsonb_extract_path_callable,
}
func jsonb_extract_path_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
cur, ok := val1.(sql.JSONWrapper)
if !ok {
return nil, nil
}
paths := val2.([]interface{})
// Fast path: for an indexed JSON document, build a single JSON path spanning
// every step and resolve it in one traversal via SearchableJSON.Lookup,
if _, ok := cur.(types.SearchableJSON); ok && len(paths) > 0 {
result, authoritative, err := extractJsonPathBySingleLookup(ctx, cur, paths)
if err != nil {
return nil, err
}
if authoritative {
return result, nil
}
}
for _, path := range paths {
if cur == nil {
return nil, nil
}
textPath, ok := path.(string)
if !ok {
return nil, nil
}
next, err := extractOneJsonPathStep(ctx, cur, textPath)
if err != nil {
return nil, err
}
if next == nil {
return nil, nil
}
cur = next
}
return cur, nil
}
// extractJsonPathBySingleLookup resolves returns the path in a JSON document by building a JSON path expression
// from the text slice provided.
// For numeric values, there's ambiguity as to whether they should be interpreted as array indices or object keys,
// so a nil result in that case is not authoritative and the caller should fall back to the step-by-step walk.
func extractJsonPathBySingleLookup(ctx *sql.Context, doc sql.JSONWrapper, paths []interface{}) (sql.JSONWrapper, bool, error) {
var sb strings.Builder
sb.WriteString("$")
ambiguous := false
for _, path := range paths {
var textPath string
switch path := path.(type) {
case string:
textPath = path
case sql.StringWrapper:
var err error
textPath, err = path.Unwrap(ctx)
if err != nil {
return nil, false, err
}
case nil:
return nil, true, nil
}
if idx, err := strconv.Atoi(textPath); err == nil {
// An integer element could be an array index or a numeric object
// key (or a negative index), so the lookup result isn't conclusive.
ambiguous = true
if idx >= 0 {
sb.WriteString(arrayIndexPathSegment(idx))
continue
}
}
sb.WriteString(objectKeyPathSegment(textPath))
}
result, err := types.LookupJSONValue(ctx, doc, sb.String())
if err != nil {
return nil, false, err
}
if result != nil {
// A non-nil result could still be semantically NULL
i, err := result.ToInterface(ctx)
if err != nil {
return nil, false, err
}
if ia, ok := i.([]interface{}); ok && len(ia) == 0 {
return nil, !ambiguous, nil
}
return result, true, nil
}
return nil, !ambiguous, nil
}
// extractOneJsonPathStep performs a single Postgres-style extract step against
// the given JSON wrapper. The text is treated as a key when the wrapper is an
// object and as an integer index when the wrapper is an array. Returns nil if
// the step cannot be resolved (missing key, out-of-range index, scalar
// wrapper, or non-integer text on an array).
func extractOneJsonPathStep(ctx *sql.Context, cur sql.JSONWrapper, textPath string) (sql.JSONWrapper, error) {
// Fast path: use the ComparableJSON.JsonType / SearchableJSON.Lookup
// interfaces to avoid materializing the entire document.
if comparable, ok := cur.(types.ComparableJSON); ok {
jt, err := comparable.JsonType(ctx)
if err != nil {
return nil, err
}
switch jt {
case "OBJECT":
return types.LookupJSONValue(ctx, cur, makeObjectKeyPath(textPath))
case "ARRAY":
idx, err := strconv.Atoi(textPath)
if err != nil {
return nil, nil
}
if idx >= 0 {
return types.LookupJSONValue(ctx, cur, makeArrayIndexPath(idx))
}
// Negative indices count from the end and require knowing the
// array length, so fall through to the materialized path below.
default:
return nil, nil
}
}
// Materialized fallback for wrappers that don't implement ComparableJSON,
// and for negative array indices on ones that do.
v, err := cur.ToInterface(ctx)
if err != nil {
return nil, err
}
switch currentValue := v.(type) {
case map[string]interface{}:
next, ok := currentValue[textPath]
if !ok {
return nil, nil
}
return types.JSONDocument{Val: next}, nil
case []interface{}:
idx, err := strconv.Atoi(textPath)
if err != nil {
return nil, nil
}
if idx < 0 {
idx += len(currentValue)
}
if idx < 0 || idx >= len(currentValue) {
return nil, nil
}
return types.JSONDocument{Val: currentValue[idx]}, nil
default:
return nil, nil
}
}
// json_extract_path_text represents the PostgreSQL function of the same name, taking the same parameters.
var json_extract_path_text = framework.Function2{
Name: "json_extract_path_text",
Return: pgtypes.Text,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Json, pgtypes.TextArray},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
// TODO: make a bespoke implementation that preserves whitespace
newVal, err := toJSONWrapper(ctx, val1)
if err != nil {
return nil, err
}
if newVal == nil {
return nil, nil
}
var unusedTypes [3]*pgtypes.DoltgresType
return jsonb_extract_path_text_callable(ctx, unusedTypes, newVal, val2)
},
}
// jsonb_extract_path_text represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_extract_path_text = framework.Function2{
Name: "jsonb_extract_path_text",
Return: pgtypes.Text,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.TextArray},
Strict: true,
Callable: jsonb_extract_path_text_callable,
}
func jsonb_extract_path_text_callable(ctx *sql.Context, dt [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
elem, err := jsonb_extract_path_callable(ctx, dt, val1, val2)
if err != nil || elem == nil {
return nil, err
}
wrapper, ok := elem.(sql.JSONWrapper)
if !ok {
return nil, nil
}
return jsonWrapperElementToText(ctx, wrapper)
}
// jsonb_contains represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_contains = framework.Function2{
Name: "jsonb_contains",
Return: pgtypes.Bool,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.JsonB},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
return nil, errors.Errorf("JSON contains is not yet supported")
},
}
// jsonb_contained represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_contained = framework.Function2{
Name: "jsonb_contained",
Return: pgtypes.Bool,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.JsonB},
Strict: true,
Callable: func(ctx *sql.Context, dt [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
return jsonb_contains.Callable(ctx, dt, val2, val1)
},
}
// jsonb_exists represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_exists = framework.Function2{
Name: "jsonb_exists",
Return: pgtypes.Bool,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
wrapper, ok := val1.(sql.JSONWrapper)
if !ok {
return false, nil
}
key := val2.(string)
// Fast path: for an indexed JSON object, use Lookup to test for the
// key without materializing the document.
if isObj, err := isComparableJsonObject(ctx, wrapper); err != nil {
return nil, err
} else if isObj {
found, err := types.LookupJSONValue(ctx, wrapper, makeObjectKeyPath(key))
if err != nil {
return nil, err
}
return found != nil, nil
}
value, err := wrapper.ToInterface(ctx)
if err != nil {
return nil, err
}
switch v := value.(type) {
case map[string]interface{}:
_, ok := v[key]
return ok, nil
case []interface{}:
for _, item := range v {
if s, ok := item.(string); ok && s == key {
return true, nil
}
}
return false, nil
case string:
return v == key, nil
default:
return false, nil
}
},
}
// isComparableJsonObject reports whether the JSON wrapper is a JSON object and
// also implements types.ComparableJSON
func isComparableJsonObject(ctx *sql.Context, wrapper sql.JSONWrapper) (bool, error) {
comparable, ok := wrapper.(types.ComparableJSON)
if !ok {
return false, nil
}
jt, err := comparable.JsonType(ctx)
if err != nil {
return false, err
}
return jt == "OBJECT", nil
}
// jsonb_exists_any represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_exists_any = framework.Function2{
Name: "jsonb_exists_any",
Return: pgtypes.Bool,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.TextArray},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
wrapper, ok := val1.(sql.JSONWrapper)
if !ok {
return false, nil
}
keys := val2.([]interface{})
// Fast path: for an indexed JSON object, test each key with Lookup
// instead of materializing the document.
if isObj, err := isComparableJsonObject(ctx, wrapper); err != nil {
return nil, err
} else if isObj {
for _, key := range keys {
found, err := types.LookupJSONValue(ctx, wrapper, makeObjectKeyPath(key.(string)))
if err != nil {
return nil, err
}
if found != nil {
return true, nil
}
}
return false, nil
}
value, err := wrapper.ToInterface(ctx)
if err != nil {
return nil, err
}
switch v := value.(type) {
case map[string]interface{}:
for _, key := range keys {
if _, ok := v[key.(string)]; ok {
return true, nil
}
}
return false, nil
case []interface{}:
for _, key := range keys {
for _, item := range v {
if s, ok := item.(string); ok && s == key.(string) {
return true, nil
}
}
}
return false, nil
case string:
for _, key := range keys {
if v == key.(string) {
return true, nil
}
}
return false, nil
default:
return false, nil
}
},
}
// jsonb_exists_all represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_exists_all = framework.Function2{
Name: "jsonb_exists_all",
Return: pgtypes.Bool,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.TextArray},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
wrapper, ok := val1.(sql.JSONWrapper)
if !ok {
return false, nil
}
keys := val2.([]interface{})
// Fast path: for an indexed JSON object, test each key with Lookup
// instead of materializing the document.
if isObj, err := isComparableJsonObject(ctx, wrapper); err != nil {
return nil, err
} else if isObj {
for _, key := range keys {
found, err := types.LookupJSONValue(ctx, wrapper, makeObjectKeyPath(key.(string)))
if err != nil {
return nil, err
}
if found == nil {
return false, nil
}
}
return true, nil
}
value, err := wrapper.ToInterface(ctx)
if err != nil {
return nil, err
}
switch v := value.(type) {
case map[string]interface{}:
for _, key := range keys {
if _, ok := v[key.(string)]; !ok {
return false, nil
}
}
return true, nil
case []interface{}:
for _, key := range keys {
found := false
for _, item := range v {
if s, ok := item.(string); ok && s == key.(string) {
found = true
break
}
}
if !found {
return false, nil
}
}
return true, nil
case string:
for _, key := range keys {
if v != key.(string) {
return false, nil
}
}
return true, nil
default:
return false, nil
}
},
}
// jsonb_delete_text represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_delete_text = framework.Function2{
Name: "jsonb_delete",
Return: pgtypes.JsonB,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
return nil, errors.Errorf("JSON deletions are not yet supported")
},
}
// jsonb_delete_text_array represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_delete_text_array = framework.Function2{
Name: "jsonb_delete",
Return: pgtypes.JsonB,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.TextArray},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
return nil, errors.Errorf("JSON deletions are not yet supported")
},
}
// jsonb_delete_int32 represents the PostgreSQL function of the same name, taking the same parameters.
var jsonb_delete_int32 = framework.Function2{
Name: "jsonb_delete",
Return: pgtypes.JsonB,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.JsonB, pgtypes.Int32},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
return nil, errors.Errorf("JSON deletions are not yet supported")
},
}