| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 |
- package executor
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "strconv"
- "strings"
- )
- // jsonText is a JSON value produced by a JSON1 function. It carries the JSON
- // subtype across nested function calls (so json_insert can embed the result of
- // json() verbatim) and is normalized to a plain string before it is stored.
- type jsonText string
- // jsonEncode marshals a decoded JSON tree without HTML escaping.
- func jsonEncode(v interface{}) (string, error) {
- var buf bytes.Buffer
- enc := json.NewEncoder(&buf)
- enc.SetEscapeHTML(false)
- if err := enc.Encode(v); err != nil {
- return "", err
- }
- return strings.TrimRight(buf.String(), "\n"), nil
- }
- // jsonDecode parses JSON text, preserving number precision with json.Number.
- func jsonDecode(s string) (interface{}, error) {
- dec := json.NewDecoder(strings.NewReader(s))
- dec.UseNumber()
- var v interface{}
- if err := dec.Decode(&v); err != nil {
- return nil, fmt.Errorf("malformed JSON")
- }
- // Reject trailing content, matching JSON1's strict parsing.
- if dec.More() {
- return nil, fmt.Errorf("malformed JSON")
- }
- return v, nil
- }
- // jsonInput resolves a JSON1 input argument to a decoded tree.
- func jsonInput(v interface{}) (interface{}, error) {
- switch t := v.(type) {
- case nil:
- return nil, nil
- case jsonText:
- return jsonDecode(string(t))
- case string:
- return jsonDecode(t)
- case []byte:
- return jsonDecode(string(t))
- default:
- return nil, fmt.Errorf("malformed JSON")
- }
- }
- // jsonArgToNode converts a SQL value into a JSON tree node for embedding.
- func jsonArgToNode(v interface{}) interface{} {
- switch t := v.(type) {
- case nil:
- return nil
- case jsonText:
- if node, err := jsonDecode(string(t)); err == nil {
- return node
- }
- return string(t)
- case bool:
- return t
- case int:
- return json.Number(strconv.FormatInt(int64(t), 10))
- case int64:
- return json.Number(strconv.FormatInt(t, 10))
- case uint64:
- return json.Number(strconv.FormatUint(t, 10))
- case float64:
- return t
- case string:
- return t
- case []byte:
- return string(t)
- default:
- return fmt.Sprintf("%v", t)
- }
- }
- // jsonNodeToSQL converts a JSON tree node to the SQL value json_extract returns.
- func jsonNodeToSQL(v interface{}) interface{} {
- switch t := v.(type) {
- case nil:
- return nil
- case json.Number:
- if i, err := t.Int64(); err == nil {
- return i
- }
- f, _ := t.Float64()
- return f
- case bool:
- if t {
- return int64(1)
- }
- return int64(0)
- case string:
- return t
- case []interface{}, map[string]interface{}:
- s, err := jsonEncode(t)
- if err != nil {
- return nil
- }
- return jsonText(s)
- default:
- return t
- }
- }
- // jsonPathSeg is one component of a parsed JSON path.
- type jsonPathSeg struct {
- key string
- isIndex bool
- index int
- appendOp bool // [#] — append for insert/set
- fromEnd bool // negative index
- }
- // parseJSONPath parses a JSON1 path expression such as $.a.b[0] or $[#].
- func parseJSONPath(path string) ([]jsonPathSeg, error) {
- if !strings.HasPrefix(path, "$") {
- return nil, fmt.Errorf("JSON path error: %s", path)
- }
- var segs []jsonPathSeg
- i := 1
- for i < len(path) {
- switch path[i] {
- case '.':
- i++
- if i >= len(path) {
- return nil, fmt.Errorf("JSON path error: %s", path)
- }
- if path[i] == '"' {
- end := strings.IndexByte(path[i+1:], '"')
- if end < 0 {
- return nil, fmt.Errorf("JSON path error: %s", path)
- }
- segs = append(segs, jsonPathSeg{key: path[i+1 : i+1+end]})
- i += end + 2
- continue
- }
- start := i
- for i < len(path) && path[i] != '.' && path[i] != '[' {
- i++
- }
- segs = append(segs, jsonPathSeg{key: path[start:i]})
- case '[':
- end := strings.IndexByte(path[i:], ']')
- if end < 0 {
- return nil, fmt.Errorf("JSON path error: %s", path)
- }
- inner := path[i+1 : i+end]
- i += end + 1
- if inner == "#" {
- segs = append(segs, jsonPathSeg{isIndex: true, appendOp: true})
- continue
- }
- if strings.HasPrefix(inner, "\"") && strings.HasSuffix(inner, "\"") && len(inner) >= 2 {
- segs = append(segs, jsonPathSeg{key: inner[1 : len(inner)-1]})
- continue
- }
- n, err := strconv.Atoi(inner)
- if err != nil {
- return nil, fmt.Errorf("JSON path error: %s", path)
- }
- segs = append(segs, jsonPathSeg{isIndex: true, index: n, fromEnd: n < 0})
- default:
- return nil, fmt.Errorf("JSON path error: %s", path)
- }
- }
- return segs, nil
- }
- // jsonLookup walks a decoded tree to the node addressed by segs.
- func jsonLookup(root interface{}, segs []jsonPathSeg) (interface{}, bool) {
- cur := root
- for _, seg := range segs {
- if seg.isIndex {
- arr, ok := cur.([]interface{})
- if !ok {
- return nil, false
- }
- idx := seg.index
- if seg.fromEnd {
- idx = len(arr) + idx
- }
- if idx < 0 || idx >= len(arr) {
- return nil, false
- }
- cur = arr[idx]
- continue
- }
- obj, ok := cur.(map[string]interface{})
- if !ok {
- return nil, false
- }
- val, ok := obj[seg.key]
- if !ok {
- return nil, false
- }
- cur = val
- }
- return cur, true
- }
- // jsonApplyPatch applies json_set/insert/replace mutations to a decoded tree.
- // mode is "set", "insert", or "replace".
- func jsonApplyPatch(root interface{}, segs []jsonPathSeg, value interface{}, mode string) (interface{}, error) {
- if len(segs) == 0 {
- return value, nil
- }
- seg := segs[0]
- last := len(segs) == 1
- childSegs := segs[1:]
- if seg.isIndex {
- arr, ok := root.([]interface{})
- if !ok {
- // A missing container is created only by json_set/json_insert.
- if mode == "replace" {
- return root, nil
- }
- arr = []interface{}{}
- }
- idx := seg.index
- if seg.appendOp {
- if last {
- return append(arr, value), nil
- }
- child, childOK := interface{}(nil), false
- _ = child
- _ = childOK
- // append a new container for nested paths
- container := newJSONContainer(childSegs[0])
- newChild, err := jsonApplyPatch(container, childSegs, value, mode)
- if err != nil {
- return nil, err
- }
- return append(arr, newChild), nil
- }
- if seg.fromEnd {
- idx = len(arr) + idx
- }
- if idx < 0 || idx > len(arr) {
- return root, nil
- }
- if last {
- if idx == len(arr) {
- if mode == "replace" {
- return root, nil
- }
- return append(arr, value), nil
- }
- replaced := append([]interface{}(nil), arr...)
- replaced[idx] = value
- return replaced, nil
- }
- if idx == len(arr) {
- if mode == "replace" {
- return root, nil
- }
- container := newJSONContainer(childSegs[0])
- newChild, err := jsonApplyPatch(container, childSegs, value, mode)
- if err != nil {
- return nil, err
- }
- return append(arr, newChild), nil
- }
- newChild, err := jsonApplyPatch(arr[idx], childSegs, value, mode)
- if err != nil {
- return nil, err
- }
- replaced := append([]interface{}(nil), arr...)
- replaced[idx] = newChild
- return replaced, nil
- }
- obj, ok := root.(map[string]interface{})
- if !ok {
- if mode == "replace" {
- return root, nil
- }
- obj = map[string]interface{}{}
- }
- if last {
- _, exists := obj[seg.key]
- if exists && mode == "insert" {
- return root, nil
- }
- if !exists && mode == "replace" {
- return root, nil
- }
- newObj := make(map[string]interface{}, len(obj)+1)
- for k, v := range obj {
- newObj[k] = v
- }
- newObj[seg.key] = value
- return newObj, nil
- }
- child, exists := obj[seg.key]
- if !exists {
- if mode == "replace" {
- return root, nil
- }
- child = newJSONContainer(childSegs[0])
- }
- newChild, err := jsonApplyPatch(child, childSegs, value, mode)
- if err != nil {
- return nil, err
- }
- newObj := make(map[string]interface{}, len(obj)+1)
- for k, v := range obj {
- newObj[k] = v
- }
- newObj[seg.key] = newChild
- return newObj, nil
- }
- func newJSONContainer(seg jsonPathSeg) interface{} {
- if seg.isIndex {
- return []interface{}{}
- }
- return map[string]interface{}{}
- }
- // jsonTypeName returns the JSON1 type name for a decoded node.
- func jsonTypeName(v interface{}) string {
- switch v.(type) {
- case nil:
- return "null"
- case bool:
- return "false" // caller distinguishes true below
- case json.Number:
- s := string(v.(json.Number))
- if !strings.ContainsAny(s, ".eE") {
- return "integer"
- }
- return "real"
- case string:
- return "text"
- case []interface{}:
- return "array"
- case map[string]interface{}:
- return "object"
- default:
- return "null"
- }
- }
- // evalJSONFunction evaluates a JSON1 scalar function. It returns handled=false
- // for names it does not own.
- func evalJSONFunction(name string, args []interface{}) (interface{}, bool, error) {
- upper := strings.ToUpper(name)
- switch upper {
- case "JSON", "JSONB":
- if len(args) != 1 {
- return nil, true, fmt.Errorf("wrong number of arguments to %s()", name)
- }
- if args[0] == nil {
- return nil, true, nil
- }
- node, err := jsonInput(args[0])
- if err != nil {
- return nil, true, err
- }
- s, err := jsonEncode(node)
- if err != nil {
- return nil, true, err
- }
- return jsonText(s), true, nil
- case "JSON_VALID", "JSONB_VALID":
- if len(args) != 1 {
- return nil, true, fmt.Errorf("wrong number of arguments to %s()", name)
- }
- if _, err := jsonInput(args[0]); err != nil {
- return int64(0), true, nil
- }
- return int64(1), true, nil
- case "JSON_TYPE":
- if len(args) < 1 || len(args) > 2 {
- return nil, true, fmt.Errorf("wrong number of arguments to json_type()")
- }
- if args[0] == nil {
- return nil, true, nil
- }
- node, err := jsonInput(args[0])
- if err != nil {
- return nil, true, err
- }
- if len(args) == 2 {
- path, ok := args[1].(string)
- if !ok {
- if jt, is := args[1].(jsonText); is {
- path = string(jt)
- } else {
- return nil, true, fmt.Errorf("JSON path error")
- }
- }
- segs, perr := parseJSONPath(path)
- if perr != nil {
- return nil, true, perr
- }
- found, ok := jsonLookup(node, segs)
- if !ok {
- return nil, true, nil
- }
- node = found
- }
- if b, isBool := node.(bool); isBool {
- if b {
- return "true", true, nil
- }
- return "false", true, nil
- }
- return jsonTypeName(node), true, nil
- case "JSON_EXTRACT", "JSONB_EXTRACT":
- if len(args) < 2 {
- return nil, true, fmt.Errorf("wrong number of arguments to json_extract()")
- }
- if args[0] == nil {
- return nil, true, nil
- }
- node, err := jsonInput(args[0])
- if err != nil {
- return nil, true, err
- }
- for _, p := range args[1:] {
- path, ok := jsonPathArg(p)
- if !ok {
- return nil, true, fmt.Errorf("JSON path error")
- }
- segs, perr := parseJSONPath(path)
- if perr != nil {
- return nil, true, perr
- }
- found, ok := jsonLookup(node, segs)
- if !ok {
- return nil, true, nil
- }
- node = found
- }
- return jsonNodeToSQL(node), true, nil
- case "JSON_SET", "JSONB_SET", "JSON_INSERT", "JSONB_INSERT", "JSON_REPLACE", "JSONB_REPLACE":
- if len(args) < 3 || len(args)%2 == 0 {
- return nil, true, fmt.Errorf("wrong number of arguments to %s()", name)
- }
- if args[0] == nil {
- return nil, true, nil
- }
- mode := "set"
- if strings.Contains(upper, "INSERT") {
- mode = "insert"
- } else if strings.Contains(upper, "REPLACE") {
- mode = "replace"
- }
- node, err := jsonInput(args[0])
- if err != nil {
- return nil, true, err
- }
- for i := 1; i+1 < len(args); i += 2 {
- path, ok := jsonPathArg(args[i])
- if !ok {
- return nil, true, fmt.Errorf("JSON path error")
- }
- segs, perr := parseJSONPath(path)
- if perr != nil {
- return nil, true, perr
- }
- node, err = jsonApplyPatch(node, segs, jsonArgToNode(args[i+1]), mode)
- if err != nil {
- return nil, true, err
- }
- }
- s, err := jsonEncode(node)
- if err != nil {
- return nil, true, err
- }
- return jsonText(s), true, nil
- case "JSON_REMOVE", "JSONB_REMOVE":
- if len(args) < 2 {
- return nil, true, fmt.Errorf("wrong number of arguments to json_remove()")
- }
- if args[0] == nil {
- return nil, true, nil
- }
- node, err := jsonInput(args[0])
- if err != nil {
- return nil, true, err
- }
- for _, p := range args[1:] {
- path, ok := jsonPathArg(p)
- if !ok {
- return nil, true, fmt.Errorf("JSON path error")
- }
- segs, perr := parseJSONPath(path)
- if perr != nil {
- return nil, true, perr
- }
- node, err = jsonRemove(node, segs)
- if err != nil {
- return nil, true, err
- }
- }
- s, err := jsonEncode(node)
- if err != nil {
- return nil, true, err
- }
- return jsonText(s), true, nil
- case "JSON_ARRAY", "JSONB_ARRAY":
- arr := make([]interface{}, len(args))
- for i, a := range args {
- arr[i] = jsonArgToNode(a)
- }
- s, err := jsonEncode(arr)
- if err != nil {
- return nil, true, err
- }
- return jsonText(s), true, nil
- case "JSON_OBJECT", "JSONB_OBJECT":
- if len(args)%2 != 0 {
- return nil, true, fmt.Errorf("json_object() requires an even number of arguments")
- }
- obj := make(map[string]interface{}, len(args)/2)
- for i := 0; i+1 < len(args); i += 2 {
- key, ok := args[i].(string)
- if !ok {
- if jt, is := args[i].(jsonText); is {
- key = string(jt)
- } else {
- return nil, true, fmt.Errorf("json_object() labels must be TEXT")
- }
- }
- obj[key] = jsonArgToNode(args[i+1])
- }
- s, err := jsonEncode(obj)
- if err != nil {
- return nil, true, err
- }
- return jsonText(s), true, nil
- case "JSON_QUOTE", "JSONB_QUOTE":
- if len(args) != 1 {
- return nil, true, fmt.Errorf("json_quote() requires exactly one argument")
- }
- s, err := jsonEncode(jsonArgToNode(args[0]))
- if err != nil {
- return nil, true, err
- }
- return jsonText(s), true, nil
- }
- return nil, false, nil
- }
- // jsonPathArg extracts a path string from an argument.
- func jsonPathArg(v interface{}) (string, bool) {
- switch t := v.(type) {
- case string:
- return t, true
- case jsonText:
- return string(t), true
- default:
- return "", false
- }
- }
- // jsonRemove removes the node addressed by segs from a decoded tree.
- func jsonRemove(root interface{}, segs []jsonPathSeg) (interface{}, error) {
- if len(segs) == 0 {
- return root, nil
- }
- seg := segs[0]
- last := len(segs) == 1
- if seg.isIndex {
- arr, ok := root.([]interface{})
- if !ok {
- return root, nil
- }
- idx := seg.index
- if seg.fromEnd {
- idx = len(arr) + idx
- }
- if idx < 0 || idx >= len(arr) {
- return root, nil
- }
- if last {
- out := make([]interface{}, 0, len(arr)-1)
- out = append(out, arr[:idx]...)
- out = append(out, arr[idx+1:]...)
- return out, nil
- }
- child, err := jsonRemove(arr[idx], segs[1:])
- if err != nil {
- return nil, err
- }
- out := append([]interface{}(nil), arr...)
- out[idx] = child
- return out, nil
- }
- obj, ok := root.(map[string]interface{})
- if !ok {
- return root, nil
- }
- if last {
- out := make(map[string]interface{}, len(obj))
- for k, v := range obj {
- if k != seg.key {
- out[k] = v
- }
- }
- return out, nil
- }
- child, exists := obj[seg.key]
- if !exists {
- return root, nil
- }
- newChild, err := jsonRemove(child, segs[1:])
- if err != nil {
- return nil, err
- }
- out := make(map[string]interface{}, len(obj))
- for k, v := range obj {
- out[k] = v
- }
- out[seg.key] = newChild
- return out, nil
- }
|