From eb882695d505de9cea71a155dddcc0fd40c376c4 Mon Sep 17 00:00:00 2001 From: Mark Sirek Date: Tue, 24 Jan 2023 18:11:37 -0800 Subject: [PATCH] builtins: array_to_string should traverse nested arrays Fixes #95588 In Postgres, `array_to_string` traverses nested arrays and prints their contents. In CRDB, the nested array structures are printed out. For example, `SELECT array_to_string(ARRAY[ARRAY[ARRAY[5,6], ARRAY[2,3]]], ' ');` CRDB Result: `ARRAY[ARRAY[5:::INT8,6:::INT8],ARRAY[2:::INT8,3:::INT8]]` Postgres Result: `5 6 2 3` This fix brings the behavior of `array_to_string` in line with Postgres, and avoids printing the nested ARRAY structures. Some tools like GoldenGate rely on Postgres-compatible behavior of `array_to_string` for proper functioning. Release note (bug fix): This patch fixes the array_to_string built-in function so that nested arrays are traversed without printing 'ARRAY' at each nesting level. --- .../testdata/logic_test/builtin_function | 12 ++++++++++++ pkg/sql/sem/builtins/builtins.go | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pkg/sql/logictest/testdata/logic_test/builtin_function b/pkg/sql/logictest/testdata/logic_test/builtin_function index 0d5cd8c50020..c9750ebdcd18 100644 --- a/pkg/sql/logictest/testdata/logic_test/builtin_function +++ b/pkg/sql/logictest/testdata/logic_test/builtin_function @@ -2517,6 +2517,18 @@ SELECT array_to_string(NULL, ','), array_to_string(NULL, 'foo', 'zerp') ---- NULL NULL +# Builtin array_to_string should recursively search nested arrays. +query T +SELECT array_to_string(ARRAY[ARRAY[ARRAY[5,6], ARRAY[2,3]], ARRAY[ARRAY[7,8], ARRAY[4,44]]], ' '); +---- +5 6 2 3 7 8 4 44 + +# Builtin array_to_string should recursively search nested arrays. +query T +SELECT array_to_string(ARRAY[(SELECT ARRAY[1,2]::int2vector)],' '); +---- +1 2 + # Examples from https://www.postgresql.org/docs/9.3/functions-string.html#FUNCTIONS-STRING-FORMAT query T SELECT format('Hello %s', 'World') diff --git a/pkg/sql/sem/builtins/builtins.go b/pkg/sql/sem/builtins/builtins.go index acb682d1e16c..e1f33fd7f40d 100644 --- a/pkg/sql/sem/builtins/builtins.go +++ b/pkg/sql/sem/builtins/builtins.go @@ -9857,6 +9857,13 @@ func arrayToString( evalCtx *eval.Context, arr *tree.DArray, delim string, nullStr *string, ) (tree.Datum, error) { f := evalCtx.FmtCtx(tree.FmtArrayToString) + arrayToStringHelper(evalCtx, arr, delim, nullStr, f) + return tree.NewDString(f.CloseAndGetString()), nil +} + +func arrayToStringHelper( + evalCtx *eval.Context, arr *tree.DArray, delim string, nullStr *string, f *tree.FmtCtx, +) { for i := range arr.Array { if arr.Array[i] == tree.DNull { @@ -9865,13 +9872,17 @@ func arrayToString( } f.WriteString(*nullStr) } else { - f.FormatNode(arr.Array[i]) + if nestedArray, ok := arr.Array[i].(*tree.DArray); ok { + // "Unpack" nested arrays to be consistent with postgres. + arrayToStringHelper(evalCtx, nestedArray, delim, nullStr, f) + } else { + f.FormatNode(arr.Array[i]) + } } if i < len(arr.Array)-1 { f.WriteString(delim) } } - return tree.NewDString(f.CloseAndGetString()), nil } // encodeEscape implements the encode(..., 'escape') Postgres builtin. It's