379 lines
11 KiB
Go
379 lines
11 KiB
Go
// Copyright 2017 PingCAP, 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,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package expression
|
|
|
|
import (
|
|
"github.com/juju/errors"
|
|
"github.com/pingcap/tidb/ast"
|
|
"github.com/pingcap/tidb/context"
|
|
"github.com/pingcap/tidb/mysql"
|
|
"github.com/pingcap/tidb/sessionctx/variable"
|
|
"github.com/pingcap/tidb/util/types"
|
|
"github.com/pingcap/tidb/util/types/json"
|
|
"github.com/pingcap/tipb/go-tipb"
|
|
)
|
|
|
|
// jsonFunctionNameToPB is for pushdown json functions to storage engine.
|
|
var jsonFunctionNameToPB = map[string]tipb.ExprType{
|
|
ast.JSONType: tipb.ExprType_JsonType,
|
|
ast.JSONExtract: tipb.ExprType_JsonExtract,
|
|
ast.JSONUnquote: tipb.ExprType_JsonUnquote,
|
|
ast.JSONValid: tipb.ExprType_JsonValid,
|
|
ast.JSONObject: tipb.ExprType_JsonObject,
|
|
ast.JSONArray: tipb.ExprType_JsonArray,
|
|
ast.JSONMerge: tipb.ExprType_JsonMerge,
|
|
ast.JSONSet: tipb.ExprType_JsonSet,
|
|
ast.JSONInsert: tipb.ExprType_JsonInsert,
|
|
ast.JSONReplace: tipb.ExprType_JsonReplace,
|
|
ast.JSONRemove: tipb.ExprType_JsonRemove,
|
|
ast.JSONContains: tipb.ExprType_JsonContains,
|
|
}
|
|
|
|
var (
|
|
_ functionClass = &jsonTypeFunctionClass{}
|
|
_ functionClass = &jsonExtractFunctionClass{}
|
|
_ functionClass = &jsonUnquoteFunctionClass{}
|
|
_ functionClass = &jsonSetFunctionClass{}
|
|
_ functionClass = &jsonInsertFunctionClass{}
|
|
_ functionClass = &jsonReplaceFunctionClass{}
|
|
_ functionClass = &jsonMergeFunctionClass{}
|
|
)
|
|
|
|
// argsAnyNull returns true if args contains any null.
|
|
func argsAnyNull(args []types.Datum) bool {
|
|
for _, arg := range args {
|
|
if arg.Kind() == types.KindNull {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// datum2JSON gets or converts to JSON from datum.
|
|
func datum2JSON(d types.Datum, sc *variable.StatementContext) (j json.JSON, err error) {
|
|
tp := types.NewFieldType(mysql.TypeJSON)
|
|
if d, err = d.ConvertTo(sc, tp); err == nil {
|
|
j = d.GetMysqlJSON()
|
|
}
|
|
return j, errors.Trace(err)
|
|
}
|
|
|
|
// parsePathExprs parses strings in datums into json.PathExpression.
|
|
func parsePathExprs(datums []types.Datum) ([]json.PathExpression, error) {
|
|
pathExprs := make([]json.PathExpression, 0, len(datums))
|
|
for _, datum := range datums {
|
|
pathExpr, err := json.ParseJSONPathExpr(datum.GetString())
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
pathExprs = append(pathExprs, pathExpr)
|
|
}
|
|
return pathExprs, nil
|
|
}
|
|
|
|
// createJSONFromDatums creates JSONs from Datums.
|
|
func createJSONFromDatums(datums []types.Datum) ([]json.JSON, error) {
|
|
jsons := make([]json.JSON, 0, len(datums))
|
|
for _, datum := range datums {
|
|
// TODO: Here semantic of creating JSON from datum is the same with types.compareMysqlJSON.
|
|
// But it's different from "CAST semantic" because for string, cast will parse it into
|
|
// JSON but here and compareMysqlJSON needs a JSON with the string as primitives.
|
|
// We should rewrite this as a function for here and compareMysqlJSON both can use it.
|
|
var j json.JSON
|
|
switch datum.Kind() {
|
|
case types.KindNull:
|
|
j = json.CreateJSON(nil)
|
|
case types.KindMysqlJSON:
|
|
j = datum.GetMysqlJSON()
|
|
case types.KindInt64, types.KindUint64:
|
|
j = json.CreateJSON(datum.GetInt64())
|
|
case types.KindFloat32, types.KindFloat64:
|
|
j = json.CreateJSON(datum.GetFloat64())
|
|
case types.KindMysqlDecimal:
|
|
f64, err := datum.GetMysqlDecimal().ToFloat64()
|
|
if err != nil {
|
|
return jsons, errors.Trace(err)
|
|
}
|
|
j = json.CreateJSON(f64)
|
|
case types.KindString, types.KindBytes:
|
|
j = json.CreateJSON(datum.GetString())
|
|
default:
|
|
s, err := datum.ToString()
|
|
if err != nil {
|
|
return jsons, errors.Trace(err)
|
|
}
|
|
j = json.CreateJSON(s)
|
|
}
|
|
jsons = append(jsons, j)
|
|
}
|
|
return jsons, nil
|
|
}
|
|
|
|
// jsonModify is the portal for modify JSON with path expressions and values.
|
|
func jsonModify(args []types.Datum, mt json.ModifyType, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
if argsAnyNull(args) {
|
|
return d, nil
|
|
}
|
|
var j json.JSON
|
|
if j, err = datum2JSON(args[0], sc); err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
|
|
// alloc 1 extra element, for len(args) is an even number.
|
|
pes := make([]types.Datum, 0, (len(args)-1)/2+1)
|
|
vs := make([]types.Datum, 0, (len(args)-1)/2+1)
|
|
for i := 1; i < len(args); i++ {
|
|
if i&1 == 1 {
|
|
pes = append(pes, args[i])
|
|
} else {
|
|
vs = append(vs, args[i])
|
|
}
|
|
}
|
|
pathExprs, err := parsePathExprs(pes)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
values, err := createJSONFromDatums(vs)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
j, err = j.Modify(pathExprs, values, mt)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
d.SetMysqlJSON(j)
|
|
return d, nil
|
|
}
|
|
|
|
// JSONType is for json_type builtin function.
|
|
func JSONType(args []types.Datum, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
if argsAnyNull(args) {
|
|
return d, nil
|
|
}
|
|
djson, err := datum2JSON(args[0], sc)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
d.SetString(djson.Type())
|
|
return
|
|
}
|
|
|
|
// JSONExtract is for json_extract builtin function.
|
|
func JSONExtract(args []types.Datum, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
if argsAnyNull(args) {
|
|
return d, nil
|
|
}
|
|
djson, err := datum2JSON(args[0], sc)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
pathExprs, err := parsePathExprs(args[1:])
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
if djson1, found := djson.Extract(pathExprs); found {
|
|
d.SetMysqlJSON(djson1)
|
|
}
|
|
return
|
|
}
|
|
|
|
// JSONUnquote is for json_unquote builtin function.
|
|
func JSONUnquote(args []types.Datum, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
if argsAnyNull(args) {
|
|
return d, nil
|
|
}
|
|
djson, err := datum2JSON(args[0], sc)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
unquoted, err := djson.Unquote()
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
d.SetString(unquoted)
|
|
return
|
|
}
|
|
|
|
// JSONSet is for json_set builtin function.
|
|
func JSONSet(args []types.Datum, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
return jsonModify(args, json.ModifySet, sc)
|
|
}
|
|
|
|
// JSONInsert is for json_insert builtin function.
|
|
func JSONInsert(args []types.Datum, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
return jsonModify(args, json.ModifyInsert, sc)
|
|
}
|
|
|
|
// JSONReplace is for json_replace builtin function.
|
|
func JSONReplace(args []types.Datum, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
return jsonModify(args, json.ModifyReplace, sc)
|
|
}
|
|
|
|
// JSONMerge is for json_merge builtin function.
|
|
func JSONMerge(args []types.Datum, sc *variable.StatementContext) (d types.Datum, err error) {
|
|
if argsAnyNull(args) {
|
|
return d, nil
|
|
}
|
|
jsons := make([]json.JSON, 0, len(args))
|
|
for _, arg := range args {
|
|
j, err := datum2JSON(arg, sc)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
jsons = append(jsons, j)
|
|
}
|
|
d.SetMysqlJSON(jsons[0].Merge(jsons[1:]))
|
|
return
|
|
}
|
|
|
|
type jsonTypeFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
type builtinJSONTypeSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (c *jsonTypeFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
|
|
return &builtinJSONTypeSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
|
|
}
|
|
|
|
func (b *builtinJSONTypeSig) eval(row []types.Datum) (d types.Datum, err error) {
|
|
args, err := b.evalArgs(row)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
return JSONType(args, b.ctx.GetSessionVars().StmtCtx)
|
|
}
|
|
|
|
type jsonExtractFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
type builtinJSONExtractSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (c *jsonExtractFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
|
|
return &builtinJSONExtractSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
|
|
}
|
|
|
|
func (b *builtinJSONExtractSig) eval(row []types.Datum) (d types.Datum, err error) {
|
|
args, err := b.evalArgs(row)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
return JSONExtract(args, b.ctx.GetSessionVars().StmtCtx)
|
|
}
|
|
|
|
type jsonUnquoteFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
type builtinJSONUnquoteSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (c *jsonUnquoteFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
|
|
return &builtinJSONUnquoteSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
|
|
}
|
|
|
|
func (b *builtinJSONUnquoteSig) eval(row []types.Datum) (d types.Datum, err error) {
|
|
args, err := b.evalArgs(row)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
return JSONUnquote(args, b.ctx.GetSessionVars().StmtCtx)
|
|
}
|
|
|
|
type jsonSetFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
type builtinJSONSetSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (c *jsonSetFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
|
|
return &builtinJSONSetSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
|
|
}
|
|
|
|
func (b *builtinJSONSetSig) eval(row []types.Datum) (d types.Datum, err error) {
|
|
args, err := b.evalArgs(row)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
return JSONSet(args, b.ctx.GetSessionVars().StmtCtx)
|
|
}
|
|
|
|
type jsonInsertFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
type builtinJSONInsertSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (c *jsonInsertFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
|
|
return &builtinJSONInsertSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
|
|
}
|
|
|
|
func (b *builtinJSONInsertSig) eval(row []types.Datum) (d types.Datum, err error) {
|
|
args, err := b.evalArgs(row)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
return JSONInsert(args, b.ctx.GetSessionVars().StmtCtx)
|
|
}
|
|
|
|
type jsonReplaceFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
type builtinJSONReplaceSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (c *jsonReplaceFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
|
|
return &builtinJSONReplaceSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
|
|
}
|
|
|
|
func (b *builtinJSONReplaceSig) eval(row []types.Datum) (d types.Datum, err error) {
|
|
args, err := b.evalArgs(row)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
return JSONReplace(args, b.ctx.GetSessionVars().StmtCtx)
|
|
}
|
|
|
|
type jsonMergeFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
type builtinJSONMergeSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (c *jsonMergeFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
|
|
return &builtinJSONMergeSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
|
|
}
|
|
|
|
func (b *builtinJSONMergeSig) eval(row []types.Datum) (d types.Datum, err error) {
|
|
args, err := b.evalArgs(row)
|
|
if err != nil {
|
|
return d, errors.Trace(err)
|
|
}
|
|
return JSONMerge(args, b.ctx.GetSessionVars().StmtCtx)
|
|
}
|