diff --git a/pkg/sql/colexec/colexecbase/BUILD.bazel b/pkg/sql/colexec/colexecbase/BUILD.bazel index 436254f2d494..cc6cdf46d420 100644 --- a/pkg/sql/colexec/colexecbase/BUILD.bazel +++ b/pkg/sql/colexec/colexecbase/BUILD.bazel @@ -39,6 +39,7 @@ go_library( "//pkg/util/uuid", # keep "@com_github_cockroachdb_apd_v3//:apd", # keep "@com_github_cockroachdb_errors//:errors", + "@com_github_cockroachdb_redact//:redact", # keep "@com_github_lib_pq//oid", # keep ], ) diff --git a/pkg/sql/colexec/colexecbase/cast.eg.go b/pkg/sql/colexec/colexecbase/cast.eg.go index 436954686863..3612e97e84b2 100644 --- a/pkg/sql/colexec/colexecbase/cast.eg.go +++ b/pkg/sql/colexec/colexecbase/cast.eg.go @@ -632,7 +632,11 @@ func GetCastOperator( } } } - return nil, errors.Errorf("unhandled cast %s -> %s", fromType.SQLString(), toType.SQLString()) + return nil, errors.Errorf( + "unhandled cast %s -> %s", + fromType.SQLStringForError(), + toType.SQLStringForError(), + ) } func IsCastSupported(fromType, toType *types.T) bool { diff --git a/pkg/sql/colexec/colexecbase/cast_tmpl.go b/pkg/sql/colexec/colexecbase/cast_tmpl.go index dedda4039926..45df1c8d440e 100644 --- a/pkg/sql/colexec/colexecbase/cast_tmpl.go +++ b/pkg/sql/colexec/colexecbase/cast_tmpl.go @@ -182,7 +182,11 @@ func GetCastOperator( // {{end}} } } - return nil, errors.Errorf("unhandled cast %s -> %s", fromType.SQLString(), toType.SQLString()) + return nil, errors.Errorf( + "unhandled cast %s -> %s", + fromType.SQLStringForError(), + toType.SQLStringForError(), + ) } func IsCastSupported(fromType, toType *types.T) bool { diff --git a/pkg/sql/colmem/allocator.go b/pkg/sql/colmem/allocator.go index 869cd981ef2f..e2ef69d7cb1e 100644 --- a/pkg/sql/colmem/allocator.go +++ b/pkg/sql/colmem/allocator.go @@ -440,14 +440,14 @@ func (a *Allocator) MaybeAppendColumn(b coldata.Batch, t *types.T, colIdx int) { // We have a vector with an unexpected type, so we panic. colexecerror.InternalError(errors.AssertionFailedf( "trying to add a column of %s type at index %d but %s vector already present", - t.SQLString(), colIdx, presentType.SQLString(), + t.SQLStringForError(), colIdx, presentType.SQLStringForError(), )) } else if colIdx > width { // We have a batch of unexpected width which indicates an error in the // planning stage. colexecerror.InternalError(errors.AssertionFailedf( "trying to add a column of %s type at index %d but batch has width %d", - t.SQLString(), colIdx, width, + t.SQLStringForError(), colIdx, width, )) } estimatedMemoryUsage := EstimateBatchSizeBytes([]*types.T{t}, desiredCapacity) @@ -648,7 +648,7 @@ func EstimateBatchSizeBytes(vecTypes []*types.T, batchLength int) int64 { // Types that have a statically known size. acc += GetFixedSizeTypeSize(t) default: - colexecerror.InternalError(errors.AssertionFailedf("unhandled type %s", t.SQLString())) + colexecerror.InternalError(errors.AssertionFailedf("unhandled type %s", t.SQLStringForError())) } } // For byte arrays, we initially allocate a constant number of bytes for @@ -689,7 +689,7 @@ func GetFixedSizeTypeSize(t *types.T) (size int64) { case types.IntervalFamily: size = memsize.Duration default: - colexecerror.InternalError(errors.AssertionFailedf("unhandled type %s", t.SQLString())) + colexecerror.InternalError(errors.AssertionFailedf("unhandled type %s", t.SQLStringForError())) } return size } @@ -967,7 +967,11 @@ func (h *SetAccountingHelper) ResetMaybeReallocate( case types.JsonFamily: h.bytesLikeVectors = append(h.bytesLikeVectors, &vecs[vecIdx].JSON().Bytes) default: - colexecerror.InternalError(errors.AssertionFailedf("unexpected bytes-like type: %s", typs[vecIdx].SQLString())) + colexecerror.InternalError( + errors.AssertionFailedf( + "unexpected bytes-like type: %s", typs[vecIdx].SQLStringForError(), + ), + ) } } h.prevBytesLikeTotalSize = h.getBytesLikeTotalSize() diff --git a/pkg/sql/types/BUILD.bazel b/pkg/sql/types/BUILD.bazel index 88b924ad5ad2..b718c4cdea0c 100644 --- a/pkg/sql/types/BUILD.bazel +++ b/pkg/sql/types/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//pkg/util/errorutil/unimplemented", "//pkg/util/protoutil", "@com_github_cockroachdb_errors//:errors", + "@com_github_cockroachdb_redact//:redact", "@com_github_gogo_protobuf//jsonpb", "@com_github_gogo_protobuf//proto", "@com_github_lib_pq//oid", diff --git a/pkg/sql/types/types.go b/pkg/sql/types/types.go index 5815c20a8ed2..2c1376ecccde 100644 --- a/pkg/sql/types/types.go +++ b/pkg/sql/types/types.go @@ -26,6 +26,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" "github.com/cockroachdb/cockroach/pkg/util/protoutil" "github.com/cockroachdb/errors" + "github.com/cockroachdb/redact" "github.com/gogo/protobuf/proto" "github.com/lib/pq/oid" ) @@ -1923,6 +1924,41 @@ func (t *T) SQLString() string { return strings.ToUpper(t.Name()) } +// SQLStringForError returns a version of SQLString that will preserve safe +// information during redaction. It is suitable for usage in error messages. +func (t *T) SQLStringForError() redact.RedactableString { + if t.UserDefined() { + // Show the redacted SQLString output with an un-redacted prefix to indicate + // that the type is user defined (and possibly enum or record). + prefix := "TYPE" + switch t.Family() { + case EnumFamily: + prefix = "ENUM" + case TupleFamily: + prefix = "RECORD" + case ArrayFamily: + prefix = "ARRAY" + } + return redact.Sprintf("USER DEFINED %s: %s", redact.Safe(prefix), t.SQLString()) + } + switch t.Family() { + case EnumFamily, TupleFamily, ArrayFamily: + // These types can be or can contain user-defined types, but the SQLString + // is safe when they are not user-defined. We filtered out the user-defined + // case above. + return redact.Sprint(redact.Safe(t.SQLString())) + case BoolFamily, IntFamily, FloatFamily, DecimalFamily, DateFamily, TimestampFamily, + IntervalFamily, StringFamily, BytesFamily, TimestampTZFamily, CollatedStringFamily, OidFamily, + UnknownFamily, UuidFamily, INetFamily, TimeFamily, JsonFamily, TimeTZFamily, BitFamily, + GeometryFamily, GeographyFamily, Box2DFamily, VoidFamily, EncodedKeyFamily, TSQueryFamily, + TSVectorFamily, AnyFamily: + // These types do not contain other types, and do not require redaction. + return redact.Sprint(redact.SafeString(t.SQLString())) + } + // Default to redaction for unhandled types. + return redact.Sprint(t.SQLString()) +} + // FormatTypeName is an injected dependency from tree to properly format a // type name. The logic for proper formatting lives in the tree package. var FormatTypeName = fallbackFormatTypeName diff --git a/pkg/sql/types/types_test.go b/pkg/sql/types/types_test.go index e9c6aa0a4d64..6c569e3037c7 100644 --- a/pkg/sql/types/types_test.go +++ b/pkg/sql/types/types_test.go @@ -1086,3 +1086,74 @@ func TestEnumWithoutTypeMetaNameDoesNotPanicInSQLString(t *testing.T) { arrayType := MakeArray(typ) require.Equal(t, "@100100[]", arrayType.SQLString()) } + +func TestSQLStringForError(t *testing.T) { + const userDefinedOID = oidext.CockroachPredefinedOIDMax + 500 + userDefinedEnum := MakeEnum(userDefinedOID, userDefinedOID+3) + nonUserDefinedEnum := MakeEnum(500, 503) + userDefinedTuple := &T{ + InternalType: InternalType{ + Family: TupleFamily, Oid: userDefinedOID, TupleContents: []*T{Int}, Locale: &emptyLocale, + }, + TypeMeta: UserDefinedTypeMetadata{Name: &UserDefinedTypeName{Name: "foo"}}, + } + arrayWithUserDefinedContent := MakeArray(userDefinedEnum) + + testCases := []struct { + typ *T + expected string + }{ + { // Case 1: un-redacted + typ: Int, + expected: "INT8", + }, + { // Case 2: un-redacted + typ: Float, + expected: "FLOAT8", + }, + { // Case 3: un-redacted + typ: Decimal, + expected: "DECIMAL", + }, + { // Case 4: un-redacted + typ: String, + expected: "STRING", + }, + { // Case 5: un-redacted + typ: TimestampTZ, + expected: "TIMESTAMPTZ", + }, + { // Case 6: un-redacted + typ: nonUserDefinedEnum, + expected: "@500", + }, + { // Case 7: redacted because user-defined + typ: userDefinedEnum, + expected: "USER DEFINED ENUM: ‹@100500›", + }, + { // Case 8: un-redacted + typ: MakeTuple([]*T{Int, Float}), + expected: "RECORD", + }, + { // Case 9: un-redacted because contents are not visible + typ: MakeTuple([]*T{Int, userDefinedEnum}), + expected: "RECORD", + }, + { // Case 10: redacted because user-defined + typ: userDefinedTuple, + expected: "USER DEFINED RECORD: ‹FOO›", + }, + { // Case 11: un-redacted + typ: MakeArray(Int), + expected: "INT8[]", + }, + { // Case 12: redacted element type + typ: arrayWithUserDefinedContent, + expected: "USER DEFINED ARRAY: ‹@100500[]›", + }, + } + + for i, tc := range testCases { + require.Equalf(t, tc.expected, string(tc.typ.SQLStringForError()), "test case %d", i+1) + } +}