github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/json/json_contains.go (about) 1 // Copyright 2021 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package json 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/dolthub/go-mysql-server/sql" 22 "github.com/dolthub/go-mysql-server/sql/types" 23 ) 24 25 // JSON_CONTAINS(target, candidate[, path]) 26 // 27 // JSONContains indicates by returning 1 or 0 whether a given candidate JSON document is contained within a target JSON 28 // document, or, if a path argument was supplied, whether the candidate is found at a specific path within the target. 29 // Returns NULL if any argument is NULL, or if the path argument does not identify a section of the target document. 30 // An error occurs if target or candidate is not a valid JSON document, or if the path argument is not a valid path 31 // expression or contains a * or ** wildcard. To check only whether any data exists at the path, use 32 // JSON_CONTAINS_PATH() instead. 33 // 34 // The following rules define containment: 35 // - A candidate scalar is contained in a target scalar if and only if they are comparable and are equal. Two scalar 36 // values are comparable if they have the same JSON_TYPE() types, with the exception that values of types INTEGER 37 // and DECIMAL are also comparable to each other. 38 // - A candidate array is contained in a target array if and only if every element in the candidate is contained in 39 // some element of the target. 40 // - A candidate non-array is contained in a target array if and only if the candidate is contained in some element 41 // of the target. 42 // - A candidate object is contained in a target object if and only if for each key in the candidate there is a key 43 // with the same name in the target and the value associated with the candidate key is contained in the value 44 // associated with the target key. 45 // 46 // Otherwise, the candidate value is not contained in the target document. 47 // 48 // https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-contains 49 // TODO: Add multi index optimization -> https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-multi-valued 50 type JSONContains struct { 51 JSONTarget sql.Expression 52 JSONCandidate sql.Expression 53 Path sql.Expression 54 } 55 56 var _ sql.FunctionExpression = (*JSONContains)(nil) 57 var _ sql.CollationCoercible = (*JSONContains)(nil) 58 59 // NewJSONContains creates a new JSONContains function. 60 func NewJSONContains(args ...sql.Expression) (sql.Expression, error) { 61 if len(args) < 2 || len(args) > 3 { 62 return nil, sql.ErrInvalidArgumentNumber.New("JSON_CONTAINS", "2 or 3", len(args)) 63 } 64 65 if len(args) == 2 { 66 return &JSONContains{args[0], args[1], nil}, nil 67 } 68 69 return &JSONContains{args[0], args[1], args[2]}, nil 70 } 71 72 // FunctionName implements sql.FunctionExpression 73 func (j *JSONContains) FunctionName() string { 74 return "json_contains" 75 } 76 77 // Description implements sql.FunctionExpression 78 func (j *JSONContains) Description() string { 79 return "returns whether JSON document contains specific object at path." 80 } 81 82 // IsUnsupported implements sql.UnsupportedFunctionStub 83 func (j JSONContains) IsUnsupported() bool { 84 return false 85 } 86 87 func (j *JSONContains) Resolved() bool { 88 for _, child := range j.Children() { 89 if child != nil && !child.Resolved() { 90 return false 91 } 92 } 93 return true 94 } 95 96 func (j *JSONContains) String() string { 97 children := j.Children() 98 var parts = make([]string, len(children)) 99 100 for i, c := range children { 101 parts[i] = c.String() 102 } 103 104 return fmt.Sprintf("%s(%s)", j.FunctionName(), strings.Join(parts, ",")) 105 } 106 107 func (j *JSONContains) Type() sql.Type { 108 return types.Boolean 109 } 110 111 // CollationCoercibility implements the interface sql.CollationCoercible. 112 func (*JSONContains) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 113 return sql.Collation_binary, 5 114 } 115 116 func (j *JSONContains) IsNullable() bool { 117 return j.JSONTarget.IsNullable() || j.JSONCandidate.IsNullable() || (j.Path != nil && j.Path.IsNullable()) 118 } 119 120 func (j *JSONContains) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 121 target, err := getSearchableJSONVal(ctx, row, j.JSONTarget) 122 if err != nil { 123 return nil, err 124 } 125 if target == nil { 126 return nil, nil 127 } 128 129 candidate, err := getSearchableJSONVal(ctx, row, j.JSONCandidate) 130 if err != nil { 131 return nil, err 132 } 133 if candidate == nil { 134 return nil, nil 135 } 136 137 // If there's a path reevaluate target based off of it 138 if j.Path != nil { 139 // Evaluate the given path if there is one 140 path, err := j.Path.Eval(ctx, row) 141 if err != nil { 142 return nil, err 143 } 144 145 path, _, err = types.LongText.Convert(path) 146 if err != nil { 147 return nil, err 148 } 149 150 extracted, err := types.LookupJSONValue(target, path.(string)) 151 if err != nil { 152 return nil, err 153 } 154 var ok bool 155 target, ok = extracted.(sql.JSONWrapper) 156 if !ok { 157 return nil, nil 158 } 159 if target == nil { 160 return nil, nil 161 } 162 } 163 164 // Now determine whether the candidate value exists in the target 165 return types.ContainsJSON(target.ToInterface(), candidate.ToInterface()) 166 } 167 168 func (j *JSONContains) Children() []sql.Expression { 169 if j.Path != nil { 170 return []sql.Expression{j.JSONTarget, j.JSONCandidate, j.Path} 171 } 172 173 return []sql.Expression{j.JSONTarget, j.JSONCandidate} 174 } 175 176 func (j *JSONContains) WithChildren(children ...sql.Expression) (sql.Expression, error) { 177 if len(j.Children()) != len(children) { 178 return nil, fmt.Errorf("json_contains did not receive the correct amount of args") 179 } 180 181 return NewJSONContains(children...) 182 }