Skip to content

Commit 5d63698

Browse files
authored
Merge pull request #154406 from spilchen/blathers/backport-release-25.4-154363
release-25.4: sql/types: reintroduce downgradeType for NAME compatibility in mixed clusters
2 parents 217a510 + 2b6b582 commit 5d63698

File tree

5 files changed

+137
-7
lines changed

5 files changed

+137
-7
lines changed

pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,4 @@ SELECT id, strip_volatile(descriptor) FROM crdb_internal.kv_catalog_descriptor W
149149
111 {"table": {"checks": [{"columnIds": [1], "constraintId": 2, "expr": "k > 0:::INT8", "name": "ck"}], "columns": [{"id": 1, "name": "k", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "v", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}], "dependedOnBy": [{"columnIds": [1, 2], "id": 112}], "formatVersion": 3, "id": 111, "name": "kv", "nextColumnId": 3, "nextConstraintId": 3, "nextIndexId": 2, "nextMutationId": 1, "parentId": 106, "primaryIndex": {"constraintId": 1, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [1], "keyColumnNames": ["k"], "name": "kv_pkey", "partitioning": {}, "sharded": {}, "storeColumnIds": [2], "storeColumnNames": ["v"], "unique": true, "vecConfig": {}, "version": 4}, "privileges": {"ownerProto": "root", "users": [{"privileges": "2", "userProto": "admin", "withGrantOption": "2"}, {"privileges": "2", "userProto": "root", "withGrantOption": "2"}], "version": 3}, "replacementOf": {"time": {}}, "schemaLocked": true, "unexposedParentSchemaId": 107, "version": "7"}}
150150
112 {"table": {"columns": [{"id": 1, "name": "k", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "v", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"defaultExpr": "unique_rowid()", "hidden": true, "id": 3, "name": "rowid", "type": {"family": "IntFamily", "oid": 20, "width": 64}}], "dependsOn": [111], "formatVersion": 3, "id": 112, "indexes": [{"createdExplicitly": true, "foreignKey": {}, "geoConfig": {}, "id": 2, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [2], "keyColumnNames": ["v"], "keySuffixColumnIds": [3], "name": "idx", "partitioning": {}, "sharded": {}, "vecConfig": {}, "version": 4}], "isMaterializedView": true, "name": "mv", "nextColumnId": 4, "nextConstraintId": 2, "nextIndexId": 4, "nextMutationId": 1, "parentId": 106, "primaryIndex": {"constraintId": 1, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [3], "keyColumnNames": ["rowid"], "name": "mv_pkey", "partitioning": {}, "sharded": {}, "storeColumnIds": [1, 2], "storeColumnNames": ["k", "v"], "unique": true, "vecConfig": {}, "version": 4}, "privileges": {"ownerProto": "root", "users": [{"privileges": "2", "userProto": "admin", "withGrantOption": "2"}, {"privileges": "2", "userProto": "root", "withGrantOption": "2"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 107, "version": "8", "viewQuery": "SELECT k, v FROM db.public.kv"}}
151151
113 {"function": {"functionBody": "SELECT json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(json_remove_path(d, ARRAY['table':::STRING, 'families':::STRING]:::STRING[]), ARRAY['table':::STRING, 'nextFamilyId':::STRING]:::STRING[]), ARRAY['table':::STRING, 'indexes':::STRING, '0':::STRING, 'createdAtNanos':::STRING]:::STRING[]), ARRAY['table':::STRING, 'indexes':::STRING, '1':::STRING, 'createdAtNanos':::STRING]:::STRING[]), ARRAY['table':::STRING, 'indexes':::STRING, '2':::STRING, 'createdAtNanos':::STRING]:::STRING[]), ARRAY['table':::STRING, 'primaryIndex':::STRING, 'createdAtNanos':::STRING]:::STRING[]), ARRAY['table':::STRING, 'createAsOfTime':::STRING]:::STRING[]), ARRAY['table':::STRING, 'modificationTime':::STRING]:::STRING[]), ARRAY['function':::STRING, 'modificationTime':::STRING]:::STRING[]), ARRAY['type':::STRING, 'modificationTime':::STRING]:::STRING[]), ARRAY['schema':::STRING, 'modificationTime':::STRING]:::STRING[]), ARRAY['database':::STRING, 'modificationTime':::STRING]:::STRING[]);", "id": 113, "lang": "SQL", "name": "strip_volatile", "nullInputBehavior": "CALLED_ON_NULL_INPUT", "params": [{"class": "IN", "name": "d", "type": {"family": "JsonFamily", "oid": 3802}}], "parentId": 104, "parentSchemaId": 105, "privileges": {"ownerProto": "root", "users": [{"privileges": "2", "userProto": "admin", "withGrantOption": "2"}, {"privileges": "1048576", "userProto": "public"}, {"privileges": "2", "userProto": "root", "withGrantOption": "2"}], "version": 3}, "returnType": {"type": {"family": "JsonFamily", "oid": 3802}}, "version": "1", "volatility": "STABLE"}}
152-
4294966962 {"table": {"columns": [{"id": 1, "name": "f_table_catalog", "nullable": true, "type": {"family": "StringFamily", "oid": 19}}, {"id": 2, "name": "f_table_schema", "nullable": true, "type": {"family": "StringFamily", "oid": 19}}, {"id": 3, "name": "f_table_name", "nullable": true, "type": {"family": "StringFamily", "oid": 19}}, {"id": 4, "name": "f_geography_column", "nullable": true, "type": {"family": "StringFamily", "oid": 19}}, {"id": 5, "name": "coord_dimension", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 6, "name": "srid", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 7, "name": "type", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}], "formatVersion": 3, "id": 4294966962, "name": "geography_columns", "nextColumnId": 8, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}, "vecConfig": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294966963, "version": "1"}}
152+
4294966962 {"table": {"columns": [{"id": 1, "name": "f_table_catalog", "nullable": true, "type": {"family": 11, "oid": 19}}, {"id": 2, "name": "f_table_schema", "nullable": true, "type": {"family": 11, "oid": 19}}, {"id": 3, "name": "f_table_name", "nullable": true, "type": {"family": 11, "oid": 19}}, {"id": 4, "name": "f_geography_column", "nullable": true, "type": {"family": 11, "oid": 19}}, {"id": 5, "name": "coord_dimension", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 6, "name": "srid", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 7, "name": "type", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}], "formatVersion": 3, "id": 4294966962, "name": "geography_columns", "nextColumnId": 8, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}, "vecConfig": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294966963, "version": "1"}}

pkg/sql/logictest/testdata/logic_test/mixed_version_char

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# LogicTest: cockroach-go-testserver-25.2
22

3+
subtest regression_152629
4+
35
# This test verifies that the "char" type is handled correctly in a mixed
46
# version cluster. The reason it's being tested is because during 25.4
57
# development we began removing the legacy code for serializing types in the
@@ -25,3 +27,48 @@ user root nodeidx=2
2527

2628
statement ok
2729
insert into t values (ARRAY['abc'::STRING, ''::STRING], 'cat'::STRING);
30+
31+
subtest end
32+
33+
subtest regression_153322
34+
35+
# This is a repro scenario for 153322. It involves the NAME data type, which is
36+
# a string type column type.
37+
38+
# 1 is already upgraded but adding a redundant one here incase this subtest is
39+
# run in isolation.
40+
upgrade 1
41+
42+
user root nodeidx=1
43+
44+
# Create the function using the NAME data type in the upgraded version.
45+
statement ok
46+
CREATE FUNCTION test_name_func(p NAME) RETURNS TEXT LANGUAGE SQL AS 'SELECT p::TEXT'
47+
48+
query T
49+
SELECT test_name_func('test_value')
50+
----
51+
test_value
52+
53+
user root nodeidx=2
54+
55+
# Attempt to access the function on the old non-upgraded versions. These had
56+
# previously failed to resolve the function.
57+
58+
query T
59+
SELECT test_name_func('test_value')
60+
----
61+
test_value
62+
63+
statement ok
64+
ALTER FUNCTION test_name_func(NAME) RENAME TO test_name_func_renamed
65+
66+
query T
67+
SELECT test_name_func_renamed('renamed_test')
68+
----
69+
renamed_test
70+
71+
statement ok
72+
DROP FUNCTION test_name_func_renamed(NAME);
73+
74+
subtest end

pkg/sql/types/types.go

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,7 +2526,18 @@ func (t *T) IsPseudoType() bool {
25262526
//
25272527
// Marshal is part of the protoutil.Message interface.
25282528
func (t *T) Size() (n int) {
2529-
return t.InternalType.Size()
2529+
// non-NAME types don't need any serialization changes to be backwards compatible.
2530+
if t.InternalType.Oid != oid.T_name {
2531+
return t.InternalType.Size()
2532+
}
2533+
// Need to first downgrade the type before delegating to InternalType,
2534+
// because Marshal will downgrade.
2535+
temp := *t
2536+
err := temp.downgradeType()
2537+
if err != nil {
2538+
panic(errors.NewAssertionErrorWithWrappedErrf(err, "error during Size call"))
2539+
}
2540+
return temp.InternalType.Size()
25302541
}
25312542

25322543
// Identical is the internal implementation for T.Identical. See that comment
@@ -2832,12 +2843,50 @@ func (t *T) upgradeType() error {
28322843
return nil
28332844
}
28342845

2846+
// downgradeType transforms NAME types for backwards-compatibility with v25.3
2847+
// and earlier versions during mixed-version clusters. For non-NAME types, this
2848+
// is a no-op.
2849+
//
2850+
// NAME types are downgraded to use the legacy Family=name encoding so that v25.3
2851+
// nodes can correctly interpret them without losing NAME semantics.
2852+
//
2853+
// TODO(sql-foundations): Remove this function entirely once v25.4 is the minimum
2854+
// supported version and we no longer need to support mixed-version clusters with
2855+
// v25.3 nodes.
2856+
func (t *T) downgradeType() error {
2857+
// Set Family for NAME type for backwards-compatibility.
2858+
switch t.Family() {
2859+
case StringFamily, CollatedStringFamily:
2860+
t.InternalType.Family = name
2861+
// If locale is an empty string, we set it to nil to ensure older versions
2862+
// don’t misclassify the NAME as a collated string.
2863+
if t.InternalType.Locale != nil && len(*t.InternalType.Locale) == 0 {
2864+
t.InternalType.Locale = nil
2865+
}
2866+
}
2867+
2868+
return nil
2869+
}
2870+
28352871
// Marshal serializes a type into a byte representation using gogo protobuf
2836-
// serialization rules. It returns the resulting bytes as a slice.
2872+
// serialization rules. It returns the resulting bytes as a slice. The bytes
2873+
// are serialized in a format that is backwards-compatible with the previous
2874+
// version of CRDB so that clusters can run in mixed version mode during
2875+
// upgrade.
28372876
//
28382877
// bytes, err := protoutil.Marshal(&typ)
28392878
func (t *T) Marshal() (data []byte, err error) {
2840-
return protoutil.Marshal(&t.InternalType)
2879+
// non-NAME types don't need any serialization changes to be backwards compatible.
2880+
if t.InternalType.Oid != oid.T_name {
2881+
return protoutil.Marshal(&t.InternalType)
2882+
}
2883+
// First downgrade to a struct that will be serialized in a backwards-
2884+
// compatible bytes format.
2885+
temp := *t
2886+
if err := temp.downgradeType(); err != nil {
2887+
return nil, err
2888+
}
2889+
return protoutil.Marshal(&temp.InternalType)
28412890
}
28422891

28432892
// MarshalToSizedBuffer is like Mashal, except that it deserializes to
@@ -2846,7 +2895,15 @@ func (t *T) Marshal() (data []byte, err error) {
28462895
//
28472896
// Marshal is part of the protoutil.Message interface.
28482897
func (t *T) MarshalToSizedBuffer(data []byte) (int, error) {
2849-
return t.InternalType.MarshalToSizedBuffer(data)
2898+
// non-NAME types don't need any serialization changes to be backwards compatible.
2899+
if t.InternalType.Oid != oid.T_name {
2900+
return t.InternalType.MarshalToSizedBuffer(data)
2901+
}
2902+
temp := *t
2903+
if err := temp.downgradeType(); err != nil {
2904+
return 0, err
2905+
}
2906+
return temp.InternalType.MarshalToSizedBuffer(data)
28502907
}
28512908

28522909
// MarshalTo behaves like Marshal, except that it deserializes to an existing
@@ -2856,7 +2913,15 @@ func (t *T) MarshalToSizedBuffer(data []byte) (int, error) {
28562913
//
28572914
// Marshal is part of the protoutil.Message interface.
28582915
func (t *T) MarshalTo(data []byte) (int, error) {
2859-
return t.InternalType.MarshalTo(data)
2916+
// non-NAME types don't need any serialization changes to be backwards compatible.
2917+
if t.InternalType.Oid != oid.T_name {
2918+
return t.InternalType.MarshalTo(data)
2919+
}
2920+
temp := *t
2921+
if err := temp.downgradeType(); err != nil {
2922+
return 0, err
2923+
}
2924+
return temp.InternalType.MarshalTo(data)
28602925
}
28612926

28622927
// String returns the name of the type, similar to the Name method. However, it

pkg/sql/types/types_jsonpb.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ import (
1919
// MarshalJSONPB marshals the T to json. This is necessary as otherwise
2020
// this field will be lost to the crdb_internal.pb_to_json and the likes.
2121
func (t *T) MarshalJSONPB(marshaler *jsonpb.Marshaler) ([]byte, error) {
22+
temp := *t
23+
// Only NAME types need changes to be backwards compatible.
24+
if temp.InternalType.Oid == oid.T_name {
25+
if err := temp.downgradeType(); err != nil {
26+
return nil, err
27+
}
28+
}
2229
// Map empty locale to nil so empty string does not appear in the JSON result.
2330
// TODO(rafi): When we upgrade to go1.24, we can modify the proto definition
2431
// of the locale field to use `[(gogoproto.jsontag) = ",omitzero"]` instead of
2532
// this workaround.
26-
temp := *t
2733
if temp.InternalType.Locale != nil && len(*temp.InternalType.Locale) == 0 {
2834
temp.InternalType.Locale = nil
2935
}

pkg/sql/types/types_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ func TestUnmarshalCompat(t *testing.T) {
789789
{InternalType{Family: StringFamily, VisibleType: visibleVARCHAR, Width: 20}, MakeVarChar(20)},
790790
{InternalType{Family: StringFamily, VisibleType: visibleCHAR}, BPChar},
791791
{InternalType{Family: StringFamily, VisibleType: visibleQCHAR, Width: 1}, QChar},
792+
{InternalType{Family: name}, Name},
792793
}
793794

794795
for _, tc := range testCases {
@@ -981,6 +982,17 @@ func TestUpgradeType(t *testing.T) {
981982
Locale: &emptyLocale,
982983
}},
983984
},
985+
{
986+
desc: "legacy NAME support",
987+
input: &T{InternalType: InternalType{
988+
Family: name,
989+
}},
990+
expected: &T{InternalType: InternalType{
991+
Family: StringFamily,
992+
Oid: oid.T_name,
993+
Locale: &emptyLocale,
994+
}},
995+
},
984996
}
985997

986998
for _, tc := range testCases {

0 commit comments

Comments
 (0)