Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sql: implement array functions for JSON arrays #81707

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/generated/sql/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
</span></td></tr>
<tr><td><a name="array_append"></a><code>array_append(array: geometry[], elem: geometry) &rarr; geometry[]</code></td><td><span class="funcdesc"><p>Appends <code>elem</code> to <code>array</code>, returning the result.</p>
</span></td></tr>
<tr><td><a name="array_append"></a><code>array_append(array: jsonb, elem: jsonb) &rarr; jsonb</code></td><td><span class="funcdesc"><p>Appends <code>elem</code> to <code>array</code>, returning the result.</p>
</span></td></tr>
<tr><td><a name="array_append"></a><code>array_append(array: jsonb[], elem: jsonb) &rarr; jsonb[]</code></td><td><span class="funcdesc"><p>Appends <code>elem</code> to <code>array</code>, returning the result.</p>
</span></td></tr>
<tr><td><a name="array_append"></a><code>array_append(array: oid[], elem: oid) &rarr; oid[]</code></td><td><span class="funcdesc"><p>Appends <code>elem</code> to <code>array</code>, returning the result.</p>
Expand Down Expand Up @@ -81,6 +83,8 @@
</span></td></tr>
<tr><td><a name="array_cat"></a><code>array_cat(left: geometry[], right: geometry[]) &rarr; geometry[]</code></td><td><span class="funcdesc"><p>Appends two arrays.</p>
</span></td></tr>
<tr><td><a name="array_cat"></a><code>array_cat(left: jsonb, right: jsonb) &rarr; jsonb</code></td><td><span class="funcdesc"><p>Appends two arrays.</p>
</span></td></tr>
<tr><td><a name="array_cat"></a><code>array_cat(left: jsonb[], right: jsonb[]) &rarr; jsonb[]</code></td><td><span class="funcdesc"><p>Appends two arrays.</p>
</span></td></tr>
<tr><td><a name="array_cat"></a><code>array_cat(left: oid[], right: oid[]) &rarr; oid[]</code></td><td><span class="funcdesc"><p>Appends two arrays.</p>
Expand Down Expand Up @@ -129,6 +133,8 @@
</span></td></tr>
<tr><td><a name="array_position"></a><code>array_position(array: geometry[], elem: geometry) &rarr; <a href="int.html">int</a></code></td><td><span class="funcdesc"><p>Return the index of the first occurrence of <code>elem</code> in <code>array</code>.</p>
</span></td></tr>
<tr><td><a name="array_position"></a><code>array_position(array: jsonb, elem: jsonb) &rarr; <a href="int.html">int</a></code></td><td><span class="funcdesc"><p>Return the index of the first occurrence of <code>elem</code> in <code>array</code>.</p>
</span></td></tr>
<tr><td><a name="array_position"></a><code>array_position(array: jsonb[], elem: jsonb) &rarr; <a href="int.html">int</a></code></td><td><span class="funcdesc"><p>Return the index of the first occurrence of <code>elem</code> in <code>array</code>.</p>
</span></td></tr>
<tr><td><a name="array_position"></a><code>array_position(array: oid[], elem: oid) &rarr; <a href="int.html">int</a></code></td><td><span class="funcdesc"><p>Return the index of the first occurrence of <code>elem</code> in <code>array</code>.</p>
Expand Down Expand Up @@ -173,6 +179,8 @@
</span></td></tr>
<tr><td><a name="array_positions"></a><code>array_positions(array: geometry[], elem: geometry) &rarr; <a href="int.html">int</a>[]</code></td><td><span class="funcdesc"><p>Returns and array of indexes of all occurrences of <code>elem</code> in <code>array</code>.</p>
</span></td></tr>
<tr><td><a name="array_positions"></a><code>array_positions(array: jsonb, elem: jsonb) &rarr; <a href="int.html">int</a>[]</code></td><td><span class="funcdesc"><p>Returns and array of indexes of all occurrences of <code>elem</code> in <code>array</code>.</p>
</span></td></tr>
<tr><td><a name="array_positions"></a><code>array_positions(array: jsonb[], elem: jsonb) &rarr; <a href="int.html">int</a>[]</code></td><td><span class="funcdesc"><p>Returns and array of indexes of all occurrences of <code>elem</code> in <code>array</code>.</p>
</span></td></tr>
<tr><td><a name="array_positions"></a><code>array_positions(array: oid[], elem: oid) &rarr; <a href="int.html">int</a>[]</code></td><td><span class="funcdesc"><p>Returns and array of indexes of all occurrences of <code>elem</code> in <code>array</code>.</p>
Expand Down Expand Up @@ -217,6 +225,8 @@
</span></td></tr>
<tr><td><a name="array_prepend"></a><code>array_prepend(elem: geometry, array: geometry[]) &rarr; geometry[]</code></td><td><span class="funcdesc"><p>Prepends <code>elem</code> to <code>array</code>, returning the result.</p>
</span></td></tr>
<tr><td><a name="array_prepend"></a><code>array_prepend(elem: jsonb, array: jsonb) &rarr; jsonb</code></td><td><span class="funcdesc"><p>Prepends <code>elem</code> to <code>array</code>, returning the result.</p>
</span></td></tr>
<tr><td><a name="array_prepend"></a><code>array_prepend(elem: jsonb, array: jsonb[]) &rarr; jsonb[]</code></td><td><span class="funcdesc"><p>Prepends <code>elem</code> to <code>array</code>, returning the result.</p>
</span></td></tr>
<tr><td><a name="array_prepend"></a><code>array_prepend(elem: oid, array: oid[]) &rarr; oid[]</code></td><td><span class="funcdesc"><p>Prepends <code>elem</code> to <code>array</code>, returning the result.</p>
Expand Down Expand Up @@ -261,6 +271,8 @@
</span></td></tr>
<tr><td><a name="array_remove"></a><code>array_remove(array: geometry[], elem: geometry) &rarr; geometry[]</code></td><td><span class="funcdesc"><p>Remove from <code>array</code> all elements equal to <code>elem</code>.</p>
</span></td></tr>
<tr><td><a name="array_remove"></a><code>array_remove(array: jsonb, elem: jsonb) &rarr; jsonb</code></td><td><span class="funcdesc"><p>Remove from <code>array</code> all elements equal to <code>elem</code>.</p>
</span></td></tr>
<tr><td><a name="array_remove"></a><code>array_remove(array: jsonb[], elem: jsonb) &rarr; jsonb[]</code></td><td><span class="funcdesc"><p>Remove from <code>array</code> all elements equal to <code>elem</code>.</p>
</span></td></tr>
<tr><td><a name="array_remove"></a><code>array_remove(array: oid[], elem: oid) &rarr; oid[]</code></td><td><span class="funcdesc"><p>Remove from <code>array</code> all elements equal to <code>elem</code>.</p>
Expand Down Expand Up @@ -305,6 +317,8 @@
</span></td></tr>
<tr><td><a name="array_replace"></a><code>array_replace(array: geometry[], toreplace: geometry, replacewith: geometry) &rarr; geometry[]</code></td><td><span class="funcdesc"><p>Replace all occurrences of <code>toreplace</code> in <code>array</code> with <code>replacewith</code>.</p>
</span></td></tr>
<tr><td><a name="array_replace"></a><code>array_replace(array: jsonb, toreplace: jsonb, replacewith: jsonb) &rarr; jsonb</code></td><td><span class="funcdesc"><p>Replace all occurrences of <code>toreplace</code> in <code>array</code> with <code>replacewith</code>.</p>
</span></td></tr>
<tr><td><a name="array_replace"></a><code>array_replace(array: jsonb[], toreplace: jsonb, replacewith: jsonb) &rarr; jsonb[]</code></td><td><span class="funcdesc"><p>Replace all occurrences of <code>toreplace</code> in <code>array</code> with <code>replacewith</code>.</p>
</span></td></tr>
<tr><td><a name="array_replace"></a><code>array_replace(array: oid[], toreplace: oid, replacewith: oid) &rarr; oid[]</code></td><td><span class="funcdesc"><p>Replace all occurrences of <code>toreplace</code> in <code>array</code> with <code>replacewith</code>.</p>
Expand Down
5 changes: 5 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/array
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,11 @@ SELECT array_remove(NULL::INT[], NULL::INT)
----
NULL

