From f5e84b260b14cf6c834d063251445638ca4e33e6 Mon Sep 17 00:00:00 2001 From: iamlinjunhong <1030420200@qq.com> Date: Thu, 27 Aug 2026 11:35:46 +0800 Subject: [PATCH 1/6] fix: correct information schema views metadata --- .../versions/v4_0_6/tenant_upgrade_list.go | 14 ++++ pkg/bootstrap/versions/v4_0_6/upgrade_test.go | 9 +++ pkg/util/sysview/predefined.go | 27 ++++++- pkg/util/sysview/predefined_test.go | 75 +++++++++++++++++++ .../information_schema_views_metadata.result | 21 ++++++ .../information_schema_views_metadata.sql | 20 +++++ .../zz_accesscontrol/inner_object.result | 4 +- 7 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 test/distributed/cases/view/information_schema_views_metadata.result create mode 100644 test/distributed/cases/view/information_schema_views_metadata.sql diff --git a/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go b/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go index dbb9b32f195bc..5a8c69ea72149 100644 --- a/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go +++ b/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go @@ -49,6 +49,7 @@ var tenantUpgEntries = []versions.UpgradeEntry{ upgradeInformationSchemaCollationCharacterSetApplicability(), backfillMoColumnsAttIsUnsigned(), upgradeInformationSchemaStatistics(), + upgradeInformationSchemaViews(), } const moColumnsUnsignedMismatchPredicate = "account_id = current_account_id() " + @@ -96,6 +97,19 @@ func upgradeInformationSchemaColumnsHideInternalColumns() versions.UpgradeEntry return upgradeInformationSchemaColumns() } +// Keep a separate entry so existing tenants replace the historical VIEWS +// definition that exposed CREATE VIEW text and claimed every view was writable. +func upgradeInformationSchemaViews() versions.UpgradeEntry { + return versions.UpgradeEntry{ + Schema: sysview.InformationDBConst, + TableName: "VIEWS", + UpgType: versions.MODIFY_VIEW, + UpgSql: sysview.InformationSchemaViewsDDL, + CheckFunc: checkViewDefinition("VIEWS", sysview.InformationSchemaViewsDDL), + PreSql: fmt.Sprintf("DROP VIEW IF EXISTS %s.VIEWS;", sysview.InformationDBConst), + } +} + func addForeignKeyMetadataColumn(column, definition, after string) versions.UpgradeEntry { return versions.UpgradeEntry{ Schema: catalog.MO_CATALOG, diff --git a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go index 2b7f6fc2b543e..3595237eb9de2 100644 --- a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go +++ b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go @@ -139,6 +139,12 @@ func TestUpgradeEntries(t *testing.T) { require.Equal(t, sysview.InformationSchemaStatisticsDDL, statistics.UpgSql) require.Contains(t, strings.ToLower(statistics.PreSql), "drop view if exists information_schema.statistics") + views := tenantUpgEntries[20] + require.Equal(t, versions.MODIFY_VIEW, views.UpgType) + require.Equal(t, sysview.InformationDBConst, views.Schema) + require.Equal(t, "VIEWS", views.TableName) + require.Equal(t, sysview.InformationSchemaViewsDDL, views.UpgSql) + require.Contains(t, strings.ToLower(views.PreSql), "drop view if exists information_schema.views") } func TestMoColumnsUnsignedBackfillPredicate(t *testing.T) { @@ -486,6 +492,7 @@ func TestTenantViewDefinitionChecks(t *testing.T) { upgradeInformationSchemaTableConstraints(), upgradeInformationSchemaCollationCharacterSetApplicability(), upgradeInformationSchemaStatistics(), + upgradeInformationSchemaViews(), } for _, entry := range entries { @@ -625,6 +632,8 @@ func TestVersionHandleLifecycleWithNoLegacyDefinitions(t *testing.T) { return true, sysview.InformationSchemaColumnsDDL, nil case "STATISTICS": return true, sysview.InformationSchemaStatisticsDDL, nil + case "VIEWS": + return true, sysview.InformationSchemaViewsDDL, nil default: return false, "", errors.New("unexpected view") } diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index e6d7dde3cb175..0f8a6ca687bb2 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -21,6 +21,29 @@ import ( "github.com/matrixorigin/matrixone/pkg/catalog" ) +const ( + informationSchemaViewIdentifierPattern = "(?:`(?:``|[^`])*`|\"(?:\"\"|[^\"])*\"|[^[:space:].(),]+)" + // The non-greedy span before VIEW covers MatrixOne's supported ALGORITHM, + // DEFINER, and SQL SECURITY clauses as well as mysqldump's version comments. + informationSchemaViewDefinitionPrefixPattern = "(?is)^[[:space:]]*(?:/[*]![0-9]+[[:space:]]*)?" + + "(?:create(?:[[:space:]]+or[[:space:]]+replace)?|alter).*?[[:space:]]+view[[:space:]]+" + + "(?:if[[:space:]]+(?:not[[:space:]]+)?exists[[:space:]]+)?" + + informationSchemaViewIdentifierPattern + + "(?:[[:space:]]*[.][[:space:]]*" + informationSchemaViewIdentifierPattern + ")?" + + "[[:space:]]*(?:[(][[:space:]]*" + informationSchemaViewIdentifierPattern + + "(?:[[:space:]]*,[[:space:]]*" + informationSchemaViewIdentifierPattern + ")*[[:space:]]*[)])?" + + "[[:space:]]+as[[:space:]]+" + informationSchemaViewDefinitionCommentSuffixPattern = "(?is)[[:space:]]*[*]/[[:space:]]*;?[[:space:]]*$" + informationSchemaViewStatementSQL = "coalesce(json_extract_string(tbl.viewdef, '$.Stmt'), tbl.rel_createsql)" + informationSchemaViewDefinitionSQL = "cast(trim(trailing ';' from trim(case when left(trim(" + + informationSchemaViewStatementSQL + "), 3) = '/*!' then " + + "regexp_replace(regexp_replace(" + informationSchemaViewStatementSQL + ", '" + + informationSchemaViewDefinitionPrefixPattern + "', '', 1, 1), '" + + informationSchemaViewDefinitionCommentSuffixPattern + "', '', 1, 1) else " + + "regexp_replace(" + informationSchemaViewStatementSQL + ", '" + + informationSchemaViewDefinitionPrefixPattern + "', '', 1, 1) end)) as text)" +) + // `mysql` database system tables // They are all Tenant level system tables var ( @@ -398,9 +421,9 @@ var ( "SELECT 'def' AS `TABLE_CATALOG`," + "tbl.reldatabase AS `TABLE_SCHEMA`," + "tbl.relname AS `TABLE_NAME`," + - "tbl.rel_createsql AS `VIEW_DEFINITION`," + + informationSchemaViewDefinitionSQL + " AS `VIEW_DEFINITION`," + "'NONE' AS `CHECK_OPTION`," + - "'YES' AS `IS_UPDATABLE`," + + "'NO' AS `IS_UPDATABLE`," + "usr.user_name + '@' + usr.user_host AS `DEFINER`," + "'DEFINER' AS `SECURITY_TYPE`," + "'utf8mb4' AS `CHARACTER_SET_CLIENT`," + diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index 032593363e9df..e98ecd51fd3b6 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -17,6 +17,7 @@ package sysview import ( "context" "fmt" + "regexp" "strings" "testing" @@ -229,6 +230,80 @@ func TestInformationSchemaCharacterSetsData(t *testing.T) { assert.Equal(t, ddlIndex+1, dataIndex) } +func TestInformationSchemaViewsMetadata(t *testing.T) { + assert.Contains(t, InformationSchemaViewsDDL, + "case when left(trim(coalesce(json_extract_string(tbl.viewdef, '$.Stmt'), tbl.rel_createsql)), 3) = '/*!'") + assert.Contains(t, InformationSchemaViewsDDL, "end)) as text) AS `VIEW_DEFINITION`") + assert.Contains(t, InformationSchemaViewsDDL, "'NO' AS `IS_UPDATABLE`") + assert.NotContains(t, InformationSchemaViewsDDL, "tbl.rel_createsql AS `VIEW_DEFINITION`") + + prefix := regexp.MustCompile(informationSchemaViewDefinitionPrefixPattern) + tests := []struct { + name string + createSQL string + definition string + }{ + { + name: "aggregate view", + createSQL: "create view agg_v as select a, count(*) cnt from t group by a;", + definition: "select a, count(*) cnt from t group by a", + }, + { + name: "qualified stable view", + createSQL: "create view `db`.`v` as select `t`.`a` as `a` from `db`.`t`", + definition: "select `t`.`a` as `a` from `db`.`t`", + }, + { + name: "replace view with cte", + createSQL: "CREATE OR REPLACE VIEW IF NOT EXISTS \"db\".\"v as quoted\" AS WITH c AS (SELECT 1) SELECT * FROM c", + definition: "WITH c AS (SELECT 1) SELECT * FROM c", + }, + { + name: "alter view with explicit columns", + createSQL: " ALTER VIEW IF EXISTS `v` (`c as quoted`, plain) AS SELECT a AS plain, b FROM t", + definition: "SELECT a AS plain, b FROM t", + }, + { + name: "view options", + createSQL: "CREATE ALGORITHM=MERGE DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `v` AS SELECT 1;", + definition: "SELECT 1", + }, + { + name: "mysqldump version comments", + createSQL: "/*!50001 CREATE ALGORITHM=UNDEFINED *//*!50013 DEFINER=`root`@`%` SQL SECURITY DEFINER */" + + "/*!50001 VIEW `v` AS select 1 */;", + definition: "select 1", + }, + { + name: "select block comment remains intact", + createSQL: "create view v as select 1 /* application comment */;", + definition: "select 1 /* application comment */", + }, + { + name: "unrecognized metadata remains visible", + createSQL: "select 1", + definition: "select 1", + }, + } + suffix := regexp.MustCompile(informationSchemaViewDefinitionCommentSuffixPattern) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + definition := strings.TrimSpace(prefix.ReplaceAllString(test.createSQL, "")) + if strings.HasPrefix(strings.TrimSpace(test.createSQL), "/*!") { + definition = strings.TrimSpace(suffix.ReplaceAllString(definition, "")) + } + definition = strings.TrimSuffix(definition, ";") + assert.Equal(t, test.definition, definition) + }) + } + + statements, err := mysql.Parse(context.Background(), InformationSchemaViewsDDL, 1) + assert.NoError(t, err) + for _, statement := range statements { + statement.Free() + } +} + func TestInformationSchemaDefaultCollationsMatchCanonicalDefinitions(t *testing.T) { assert.Empty(t, DefaultCollationForCharset("unknown_charset")) for _, charset := range []string{"binary", "utf8", "utf8mb4"} { diff --git a/test/distributed/cases/view/information_schema_views_metadata.result b/test/distributed/cases/view/information_schema_views_metadata.result new file mode 100644 index 0000000000000..9275bb7d1d772 --- /dev/null +++ b/test/distributed/cases/view/information_schema_views_metadata.result @@ -0,0 +1,21 @@ +drop database if exists information_schema_views_metadata; +create database information_schema_views_metadata; +use information_schema_views_metadata; +create table t(a int, b int); +insert into t values (1, 10), (1, 20), (2, 30); +create view direct_v as select a, b from t; +create view agg_v as select a, count(*) cnt from t group by a; +/*!50001 CREATE DEFINER = `root`@`%` VIEW dump_v AS select a from t */; +select table_name, view_definition, is_updatable +from information_schema.views +where table_schema = 'information_schema_views_metadata' +order by table_name; +➤ table_name[12,3750,0] ¦ view_definition[12,0,0] ¦ is_updatable[12,2,0] 𝄀 +agg_v ¦ select a, count(*) cnt from t group by a ¦ NO 𝄀 +direct_v ¦ select a, b from t ¦ NO 𝄀 +dump_v ¦ select a from t ¦ NO +update agg_v set cnt = 1; +invalid input: cannot insert/update/delete from view +update direct_v set b = 1; +invalid input: cannot insert/update/delete from view +drop database information_schema_views_metadata; diff --git a/test/distributed/cases/view/information_schema_views_metadata.sql b/test/distributed/cases/view/information_schema_views_metadata.sql new file mode 100644 index 0000000000000..5459bbf3e4b8a --- /dev/null +++ b/test/distributed/cases/view/information_schema_views_metadata.sql @@ -0,0 +1,20 @@ +-- @label:bvt +drop database if exists information_schema_views_metadata; +create database information_schema_views_metadata; +use information_schema_views_metadata; + +create table t(a int, b int); +insert into t values (1, 10), (1, 20), (2, 30); +create view direct_v as select a, b from t; +create view agg_v as select a, count(*) cnt from t group by a; +/*!50001 CREATE DEFINER = `root`@`%` VIEW dump_v AS select a from t */; + +select table_name, view_definition, is_updatable +from information_schema.views +where table_schema = 'information_schema_views_metadata' +order by table_name; + +update agg_v set cnt = 1; +update direct_v set b = 1; + +drop database information_schema_views_metadata; diff --git a/test/distributed/cases/zz_accesscontrol/inner_object.result b/test/distributed/cases/zz_accesscontrol/inner_object.result index f8199f6ca9103..6bfc40570a2b9 100644 --- a/test/distributed/cases/zz_accesscontrol/inner_object.result +++ b/test/distributed/cases/zz_accesscontrol/inner_object.result @@ -334,7 +334,7 @@ select count(*),table_name from information_schema.tables group by table_name ha ➤ count(*)[-5,64,0] ¦ table_name[12,-1,0] select * from information_schema.views where table_name='ac_v1'; ➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] 𝄀 -def ¦ ac_db ¦ ac_v1 ¦ create view `ac_db`.`ac_v1` as select `ac_t1`.`c1` as `c1` from `ac_db`.`ac_t1` ¦ NONE ¦ YES ¦ admin@localhost ¦ DEFINER ¦ utf8mb4 ¦ utf8mb4_general_ci +def ¦ ac_db ¦ ac_v1 ¦ select `ac_t1`.`c1` as `c1` from `ac_db`.`ac_t1` ¦ NONE ¦ NO ¦ admin@localhost ¦ DEFINER ¦ utf8mb4 ¦ utf8mb4_general_ci select * from information_schema.views where table_name='sys_v1'; ➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] select count(*),table_name from information_schema.views group by table_name having count(*)>1; @@ -368,7 +368,7 @@ select count(*),table_name from information_schema.tables group by table_name ha ➤ count(*)[-5,64,0] ¦ table_name[12,-1,0] select * from information_schema.views where table_name='sys_v1'; ➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] 𝄀 -def ¦ sys_db1 ¦ sys_v1 ¦ create view `sys_db1`.`sys_v1` as select `sys_t1`.`c1` as `c1` from `sys_db1`.`sys_t1` ¦ NONE ¦ YES ¦ dump@localhost ¦ DEFINER ¦ utf8mb4 ¦ utf8mb4_general_ci +def ¦ sys_db1 ¦ sys_v1 ¦ select `sys_t1`.`c1` as `c1` from `sys_db1`.`sys_t1` ¦ NONE ¦ NO ¦ dump@localhost ¦ DEFINER ¦ utf8mb4 ¦ utf8mb4_general_ci select * from information_schema.views where table_name='ac_v1'; ➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] select count(*),table_name from information_schema.views group by table_name having count(*)>1; From 0f8e60ebaa63180e99fade54f307e7848fe7b3cd Mon Sep 17 00:00:00 2001 From: iamlinjunhong <1030420200@qq.com> Date: Thu, 27 Aug 2026 15:36:09 +0800 Subject: [PATCH 2/6] test: update views metadata type expectation --- test/distributed/cases/system_variable/system_variables.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/distributed/cases/system_variable/system_variables.result b/test/distributed/cases/system_variable/system_variables.result index 0c50e8df76899..e985a06e872fd 100644 --- a/test/distributed/cases/system_variable/system_variables.result +++ b/test/distributed/cases/system_variable/system_variables.result @@ -259,7 +259,7 @@ table_schema ¦ VARCHAR(5000) ¦ YES ¦ ¦ null ¦ ¦ 𝄀 table_name ¦ VARCHAR(5000) ¦ YES ¦ ¦ null ¦ ¦ 𝄀 view_definition ¦ TEXT(0) ¦ YES ¦ ¦ null ¦ ¦ 𝄀 check_option ¦ VARCHAR(4) ¦ NO ¦ ¦ null ¦ ¦ 𝄀 -is_updatable ¦ VARCHAR(3) ¦ NO ¦ ¦ null ¦ ¦ 𝄀 +is_updatable ¦ VARCHAR(2) ¦ NO ¦ ¦ null ¦ ¦ 𝄀 definer ¦ VARCHAR(65535) ¦ YES ¦ ¦ null ¦ ¦ 𝄀 security_type ¦ VARCHAR(7) ¦ NO ¦ ¦ null ¦ ¦ 𝄀 character_set_client ¦ VARCHAR(7) ¦ NO ¦ ¦ null ¦ ¦ 𝄀 From 3b3879c5ad117a37b0eadd9981adf0a794a1ca35 Mon Sep 17 00:00:00 2001 From: iamlinjunhong <1030420200@qq.com> Date: Thu, 27 Aug 2026 19:26:17 +0800 Subject: [PATCH 3/6] fix: keep information schema views cloneable --- pkg/util/sysview/predefined.go | 28 +++++++++++++------ pkg/util/sysview/predefined_test.go | 6 ++-- .../information_schema_views_metadata.result | 3 ++ .../information_schema_views_metadata.sql | 5 ++++ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index 0f8a6ca687bb2..ea27ec0ba60bd 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -33,15 +33,25 @@ const ( "[[:space:]]*(?:[(][[:space:]]*" + informationSchemaViewIdentifierPattern + "(?:[[:space:]]*,[[:space:]]*" + informationSchemaViewIdentifierPattern + ")*[[:space:]]*[)])?" + "[[:space:]]+as[[:space:]]+" - informationSchemaViewDefinitionCommentSuffixPattern = "(?is)[[:space:]]*[*]/[[:space:]]*;?[[:space:]]*$" - informationSchemaViewStatementSQL = "coalesce(json_extract_string(tbl.viewdef, '$.Stmt'), tbl.rel_createsql)" - informationSchemaViewDefinitionSQL = "cast(trim(trailing ';' from trim(case when left(trim(" + - informationSchemaViewStatementSQL + "), 3) = '/*!' then " + - "regexp_replace(regexp_replace(" + informationSchemaViewStatementSQL + ", '" + - informationSchemaViewDefinitionPrefixPattern + "', '', 1, 1), '" + - informationSchemaViewDefinitionCommentSuffixPattern + "', '', 1, 1) else " + - "regexp_replace(" + informationSchemaViewStatementSQL + ", '" + - informationSchemaViewDefinitionPrefixPattern + "', '', 1, 1) end)) as text)" + informationSchemaViewDefinitionVersionCommentPrefixPattern = "(?is)^[[:space:]]*/[*]![0-9]+[[:space:]]*" + informationSchemaViewDefinitionCommentSuffixPattern = "(?is)[[:space:]]*[*]/[[:space:]]*;?[[:space:]]*$" + informationSchemaViewStatementSQL = "coalesce(json_extract_string(tbl.viewdef, '$.Stmt'), tbl.rel_createsql)" + informationSchemaViewStatementWithoutTerminatorSQL = "trim(regexp_replace(trim(" + + informationSchemaViewStatementSQL + "), '[;][[:space:]]*$', '', 1, 1))" + informationSchemaViewDefinitionPrefixLengthSQL = "char_length(coalesce(regexp_substr(" + + informationSchemaViewStatementWithoutTerminatorSQL + ", '" + informationSchemaViewDefinitionPrefixPattern + "'), ''))" + informationSchemaViewDefinitionVersionCommentPrefixLengthSQL = "char_length(coalesce(regexp_substr(" + + informationSchemaViewStatementWithoutTerminatorSQL + ", '" + informationSchemaViewDefinitionVersionCommentPrefixPattern + "'), ''))" + // Keep the persisted system-view definition free of CASE/IF, which the + // database-clone catalog restore cannot parse in this view definition. + // Prefix lengths are counted in characters so they match substr even for + // multibyte view identifiers. The version-comment prefix recognizes only a + // mysqldump wrapper, so a trailing */ is removed only for that wrapper and + // not for an application comment. + informationSchemaViewDefinitionSQL = "cast(trim(substr(" + informationSchemaViewStatementWithoutTerminatorSQL + + ", " + informationSchemaViewDefinitionPrefixLengthSQL + " + 1, char_length(" + + informationSchemaViewStatementWithoutTerminatorSQL + ") - " + informationSchemaViewDefinitionPrefixLengthSQL + " - " + + "2 * least(" + informationSchemaViewDefinitionVersionCommentPrefixLengthSQL + ", 1))) as text)" ) // `mysql` database system tables diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index e98ecd51fd3b6..b5beeed564c81 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -232,8 +232,10 @@ func TestInformationSchemaCharacterSetsData(t *testing.T) { func TestInformationSchemaViewsMetadata(t *testing.T) { assert.Contains(t, InformationSchemaViewsDDL, - "case when left(trim(coalesce(json_extract_string(tbl.viewdef, '$.Stmt'), tbl.rel_createsql)), 3) = '/*!'") - assert.Contains(t, InformationSchemaViewsDDL, "end)) as text) AS `VIEW_DEFINITION`") + "char_length(coalesce(regexp_substr(trim(regexp_replace(trim(coalesce(json_extract_string(tbl.viewdef, '$.Stmt'), tbl.rel_createsql))") + assert.Contains(t, InformationSchemaViewsDDL, "2 * least(char_length(coalesce(regexp_substr(") + assert.NotContains(t, InformationSchemaViewsDDL, "case when") + assert.NotContains(t, InformationSchemaViewsDDL, "trim(if(") assert.Contains(t, InformationSchemaViewsDDL, "'NO' AS `IS_UPDATABLE`") assert.NotContains(t, InformationSchemaViewsDDL, "tbl.rel_createsql AS `VIEW_DEFINITION`") diff --git a/test/distributed/cases/view/information_schema_views_metadata.result b/test/distributed/cases/view/information_schema_views_metadata.result index 9275bb7d1d772..a7b8fd1bbd32c 100644 --- a/test/distributed/cases/view/information_schema_views_metadata.result +++ b/test/distributed/cases/view/information_schema_views_metadata.result @@ -19,3 +19,6 @@ invalid input: cannot insert/update/delete from view update direct_v set b = 1; invalid input: cannot insert/update/delete from view drop database information_schema_views_metadata; +drop database if exists information_schema_views_clone; +create database information_schema_views_clone clone information_schema; +drop database information_schema_views_clone; diff --git a/test/distributed/cases/view/information_schema_views_metadata.sql b/test/distributed/cases/view/information_schema_views_metadata.sql index 5459bbf3e4b8a..485a324abb875 100644 --- a/test/distributed/cases/view/information_schema_views_metadata.sql +++ b/test/distributed/cases/view/information_schema_views_metadata.sql @@ -18,3 +18,8 @@ update agg_v set cnt = 1; update direct_v set b = 1; drop database information_schema_views_metadata; + +-- The stored VIEWS definition must remain executable when a system database is cloned. +drop database if exists information_schema_views_clone; +create database information_schema_views_clone clone information_schema; +drop database information_schema_views_clone; From 619c939ce407252ac7d7e09f8cfd9cba71dcda10 Mon Sep 17 00:00:00 2001 From: iamlinjunhong <1030420200@qq.com> Date: Thu, 27 Aug 2026 22:08:54 +0800 Subject: [PATCH 4/6] fix: handle line comments in view metadata --- pkg/util/sysview/predefined.go | 16 ++++++++++------ pkg/util/sysview/predefined_test.go | 5 +++++ .../information_schema_views_metadata.result | 7 +++++-- .../view/information_schema_views_metadata.sql | 2 ++ .../cases/zz_accesscontrol/inner_object.result | 4 ++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index ea27ec0ba60bd..78d1b0c5c67c2 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -23,16 +23,20 @@ import ( const ( informationSchemaViewIdentifierPattern = "(?:`(?:``|[^`])*`|\"(?:\"\"|[^\"])*\"|[^[:space:].(),]+)" + // GetRootSql preserves line comments, so separators in the persisted DDL must + // accept them wherever valid SQL permits whitespace between view tokens. + informationSchemaViewOptionalSeparatorPattern = "(?:[[:space:]]|--[^\\r\\n]*(?:\\r?\\n|$))*" + informationSchemaViewRequiredSeparatorPattern = "(?:[[:space:]]|--[^\\r\\n]*(?:\\r?\\n|$))+" // The non-greedy span before VIEW covers MatrixOne's supported ALGORITHM, // DEFINER, and SQL SECURITY clauses as well as mysqldump's version comments. informationSchemaViewDefinitionPrefixPattern = "(?is)^[[:space:]]*(?:/[*]![0-9]+[[:space:]]*)?" + - "(?:create(?:[[:space:]]+or[[:space:]]+replace)?|alter).*?[[:space:]]+view[[:space:]]+" + - "(?:if[[:space:]]+(?:not[[:space:]]+)?exists[[:space:]]+)?" + + "(?:create(?:" + informationSchemaViewRequiredSeparatorPattern + "or" + informationSchemaViewRequiredSeparatorPattern + "replace)?|alter).*?" + informationSchemaViewRequiredSeparatorPattern + "view" + informationSchemaViewRequiredSeparatorPattern + + "(?:if" + informationSchemaViewRequiredSeparatorPattern + "(?:not" + informationSchemaViewRequiredSeparatorPattern + ")?exists" + informationSchemaViewRequiredSeparatorPattern + ")?" + informationSchemaViewIdentifierPattern + - "(?:[[:space:]]*[.][[:space:]]*" + informationSchemaViewIdentifierPattern + ")?" + - "[[:space:]]*(?:[(][[:space:]]*" + informationSchemaViewIdentifierPattern + - "(?:[[:space:]]*,[[:space:]]*" + informationSchemaViewIdentifierPattern + ")*[[:space:]]*[)])?" + - "[[:space:]]+as[[:space:]]+" + "(?:" + informationSchemaViewOptionalSeparatorPattern + "[.]" + informationSchemaViewOptionalSeparatorPattern + informationSchemaViewIdentifierPattern + ")?" + + informationSchemaViewOptionalSeparatorPattern + "(?:[(]" + informationSchemaViewOptionalSeparatorPattern + informationSchemaViewIdentifierPattern + + "(?:" + informationSchemaViewOptionalSeparatorPattern + "[,]" + informationSchemaViewOptionalSeparatorPattern + informationSchemaViewIdentifierPattern + ")*" + informationSchemaViewOptionalSeparatorPattern + "[)])?" + + informationSchemaViewRequiredSeparatorPattern + "as" + informationSchemaViewRequiredSeparatorPattern informationSchemaViewDefinitionVersionCommentPrefixPattern = "(?is)^[[:space:]]*/[*]![0-9]+[[:space:]]*" informationSchemaViewDefinitionCommentSuffixPattern = "(?is)[[:space:]]*[*]/[[:space:]]*;?[[:space:]]*$" informationSchemaViewStatementSQL = "coalesce(json_extract_string(tbl.viewdef, '$.Stmt'), tbl.rel_createsql)" diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index b5beeed564c81..c823a7dfcce57 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -281,6 +281,11 @@ func TestInformationSchemaViewsMetadata(t *testing.T) { createSQL: "create view v as select 1 /* application comment */;", definition: "select 1 /* application comment */", }, + { + name: "line comment before as", + createSQL: "create view v -- migration comment\n as select 1;", + definition: "select 1", + }, { name: "unrecognized metadata remains visible", createSQL: "select 1", diff --git a/test/distributed/cases/view/information_schema_views_metadata.result b/test/distributed/cases/view/information_schema_views_metadata.result index a7b8fd1bbd32c..027cbb43828fe 100644 --- a/test/distributed/cases/view/information_schema_views_metadata.result +++ b/test/distributed/cases/view/information_schema_views_metadata.result @@ -6,14 +6,17 @@ insert into t values (1, 10), (1, 20), (2, 30); create view direct_v as select a, b from t; create view agg_v as select a, count(*) cnt from t group by a; /*!50001 CREATE DEFINER = `root`@`%` VIEW dump_v AS select a from t */; +create view line_comment_v -- migration-generated view +as select a from t; select table_name, view_definition, is_updatable from information_schema.views where table_schema = 'information_schema_views_metadata' order by table_name; -➤ table_name[12,3750,0] ¦ view_definition[12,0,0] ¦ is_updatable[12,2,0] 𝄀 +➤ table_name[12,5000,0] ¦ view_definition[-1,16383,0] ¦ is_updatable[12,2,0] 𝄀 agg_v ¦ select a, count(*) cnt from t group by a ¦ NO 𝄀 direct_v ¦ select a, b from t ¦ NO 𝄀 -dump_v ¦ select a from t ¦ NO +dump_v ¦ select a from t ¦ NO 𝄀 +line_comment_v ¦ select a from t ¦ NO update agg_v set cnt = 1; invalid input: cannot insert/update/delete from view update direct_v set b = 1; diff --git a/test/distributed/cases/view/information_schema_views_metadata.sql b/test/distributed/cases/view/information_schema_views_metadata.sql index 485a324abb875..2727a4376ca9f 100644 --- a/test/distributed/cases/view/information_schema_views_metadata.sql +++ b/test/distributed/cases/view/information_schema_views_metadata.sql @@ -8,6 +8,8 @@ insert into t values (1, 10), (1, 20), (2, 30); create view direct_v as select a, b from t; create view agg_v as select a, count(*) cnt from t group by a; /*!50001 CREATE DEFINER = `root`@`%` VIEW dump_v AS select a from t */; +create view line_comment_v -- migration-generated view +as select a from t; select table_name, view_definition, is_updatable from information_schema.views diff --git a/test/distributed/cases/zz_accesscontrol/inner_object.result b/test/distributed/cases/zz_accesscontrol/inner_object.result index 6bfc40570a2b9..00a3c97d003c9 100644 --- a/test/distributed/cases/zz_accesscontrol/inner_object.result +++ b/test/distributed/cases/zz_accesscontrol/inner_object.result @@ -333,7 +333,7 @@ ac_db ¦ ac_t1 select count(*),table_name from information_schema.tables group by table_name having count(*) >1; ➤ count(*)[-5,64,0] ¦ table_name[12,-1,0] select * from information_schema.views where table_name='ac_v1'; -➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] 𝄀 +➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[-1,16383,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] 𝄀 def ¦ ac_db ¦ ac_v1 ¦ select `ac_t1`.`c1` as `c1` from `ac_db`.`ac_t1` ¦ NONE ¦ NO ¦ admin@localhost ¦ DEFINER ¦ utf8mb4 ¦ utf8mb4_general_ci select * from information_schema.views where table_name='sys_v1'; ➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] @@ -367,7 +367,7 @@ select table_schema,table_name from information_schema.tables where table_name=' select count(*),table_name from information_schema.tables group by table_name having count(*) >1; ➤ count(*)[-5,64,0] ¦ table_name[12,-1,0] select * from information_schema.views where table_name='sys_v1'; -➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] 𝄀 +➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[-1,16383,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] 𝄀 def ¦ sys_db1 ¦ sys_v1 ¦ select `sys_t1`.`c1` as `c1` from `sys_db1`.`sys_t1` ¦ NONE ¦ NO ¦ dump@localhost ¦ DEFINER ¦ utf8mb4 ¦ utf8mb4_general_ci select * from information_schema.views where table_name='ac_v1'; ➤ TABLE_CATALOG[12,2,0] ¦ TABLE_SCHEMA[12,3750,0] ¦ TABLE_NAME[12,3750,0] ¦ VIEW_DEFINITION[12,0,0] ¦ CHECK_OPTION[12,3,0] ¦ IS_UPDATABLE[12,2,0] ¦ DEFINER[12,49151,0] ¦ SECURITY_TYPE[12,5,0] ¦ CHARACTER_SET_CLIENT[12,5,0] ¦ COLLATION_CONNECTION[12,13,0] From 8099ce86e21202b551f006421aa66754e4fde67b Mon Sep 17 00:00:00 2001 From: iamlinjunhong <1030420200@qq.com> Date: Fri, 28 Aug 2026 19:39:01 +0800 Subject: [PATCH 5/6] fix: support all view line comment forms --- pkg/util/sysview/predefined.go | 8 +++++--- pkg/util/sysview/predefined_test.go | 20 +++++++++++++++++++ .../information_schema_views_metadata.result | 8 +++++++- .../information_schema_views_metadata.sql | 4 ++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index 78d1b0c5c67c2..6662ed532724c 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -24,9 +24,11 @@ import ( const ( informationSchemaViewIdentifierPattern = "(?:`(?:``|[^`])*`|\"(?:\"\"|[^\"])*\"|[^[:space:].(),]+)" // GetRootSql preserves line comments, so separators in the persisted DDL must - // accept them wherever valid SQL permits whitespace between view tokens. - informationSchemaViewOptionalSeparatorPattern = "(?:[[:space:]]|--[^\\r\\n]*(?:\\r?\\n|$))*" - informationSchemaViewRequiredSeparatorPattern = "(?:[[:space:]]|--[^\\r\\n]*(?:\\r?\\n|$))+" + // accept every lexer-supported form wherever valid SQL permits whitespace + // between view tokens. + informationSchemaViewLineCommentPattern = "(?:(?:--|#|//)[^\\r\\n]*(?:\\r?\\n|$))" + informationSchemaViewOptionalSeparatorPattern = "(?:[[:space:]]|" + informationSchemaViewLineCommentPattern + ")*" + informationSchemaViewRequiredSeparatorPattern = "(?:[[:space:]]|" + informationSchemaViewLineCommentPattern + ")+" // The non-greedy span before VIEW covers MatrixOne's supported ALGORITHM, // DEFINER, and SQL SECURITY clauses as well as mysqldump's version comments. informationSchemaViewDefinitionPrefixPattern = "(?is)^[[:space:]]*(?:/[*]![0-9]+[[:space:]]*)?" + diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index c823a7dfcce57..78d81950bd142 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -286,6 +286,16 @@ func TestInformationSchemaViewsMetadata(t *testing.T) { createSQL: "create view v -- migration comment\n as select 1;", definition: "select 1", }, + { + name: "hash line comment before as", + createSQL: "create view v # migration comment\n as select 1;", + definition: "select 1", + }, + { + name: "slash line comment before as", + createSQL: "create view v // migration comment\n as select 1;", + definition: "select 1", + }, { name: "unrecognized metadata remains visible", createSQL: "select 1", @@ -303,6 +313,16 @@ func TestInformationSchemaViewsMetadata(t *testing.T) { assert.Equal(t, test.definition, definition) }) } + for _, createSQL := range []string{ + "create view hash_comment_v # migration comment\n as select 1;", + "create view slash_comment_v // migration comment\n as select 1;", + } { + statements, err := mysql.Parse(context.Background(), createSQL, 1) + assert.NoError(t, err) + for _, statement := range statements { + statement.Free() + } + } statements, err := mysql.Parse(context.Background(), InformationSchemaViewsDDL, 1) assert.NoError(t, err) diff --git a/test/distributed/cases/view/information_schema_views_metadata.result b/test/distributed/cases/view/information_schema_views_metadata.result index 027cbb43828fe..5ac4a159e8220 100644 --- a/test/distributed/cases/view/information_schema_views_metadata.result +++ b/test/distributed/cases/view/information_schema_views_metadata.result @@ -8,6 +8,10 @@ create view agg_v as select a, count(*) cnt from t group by a; /*!50001 CREATE DEFINER = `root`@`%` VIEW dump_v AS select a from t */; create view line_comment_v -- migration-generated view as select a from t; +create view hash_comment_v # migration-generated view +as select a from t; +create view slash_comment_v // migration-generated view +as select a from t; select table_name, view_definition, is_updatable from information_schema.views where table_schema = 'information_schema_views_metadata' @@ -16,7 +20,9 @@ order by table_name; agg_v ¦ select a, count(*) cnt from t group by a ¦ NO 𝄀 direct_v ¦ select a, b from t ¦ NO 𝄀 dump_v ¦ select a from t ¦ NO 𝄀 -line_comment_v ¦ select a from t ¦ NO +hash_comment_v ¦ select a from t ¦ NO 𝄀 +line_comment_v ¦ select a from t ¦ NO 𝄀 +slash_comment_v ¦ select a from t ¦ NO update agg_v set cnt = 1; invalid input: cannot insert/update/delete from view update direct_v set b = 1; diff --git a/test/distributed/cases/view/information_schema_views_metadata.sql b/test/distributed/cases/view/information_schema_views_metadata.sql index 2727a4376ca9f..0f74949d0731e 100644 --- a/test/distributed/cases/view/information_schema_views_metadata.sql +++ b/test/distributed/cases/view/information_schema_views_metadata.sql @@ -10,6 +10,10 @@ create view agg_v as select a, count(*) cnt from t group by a; /*!50001 CREATE DEFINER = `root`@`%` VIEW dump_v AS select a from t */; create view line_comment_v -- migration-generated view as select a from t; +create view hash_comment_v # migration-generated view +as select a from t; +create view slash_comment_v // migration-generated view +as select a from t; select table_name, view_definition, is_updatable from information_schema.views From c37e1ea280cc55c7914907e49d764261d91caad8 Mon Sep 17 00:00:00 2001 From: iamlinjunhong <1030420200@qq.com> Date: Fri, 28 Aug 2026 20:47:31 +0800 Subject: [PATCH 6/6] test: align views upgrade entry assertions --- pkg/bootstrap/versions/v4_0_6/upgrade_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go index 3595237eb9de2..b445ac8cd34cf 100644 --- a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go +++ b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go @@ -37,7 +37,7 @@ import ( ) func TestUpgradeEntries(t *testing.T) { - require.Len(t, tenantUpgEntries, 21) + require.Len(t, tenantUpgEntries, 22) require.Len(t, clusterUpgEntries, 3) require.Equal(t, retireKafkaSinkDaemonTasks.UpgSql, clusterUpgEntries[0].UpgSql) require.Equal(t, catalog.MO_VIEW_DEPENDENCIES, clusterUpgEntries[1].TableName) @@ -139,7 +139,7 @@ func TestUpgradeEntries(t *testing.T) { require.Equal(t, sysview.InformationSchemaStatisticsDDL, statistics.UpgSql) require.Contains(t, strings.ToLower(statistics.PreSql), "drop view if exists information_schema.statistics") - views := tenantUpgEntries[20] + views := tenantUpgEntries[21] require.Equal(t, versions.MODIFY_VIEW, views.UpgType) require.Equal(t, sysview.InformationDBConst, views.Schema) require.Equal(t, "VIEWS", views.TableName) @@ -253,7 +253,7 @@ func TestUserDefinedFunctionArgumentTypesBackfillRejectsOversizedSignature(t *te } func TestForeignKeyMetadataTenantUpgradeEntries(t *testing.T) { - require.Len(t, tenantUpgEntries, 21) + require.Len(t, tenantUpgEntries, 22) for i, column := range []string{"referenced_index_name", "on_delete_origin", "on_update_origin"} { entry := tenantUpgEntries[2+i]