Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pkg/sessionctx/variable/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -2133,7 +2133,7 @@ func (p *PlanCacheParamList) String() string {
p.forNonPrepCache { // hide non-prep parameter values by default
return ""
}
return " [arguments: " + types.DatumsToStrNoErr(p.paramValues) + "]"
return " [arguments: " + types.DatumsToStrNoErrSmart(p.paramValues) + "]"
}

// Append appends a parameter value to the PlanCacheParams.
Expand Down
47 changes: 44 additions & 3 deletions pkg/types/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
"unsafe"

Expand Down Expand Up @@ -2420,8 +2421,34 @@ func SortDatums(ctx Context, datums []Datum) error {
return err
}

// Check if a string is considered printable
//
// Checks
// 1. Must be valid UTF-8
// 2. Must not contain control characters like NUL (0x0) and backspace (0x8)
func isPrintable(s string) bool {
if !utf8.ValidString(s) {
return false
}
for _, r := range s {
if unicode.IsControl(r) {
return false
}
}
return true
}

// DatumsToString converts several datums to formatted string.
func DatumsToString(datums []Datum, handleSpecialValue bool) (string, error) {
return datumsToString(datums, handleSpecialValue, false)
}

// DatumsToStringSmart is like DatumsToString, but with smart detection of non-printable data
func DatumsToStringSmart(datums []Datum, handleSpecialValue bool) (string, error) {
return datumsToString(datums, handleSpecialValue, true)
}

func datumsToString(datums []Datum, handleSpecialValue bool, binaryAsHex bool) (string, error) {
n := len(datums)
builder := &strings.Builder{}
builder.Grow(8 * n)
Expand Down Expand Up @@ -2456,9 +2483,14 @@ func DatumsToString(datums []Datum, handleSpecialValue bool) (string, error) {
str = str[:logDatumLen]
}
if datum.Kind() == KindString {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the type of binary is string, not KindBytes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to the response that we send for the prepare statement. We set the collation to utf8mb4_bin with no binary flag. We might be able to improve this by changing the response we send for the prepare call.

builder.WriteString(`"`)
builder.WriteString(str)
builder.WriteString(`"`)
if !binaryAsHex || isPrintable(str) {
builder.WriteString(`"`)
builder.WriteString(str)
builder.WriteString(`"`)
} else {
// Print as hex-literal instead
fmt.Fprintf(builder, "0x%X", str)
}
} else {
builder.WriteString(str)
}
Expand All @@ -2482,6 +2514,15 @@ func DatumsToStrNoErr(datums []Datum) string {
return str
}

// DatumsToStrNoErrSmart converts some datums to a formatted string.
// If an error occurs, it will print a log instead of returning an error.
// It also enables detection of non-pritable arguments
func DatumsToStrNoErrSmart(datums []Datum) string {
str, err := DatumsToStringSmart(datums, true)
terror.Log(errors.Trace(err))
return str
}

// CloneRow deep copies a Datum slice.
func CloneRow(dr []Datum) []Datum {
c := make([]Datum, len(dr))
Expand Down
35 changes: 35 additions & 0 deletions pkg/types/datum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -790,3 +790,38 @@ func BenchmarkDatumTruncatedStringify(b *testing.B) {
_ = d2.TruncatedStringify()
}
}

func TestIsPrintable(t *testing.T) {
testcases := []struct {
input string
valid bool
}{
{string([]byte{0x61, 0x62, 0x63}), true}, // abc
{string([]byte{0x61, 0x0, 0x62, 0x63}), false}, // a<NUL>bc
{string([]byte{0x61, 0x62, 0x63, 0xc3, 0xa9}), true}, // abcé
{string([]byte{0x61, 0x62, 0x63, 0xc3}), false}, // abc<half char>
}

for _, tc := range testcases {
r := isPrintable(tc.input)
require.Equal(t, tc.valid, r, "%v (0x%X) expected printable: %v (but got %v)",
tc.input, tc.input, tc.valid, r)
}
}

func BenchmarkIsPrintable(b *testing.B) {
inputs := []string{
"abc",
"abcé",
string([]byte{0x61, 0x0, 0x62, 0x63}), // broken: contains NUL
string([]byte{0x61, 0x62, 0x63, 0xc3, 0xa9}), // broken: contains half char
strings.Repeat("abc", 1000), // longer string
}
b.ResetTimer()

for i := 0; i < b.N; i++ {
for _, input := range inputs {
isPrintable(input)
}
}
}