query T
SELECT array_remove('[1,2,3,2]'::jsonb, '2'::jsonb)
----
[1, 3]

# ARRAY_REPLACE function

query T
Expand Down
77 changes: 76 additions & 1 deletion pkg/sql/sem/builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -8008,12 +8008,18 @@ var similarOverloads = []tree.Overload{
}

func arrayBuiltin(impl func(*types.T) tree.Overload) builtinDefinition {
overloads := make([]tree.Overload, 0, len(types.Scalar)+2)
overloads := make([]tree.Overload, 0, len(types.Scalar)+3)
for _, typ := range append(types.Scalar, types.AnyEnum) {
if ok, _ := types.IsValidArrayElementType(typ); ok {
overloads = append(overloads, impl(typ))
}
}

arrayOfJSONImpl := impl(types.Jsonb)
if _, ok := arrayOfJSONImpl.Types.(tree.ArgTypes); ok && arrayOfJSONImpl.Fn != nil {
overloads = append(overloads, makeJSONArrayOverload(arrayOfJSONImpl))
}

// Prevent usage in DistSQL because it cannot handle arrays of untyped tuples.
tupleOverload := impl(types.AnyTuple)
tupleOverload.DistsqlBlocklist = true
Expand All @@ -8024,6 +8030,75 @@ func arrayBuiltin(impl func(*types.T) tree.Overload) builtinDefinition {
}
}

