Skip to content

Commit 202a547

Browse files
committed
Merge upstream/main into codex/issue-23143-copy-clone-auto-increment-reconcile
2 parents 177edcf + a55820b commit 202a547

194 files changed

Lines changed: 21676 additions & 2625 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/mo-object-tool/ckp/checkpoint_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,37 @@ func TestRestoreCreateTableDDLUsesExternalCreateSQL(t *testing.T) {
189189
assert.Equal(t, "CREATE EXTERNAL TABLE `ckp_external`.`ext_csv_local` (\n `id` INT\n) INFILE {'filepath'='/tmp/ext.csv', 'compression'='none', 'format'='csv'} FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\\\n' IGNORE 1 LINES", ddl)
190190
}
191191

192+
func TestRestoreCreateTableDDLAcceptsUnicodeColumnComment(t *testing.T) {
193+
table := checkpointtool.TableCatalogEntry{
194+
TableID: 272864,
195+
DatabaseName: "big_data_test",
196+
TableName: "ca_comprehensive_dataset",
197+
}
198+
data := &checkpointtool.TableDumpData{
199+
TableID: table.TableID,
200+
Schema: &checkpointtool.TableSchema{
201+
TableName: table.TableName,
202+
DatabaseName: table.DatabaseName,
203+
Columns: []checkpointtool.TableColumn{
204+
{Name: "md5_id", SQLType: "VARCHAR(255)", NotNull: true},
205+
{Name: "question_vector", SQLType: "VECF64(1024)", Comment: "摘要的向量集"},
206+
},
207+
PrimaryKey: []string{"md5_id"},
208+
},
209+
}
210+
211+
ddl, err := restoreCreateTableDDL(
212+
context.Background(),
213+
&checkpointtool.CheckpointReader{},
214+
table,
215+
data,
216+
types.BuildTS(10, 0),
217+
)
218+
require.NoError(t, err)
219+
require.Contains(t, ddl, "`question_vector` VECF64(1024)")
220+
require.Contains(t, ddl, "COMMENT '摘要的向量集'")
221+
}
222+
192223
func TestRestoreCreateTableDDLExternalRequiresCreateSQL(t *testing.T) {
193224
table := checkpointtool.TableCatalogEntry{
194225
DatabaseName: "ckp_external",

cmd/mo-tool/main.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package main
1616

1717
import (
1818
"os"
19+
"strings"
1920

2021
dashboard "github.com/matrixorigin/matrixone/cmd/mo-dashboard"
2122
debug "github.com/matrixorigin/matrixone/cmd/mo-debug"
@@ -38,7 +39,46 @@ func main() {
3839
rootCmd.AddCommand(object.PrepareCommand())
3940
rootCmd.AddCommand(ckp.PrepareCommand())
4041

42+
rootCmd.SetArgs(normalizeLegacyRemoteS3Args(os.Args[1:]))
4143
if err := rootCmd.Execute(); err != nil {
4244
os.Exit(1)
4345
}
4446
}
47+
48+
// normalizeLegacyRemoteS3Args keeps ckp invocations from before --remote-s3
49+
// was introduced working without changing the local S3FS selector semantics of
50+
// a bare --s3 flag.
51+
func normalizeLegacyRemoteS3Args(args []string) []string {
52+
normalized := append([]string(nil), args...)
53+
if len(normalized) == 0 || normalized[0] != "ckp" {
54+
return normalized
55+
}
56+
57+
for i := 1; i < len(normalized); i++ {
58+
switch {
59+
case normalized[i] == "--s3" &&
60+
i+1 < len(normalized) &&
61+
looksLikeRemoteS3Arguments(normalized[i+1]):
62+
normalized[i] = "--remote-s3"
63+
i++
64+
case strings.HasPrefix(normalized[i], "--s3=") &&
65+
looksLikeRemoteS3Arguments(strings.TrimPrefix(normalized[i], "--s3=")):
66+
normalized[i] = "--remote-s3=" + strings.TrimPrefix(normalized[i], "--s3=")
67+
}
68+
}
69+
70+
return normalized
71+
}
72+
73+
func looksLikeRemoteS3Arguments(value string) bool {
74+
for _, option := range strings.Split(value, ",") {
75+
key, optionValue, ok := strings.Cut(strings.TrimSpace(option), "=")
76+
if !ok || optionValue == "" {
77+
continue
78+
}
79+
if strings.EqualFold(strings.TrimSpace(key), "bucket") {
80+
return true
81+
}
82+
}
83+
return false
84+
}

cmd/mo-tool/main_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2026 Matrix Origin
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
func TestNormalizeLegacyRemoteS3Args(t *testing.T) {
24+
remoteArgs := "bucket=test,endpoint=http://minio:9000,region=us-east-1,key-prefix=data,key-id=id,key-secret=secret"
25+
tests := []struct {
26+
name string
27+
args []string
28+
want []string
29+
}{
30+
{
31+
name: "separate legacy argument",
32+
args: []string{"ckp", "list", "--backend=S3", "--s3", remoteArgs},
33+
want: []string{"ckp", "list", "--backend=S3", "--remote-s3", remoteArgs},
34+
},
35+
{
36+
name: "joined legacy argument",
37+
args: []string{"ckp", "dump", "--s3=" + remoteArgs},
38+
want: []string{"ckp", "dump", "--remote-s3=" + remoteArgs},
39+
},
40+
{
41+
name: "bucket with default AWS settings",
42+
args: []string{"ckp", "list", "--s3", "bucket=test"},
43+
want: []string{"ckp", "list", "--remote-s3", "bucket=test"},
44+
},
45+
{
46+
name: "local S3FS selector",
47+
args: []string{"ckp", "list", "--s3", "/tmp/mo-data"},
48+
want: []string{"ckp", "list", "--s3", "/tmp/mo-data"},
49+
},
50+
{
51+
name: "current remote argument",
52+
args: []string{"ckp", "list", "--remote-s3", remoteArgs},
53+
want: []string{"ckp", "list", "--remote-s3", remoteArgs},
54+
},
55+
{
56+
name: "other command",
57+
args: []string{"object", "stat", "--s3", remoteArgs},
58+
want: []string{"object", "stat", "--s3", remoteArgs},
59+
},
60+
}
61+
62+
for _, test := range tests {
63+
t.Run(test.name, func(t *testing.T) {
64+
original := append([]string(nil), test.args...)
65+
require.Equal(t, test.want, normalizeLegacyRemoteS3Args(test.args))
66+
require.Equal(t, original, test.args)
67+
})
68+
}
69+
}

pkg/bootstrap/versions/v4_0_0/upgrade_test.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/golang/mock/gomock"
2323
"github.com/prashantv/gostub"
2424
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
2526

2627
"github.com/matrixorigin/matrixone/pkg/bootstrap/versions"
2728
"github.com/matrixorigin/matrixone/pkg/catalog"
@@ -48,6 +49,10 @@ func mockTenantUpgrade(t *testing.T) {
4849
}
4950

5051
func Test_Upgrade(t *testing.T) {
52+
originalTenantUpgEntries := append([]versions.UpgradeEntry(nil), tenantUpgEntries...)
53+
defer func() {
54+
tenantUpgEntries = originalTenantUpgEntries
55+
}()
5156
mockTenantUpgrade(t)
5257

5358
sid := ""
@@ -85,11 +90,11 @@ func Test_Upgrade(t *testing.T) {
8590
}
8691

8792
func Test_versionHandle_HandleClusterUpgrade(t *testing.T) {
88-
originalEntries := clusterUpgEntries
89-
clusterUpgEntries = nil
93+
originalClusterUpgEntries := append([]versions.UpgradeEntry(nil), clusterUpgEntries...)
9094
defer func() {
91-
clusterUpgEntries = originalEntries
95+
clusterUpgEntries = originalClusterUpgEntries
9296
}()
97+
clusterUpgEntries = []versions.UpgradeEntry{}
9398

9499
v := &versionHandle{
95100
metadata: versions.Version{
@@ -109,6 +114,16 @@ func Test_versionHandle_HandleClusterUpgrade(t *testing.T) {
109114
assert.Nil(t, err)
110115
}
111116

117+
func TestUpgradeDefersMoIndexesIncludeColumn(t *testing.T) {
118+
for _, entries := range [][]versions.UpgradeEntry{clusterUpgEntries, tenantUpgEntries} {
119+
for _, entry := range entries {
120+
require.False(t,
121+
entry.Schema == catalog.MO_CATALOG && entry.TableName == catalog.MO_INDEXES && entry.UpgType == versions.ADD_COLUMN,
122+
"mo_indexes schema changes require a staged release after all writers use explicit column lists")
123+
}
124+
}
125+
}
126+
112127
func Test_upg_create_mo_task_sql_task_check_exists(t *testing.T) {
113128
stubs := gostub.Stub(&versions.CheckTableDefinition, func(txn executor.TxnExecutor, accountId uint32, schema string, table string) (bool, error) {
114129
assert.Equal(t, uint32(0), accountId)

pkg/bootstrap/versions/v4_0_1/upgrade_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import (
2121
"github.com/golang/mock/gomock"
2222
"github.com/prashantv/gostub"
2323
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
2425

2526
"github.com/matrixorigin/matrixone/pkg/bootstrap/versions"
27+
"github.com/matrixorigin/matrixone/pkg/catalog"
2628
"github.com/matrixorigin/matrixone/pkg/common/moerr"
2729
"github.com/matrixorigin/matrixone/pkg/common/runtime"
2830
mock_frontend "github.com/matrixorigin/matrixone/pkg/frontend/test"
@@ -94,6 +96,16 @@ func Test_versionHandle_HandleClusterUpgrade(t *testing.T) {
9496
assert.Nil(t, err)
9597
}
9698

99+
func TestUpgradeDefersMoIndexesIncludeColumn(t *testing.T) {
100+
for _, entries := range [][]versions.UpgradeEntry{clusterUpgEntries, tenantUpgEntries} {
101+
for _, entry := range entries {
102+
require.False(t,
103+
entry.Schema == catalog.MO_CATALOG && entry.TableName == catalog.MO_INDEXES && entry.UpgType == versions.ADD_COLUMN,
104+
"mo_indexes schema changes require a staged release after all writers use explicit column lists")
105+
}
106+
}
107+
}
108+
97109
func Test_upg_statistics_view_check_error(t *testing.T) {
98110
stubs := gostub.Stub(&versions.CheckViewDefinition, func(txn executor.TxnExecutor, accountId uint32, schema string, viewName string) (bool, string, error) {
99111
return true, "", moerr.NewInternalErrorNoCtx("return error")

pkg/catalog/secondary_index_utils.go

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ const (
113113
IntermediateGraphDegree = "intermediate_graph_degree"
114114
GraphDegree = "graph_degree"
115115
ITopkSize = "itopk_size"
116-
IncludedColumns = "included_columns"
116+
// IncludedColumns persists INCLUDE metadata inside algo_params. Consumers
117+
// prefer plan.IndexDef.IncludedColumns when present and fall back to this key
118+
// for catalog-loaded definitions.
119+
IncludedColumns = "included_columns"
117120

118121
// Index-defining build params, settable as CREATE INDEX options (parsed by
119122
// each plugin's ParamsFromTree). Written into flat algo_params only when
@@ -127,6 +130,49 @@ const (
127130
IndexAlgoParamPrefixLengths = "prefix_lengths"
128131
)
129132

133+
func ParseIncludeColumnsValue(raw string) ([]string, error) {
134+
raw = strings.TrimSpace(raw)
135+
if raw == "" {
136+
return nil, nil
137+
}
138+
139+
if strings.HasPrefix(raw, "[") {
140+
var cols []string
141+
if err := json.Unmarshal([]byte(raw), &cols); err == nil {
142+
return cols, nil
143+
}
144+
}
145+
146+
parts := strings.Split(raw, ",")
147+
cols := make([]string, 0, len(parts))
148+
for _, part := range parts {
149+
part = strings.TrimSpace(part)
150+
if part == "" {
151+
continue
152+
}
153+
cols = append(cols, part)
154+
}
155+
if len(cols) == 0 {
156+
return nil, nil
157+
}
158+
return cols, nil
159+
}
160+
161+
// MarshalIncludeColumnsValue encodes INCLUDE column names without losing
162+
// commas or leading/trailing whitespace that are valid inside quoted SQL
163+
// identifiers. ParseIncludeColumnsValue retains support for the historical
164+
// comma-separated representation when catalog metadata is reloaded.
165+
func MarshalIncludeColumnsValue(cols []string) (string, error) {
166+
if len(cols) == 0 {
167+
return "", nil
168+
}
169+
encoded, err := json.Marshal(cols)
170+
if err != nil {
171+
return "", err
172+
}
173+
return string(encoded), nil
174+
}
175+
130176
/* 1. ToString Functions */
131177

132178
// IndexParamsToStringList used by buildShowCreateTable and restoreDDL
@@ -223,19 +269,6 @@ func IndexParamsToStringList(indexParams string) (string, error) {
223269
if val, ok := result[IndexAlgoParamQuantizerTrainLimit]; ok {
224270
res += fmt.Sprintf(" %s = %s ", IndexAlgoParamQuantizerTrainLimit, val)
225271
}
226-
227-
if val, ok := result[IncludedColumns]; ok && len(val) > 0 {
228-
raw := strings.Split(val, ",")
229-
parts := make([]string, 0, len(raw))
230-
for _, p := range raw {
231-
if p = strings.TrimSpace(p); p != "" {
232-
parts = append(parts, p)
233-
}
234-
}
235-
if len(parts) > 0 {
236-
res += " INCLUDE (" + strings.Join(parts, ", ") + ") "
237-
}
238-
}
239272
return res, nil
240273
}
241274

@@ -425,7 +458,7 @@ func indexParamsToMap(def interface{}) (map[string]string, error) {
425458
case tree.INDEX_TYPE_BTREE, tree.INDEX_TYPE_INVALID, tree.INDEX_TYPE_RTREE:
426459
// do nothing
427460
case tree.INDEX_TYPE_MASTER:
428-
// do nothing
461+
// do nothing
429462
default:
430463
// Vector algorithms (IVFFLAT / HNSW / CAGRA / IVFPQ) build their
431464
// algo_params via the per-plugin plan hook BuildIndexParams; they

pkg/catalog/secondary_index_utils_test.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ func newPrefixKeyPart(colName string, length int) *tree.KeyPart {
5959
}
6060

6161
func TestIsIndexAsync(t *testing.T) {
62-
6362
var (
6463
json string
6564
err error
@@ -252,3 +251,56 @@ func TestIndexPrefixLengthsFromParams(t *testing.T) {
252251
require.Nil(t, IndexPrefixLengthsFromParams("{bad json"))
253252
require.Nil(t, IndexPrefixLengthsFromParams(`{"prefix_lengths":"t:0"}`))
254253
}
254+
255+
func unresolvedName(name string) *tree.UnresolvedName {
256+
return tree.NewUnresolvedName(tree.NewCStr(name, 1))
257+
}
258+
259+
func TestIndexParamsToJsonString_RejectsPluginIvfFlatPath(t *testing.T) {
260+
idx := tree.NewIndex(
261+
false,
262+
[]*tree.KeyPart{tree.NewKeyPart(unresolvedName("embedding"), -1, tree.DefaultDirection, nil)},
263+
"idx1",
264+
tree.INDEX_TYPE_IVFFLAT,
265+
&tree.IndexOption{
266+
AlgoParamList: 10,
267+
AlgoParamVectorOpType: "vector_l2_ops",
268+
IncludeColumns: []*tree.UnresolvedName{
269+
unresolvedName("title"),
270+
unresolvedName("category"),
271+
},
272+
},
273+
)
274+
275+
params, err := IndexParamsToJsonString(idx)
276+
require.Error(t, err)
277+
require.Empty(t, params)
278+
require.Contains(t, err.Error(), "invalid index alogorithm type")
279+
}
280+
281+
func TestIndexParamsToStringList_DoesNotRenderIncludeColumns(t *testing.T) {
282+
paramList, err := IndexParamsToStringList(`{"lists":"10","op_type":"vector_l2_ops","included_columns":"title,category"}`)
283+
require.NoError(t, err)
284+
require.Contains(t, paramList, "lists = 10")
285+
require.Contains(t, paramList, "op_type 'vector_l2_ops'")
286+
require.NotContains(t, paramList, "INCLUDE")
287+
require.NotContains(t, paramList, "title")
288+
}
289+
290+
func TestParseIncludeColumnsValue_BackwardCompatible(t *testing.T) {
291+
encoded, err := MarshalIncludeColumnsValue([]string{"title, label", " rank "})
292+
require.NoError(t, err)
293+
require.Equal(t, `["title, label"," rank "]`, encoded)
294+
295+
cols, err := ParseIncludeColumnsValue(encoded)
296+
require.NoError(t, err)
297+
require.Equal(t, []string{"title, label", " rank "}, cols)
298+
299+
cols, err = ParseIncludeColumnsValue("title,category")
300+
require.NoError(t, err)
301+
require.Equal(t, []string{"title", "category"}, cols)
302+
303+
cols, err = ParseIncludeColumnsValue("[legacy,other")
304+
require.NoError(t, err)
305+
require.Equal(t, []string{"[legacy", "other"}, cols)
306+
}

0 commit comments

Comments
 (0)