// Convert a json[] function to a json_array function.
func makeJSONArrayOverload(arrayOfJSONImpl tree.Overload) tree.Overload {
isJSONArray := func(t *types.T) bool {
return t != nil && t.ArrayContents() == types.Jsonb
}
castToJSON := func(t *types.T) *types.T {
if isJSONArray(t) {
return types.Jsonb
}
return t
}
newImpl := arrayOfJSONImpl
newImpl.Types = make(tree.ArgTypes, len(arrayOfJSONImpl.Types.(tree.ArgTypes)))
argsToCast := make([]int, 0)
// TODO: Don't panic if it's not an ArgTypes
for i, t := range arrayOfJSONImpl.Types.(tree.ArgTypes) {
if isJSONArray(t.Typ) {
argsToCast = append(argsToCast, i)
newImpl.Types.(tree.ArgTypes)[i] = struct {
Name string
Typ *types.T
}{t.Name, types.Jsonb}
} else {
newImpl.Types.(tree.ArgTypes)[i] = t
}
}
newImpl.ReturnType = func(args []tree.TypedExpr) *types.T {
return castToJSON(arrayOfJSONImpl.ReturnType(args))
}
newImpl.Fn = func(ctx *eval.Context, args tree.Datums) (tree.Datum, error) {
newArgs := append(tree.Datums{}, args...)
for _, i := range argsToCast {
j := tree.MustBeDJSON(args[i])
if j.Type() != json.ArrayJSONType {
return nil, pgerror.Newf(pgcode.InvalidParameterValue,
"cannot call array function on non-array json value")
}
ary := tree.NewDArray(types.Jsonb)
for idx := 0; idx < j.Len(); idx++ {
json, err := j.FetchValIdx(idx)
if err != nil {
return nil, err
}
d, err := tree.MakeDJSON(json)
if err != nil {
return nil, err
}
if err := ary.Append(d); err != nil {
return nil, err
}
}
newArgs[i] = ary
}
ret, err := arrayOfJSONImpl.Fn.(eval.FnOverload)(ctx, newArgs)
if err == nil && isJSONArray(ret.ResolvedType()) {
dArray := tree.MustBeDArray(ret).Array
builder := json.NewArrayBuilder(len(dArray))
for _, elt := range dArray {
builder.Add(tree.MustBeDJSON(elt).JSON)
}
return tree.NewDJSON(builder.Build()), nil
}
return ret, err
}

return newImpl

}

func setProps(props tree.FunctionProperties, d builtinDefinition) builtinDefinition {
d.props = props
return d
Expand Down