From 9599a90c0ca840ee7d4389816a7f23c188af348f Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 27 Aug 2026 00:12:15 +0800 Subject: [PATCH 01/17] fix(sysview): enforce object metadata visibility --- .../versions/v4_0_6/tenant_upgrade_list.go | 22 +++ pkg/bootstrap/versions/v4_0_6/upgrade_test.go | 85 +++++++++- pkg/util/sysview/predefined.go | 51 ++++-- pkg/util/sysview/predefined_test.go | 47 ++++++ ...nformation_schema_object_visibility.result | 150 ++++++++++++++++++ .../information_schema_object_visibility.sql | 125 +++++++++++++++ 6 files changed, 467 insertions(+), 13 deletions(-) create mode 100644 test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result create mode 100644 test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.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 a04c6289c072a..859097f2a5a4e 100644 --- a/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go +++ b/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go @@ -47,6 +47,28 @@ var tenantUpgEntries = []versions.UpgradeEntry{ backfillUserDefinedFunctionArgumentTypes(), addUserDefinedFunctionSignatureIndex(), upgradeInformationSchemaCollationCharacterSetApplicability(), + upgradeInformationSchemaMetadataVisibilityView("TABLES", sysview.InformationSchemaTablesDDL), + upgradeInformationSchemaMetadataVisibilityView("COLUMNS", sysview.InformationSchemaColumnsDDL), + upgradeInformationSchemaMetadataVisibilityView("STATISTICS", sysview.InformationSchemaStatisticsDDL), + upgradeInformationSchemaMetadataVisibilityTableConstraints(), +} + +func upgradeInformationSchemaMetadataVisibilityView(viewName, viewDDL string) versions.UpgradeEntry { + return versions.UpgradeEntry{ + Schema: sysview.InformationDBConst, + TableName: viewName, + UpgType: versions.MODIFY_VIEW, + UpgSql: viewDDL, + CheckFunc: checkViewDefinition(viewName, viewDDL), + PreSql: fmt.Sprintf("DROP VIEW IF EXISTS %s.%s;", sysview.InformationDBConst, viewName), + } +} + +func upgradeInformationSchemaMetadataVisibilityTableConstraints() versions.UpgradeEntry { + entry := upgradeInformationSchemaMetadataVisibilityView( + "TABLE_CONSTRAINTS", sysview.InformationSchemaTableConstraintsDDL) + entry.RequiredProtocolVersion = defines.MORPCVersion16 + return entry } // Keep this as a separate upgrade entry so tenants that already completed diff --git a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go index 2c16b575d39c9..4e447d82827e4 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, 19) + require.Len(t, tenantUpgEntries, 23) require.Len(t, clusterUpgEntries, 3) require.Equal(t, retireKafkaSinkDaemonTasks.UpgSql, clusterUpgEntries[0].UpgSql) require.Equal(t, catalog.MO_VIEW_DEPENDENCIES, clusterUpgEntries[1].TableName) @@ -126,6 +126,83 @@ func TestUpgradeEntries(t *testing.T) { require.Equal(t, sysview.InformationSchemaCollationCharacterSetApplicabilityDDL, collationApplicability.UpgSql) require.Contains(t, strings.ToLower(collationApplicability.PreSql), "drop view if exists information_schema.collation_character_set_applicability") + + metadataViews := []struct { + name string + ddl string + requiredProtocol int64 + }{ + {name: "TABLES", ddl: sysview.InformationSchemaTablesDDL}, + {name: "COLUMNS", ddl: sysview.InformationSchemaColumnsDDL}, + {name: "STATISTICS", ddl: sysview.InformationSchemaStatisticsDDL}, + {name: "TABLE_CONSTRAINTS", ddl: sysview.InformationSchemaTableConstraintsDDL, + requiredProtocol: defines.MORPCVersion16}, + } + for i, view := range metadataViews { + entry := tenantUpgEntries[19+i] + require.Equal(t, sysview.InformationDBConst, entry.Schema) + require.Equal(t, view.name, entry.TableName) + require.Equal(t, versions.MODIFY_VIEW, entry.UpgType) + require.Equal(t, view.ddl, entry.UpgSql) + require.Equal(t, view.requiredProtocol, entry.RequiredProtocolVersion) + require.Contains(t, strings.ToLower(entry.PreSql), + "drop view if exists information_schema."+strings.ToLower(view.name)) + } +} + +func TestInformationSchemaMetadataVisibilityUpgradeChecks(t *testing.T) { + views := []struct { + name string + ddl string + }{ + {name: "TABLES", ddl: sysview.InformationSchemaTablesDDL}, + {name: "COLUMNS", ddl: sysview.InformationSchemaColumnsDDL}, + {name: "STATISTICS", ddl: sysview.InformationSchemaStatisticsDDL}, + {name: "TABLE_CONSTRAINTS", ddl: sysview.InformationSchemaTableConstraintsDDL}, + } + checkErr := errors.New("check metadata view definition failed") + + for _, view := range views { + for _, state := range []struct { + name string + exists bool + definition string + checkErr error + want bool + }{ + {name: "current", exists: true, definition: view.ddl, want: true}, + {name: "old", exists: true, definition: "old view definition"}, + {name: "missing", definition: view.ddl}, + {name: "error", checkErr: checkErr}, + } { + t.Run(view.name+"/"+state.name, func(t *testing.T) { + oldCheck := versions.CheckViewDefinition + versions.CheckViewDefinition = func( + txn executor.TxnExecutor, + accountID uint32, + schema string, + viewName string, + ) (bool, string, error) { + require.Nil(t, txn) + require.Equal(t, uint32(42), accountID) + require.Equal(t, sysview.InformationDBConst, schema) + require.Equal(t, view.name, viewName) + return state.exists, state.definition, state.checkErr + } + defer func() { versions.CheckViewDefinition = oldCheck }() + + entry := upgradeInformationSchemaMetadataVisibilityView(view.name, view.ddl) + ok, err := entry.CheckFunc(nil, 42) + if state.checkErr != nil { + require.ErrorIs(t, err, state.checkErr) + require.False(t, ok) + return + } + require.NoError(t, err) + require.Equal(t, state.want, ok) + }) + } + } } func TestInformationSchemaCollationsUpgradeCheckIsExact(t *testing.T) { @@ -161,7 +238,7 @@ func TestUserDefinedFunctionArgumentTypesBackfillRejectsOversizedSignature(t *te } func TestForeignKeyMetadataTenantUpgradeEntries(t *testing.T) { - require.Len(t, tenantUpgEntries, 19) + require.Len(t, tenantUpgEntries, 23) for i, column := range []string{"referenced_index_name", "on_delete_origin", "on_update_origin"} { entry := tenantUpgEntries[2+i] @@ -535,6 +612,10 @@ func TestVersionHandleLifecycleWithNoLegacyDefinitions(t *testing.T) { return true, sysview.InformationSchemaTableConstraintsDDL, nil case "COLUMNS": return true, sysview.InformationSchemaColumnsDDL, nil + case "TABLES": + return true, sysview.InformationSchemaTablesDDL, nil + case "STATISTICS": + return true, sysview.InformationSchemaStatisticsDDL, nil default: return false, "", errors.New("unexpected view") } diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index 32b5f0fb7ebe3..e992b37b77c1c 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -153,6 +153,34 @@ var ( )` ) +// informationSchemaMetadataVisibilityCTE limits user-object metadata to the +// objects visible to the session's single active role and its inherited roles. +// System schemas remain universally visible for MySQL/tooling compatibility. +func informationSchemaMetadataVisibilityCTE() string { + return "WITH RECURSIVE __mo_active_roles(role_id) AS (" + + "SELECT cast(role_id AS bigint) FROM mo_catalog.mo_role WHERE role_name = current_role() " + + "UNION " + + "SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg " + + "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), " + + "__mo_visible_tables AS (" + + "SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, " + + "tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, " + + "tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl " + + "WHERE tbl.account_id = current_account_id() AND (" + + "tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') " + + "OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) " + + "OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id " + + "WHERE db.dat_id = tbl.reldatabase_id) " + + "OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id " + + "WHERE (rp.obj_type IN ('table','view') AND (" + + "(rp.privilege_level = '*.*' AND rp.obj_id = 0) " + + "OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) " + + "OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) " + + "OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND (" + + "(rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) " + + "OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) " +} + // `information_schema` database // They are all Tenant level system tables/system views var ( @@ -172,7 +200,7 @@ var ( "CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME " + "FROM mo_catalog.mo_foreign_keys fk" - InformationSchemaColumnsDDL = fmt.Sprintf("CREATE VIEW information_schema.COLUMNS AS select "+ + InformationSchemaColumnsDDL = fmt.Sprintf("CREATE VIEW information_schema.COLUMNS AS "+informationSchemaMetadataVisibilityCTE()+"select "+ "'def' as TABLE_CATALOG,"+ "mc.att_database as TABLE_SCHEMA,"+ "mc.att_relname AS TABLE_NAME,"+ @@ -201,7 +229,7 @@ var ( "cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,"+ "(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '%% SRID %%' "+ " then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID "+ - "from mo_catalog.mo_columns mc join mo_catalog.mo_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname "+ + "from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname "+ "where mc.account_id = current_account_id() "+ "and mc.att_is_hidden = 0 and mc.att_relname!='%s' and mc.att_relname not like '%s' and mc.attname != '%s' and mc.att_relname not like '%s' and mc.att_relname != '%s' and %s", catalog.MOAutoIncrTable, catalog.PrefixPriColName+"%", catalog.Row_ID, catalog.PartitionSubTableWildcard, catalog.MO_ACCOUNT_LOCK, catalog.NonTemporaryTableSQLPredicate("mt")) @@ -283,7 +311,7 @@ var ( "DATABASE_COLLATION varchar(64)" + ")" - InformationSchemaTablesDDL = fmt.Sprintf("CREATE VIEW information_schema.TABLES AS "+ + InformationSchemaTablesDDL = fmt.Sprintf("CREATE VIEW information_schema.TABLES AS "+informationSchemaMetadataVisibilityCTE()+ "SELECT 'def' AS TABLE_CATALOG,"+ "reldatabase AS TABLE_SCHEMA,"+ "relname AS TABLE_NAME,"+ @@ -309,7 +337,7 @@ var ( "if(relkind = 'v', NULL, 0) AS CHECKSUM,"+ "if(relkind = 'v', NULL, if(partitioned = 0, '', cast('partitioned' as varchar(256)))) AS CREATE_OPTIONS,"+ "cast(rel_comment as text) AS TABLE_COMMENT "+ - "FROM mo_catalog.mo_tables tbl "+ + "FROM __mo_visible_tables tbl "+ "WHERE tbl.account_id = current_account_id() and tbl.relname not like '%s' and %s and tbl.relname != '%s' and tbl.relkind != '%s'", catalog.IndexTableNamePrefix+"%", catalog.NonTemporaryTableSQLPredicate("tbl"), catalog.MO_ACCOUNT_LOCK, catalog.SystemPartitionRel) @@ -382,7 +410,7 @@ var ( "FROM mo_catalog.mo_tables tbl LEFT JOIN mo_catalog.mo_user usr ON tbl.creator = usr.user_id " + "WHERE tbl.account_id = current_account_id() and tbl.relkind = 'v' and tbl.reldatabase != 'information_schema'" - InformationSchemaStatisticsDDL = fmt.Sprintf("CREATE VIEW information_schema.`STATISTICS` AS "+ + InformationSchemaStatisticsDDL = fmt.Sprintf("CREATE VIEW information_schema.`STATISTICS` AS "+informationSchemaMetadataVisibilityCTE()+ "select 'def' AS `TABLE_CATALOG`,"+ "`tbl`.`reldatabase` AS `TABLE_SCHEMA`,"+ "`tbl`.`relname` AS `TABLE_NAME`,"+ @@ -402,7 +430,7 @@ var ( "if(`idx`.`is_visible`,'YES','NO') AS `IS_VISIBLE`,"+ "NULL AS `EXPRESSION` "+ "from (`mo_catalog`.`mo_indexes` `idx` "+ - "join `mo_catalog`.`mo_tables` `tbl` on (`idx`.`table_id` = `tbl`.`rel_id`)) "+ + "join `__mo_visible_tables` `tbl` on (`idx`.`table_id` = `tbl`.`rel_id`)) "+ "join `mo_catalog`.`mo_columns` `tcl` on (`idx`.`table_id` = `tcl`.`att_relname_id` and `idx`.`column_name` = `tcl`.`attname` "+ "and `tcl`.`account_id` = `tbl`.`account_id` and `tcl`.`att_database` = `tbl`.`reldatabase` and `tcl`.`att_relname` = `tbl`.`relname`) "+ "where `tbl`.`account_id` = current_account_id() and %s", catalog.NonTemporaryTableSQLPredicate("tbl")) @@ -550,7 +578,7 @@ var ( "SELECT COLLATION_NAME, CHARACTER_SET_NAME " + "FROM information_schema.COLLATIONS" - InformationSchemaTableConstraintsDDL = fmt.Sprintf("CREATE VIEW information_schema.TABLE_CONSTRAINTS AS SELECT "+ + InformationSchemaTableConstraintsDDL = fmt.Sprintf("CREATE VIEW information_schema.TABLE_CONSTRAINTS AS "+informationSchemaMetadataVisibilityCTE()+"SELECT "+ "'def' AS CONSTRAINT_CATALOG, "+ "tbl.reldatabase AS CONSTRAINT_SCHEMA, "+ "idx.name AS CONSTRAINT_NAME, "+ @@ -559,7 +587,7 @@ var ( "idx.type AS CONSTRAINT_TYPE, "+ "'YES' AS ENFORCED "+ "FROM mo_catalog.mo_indexes idx "+ - "join mo_catalog.mo_tables tbl on idx.table_id = tbl.rel_id "+ + "join __mo_visible_tables tbl on idx.table_id = tbl.rel_id "+ "where %s UNION ALL "+ "SELECT cc.constraint_catalog AS CONSTRAINT_CATALOG, "+ "cc.constraint_schema AS CONSTRAINT_SCHEMA, "+ @@ -568,9 +596,10 @@ var ( "cc.table_name AS TABLE_NAME, "+ "cc.constraint_type AS CONSTRAINT_TYPE, "+ "cc.enforced AS ENFORCED "+ - "FROM mo_check_constraints() cc", catalog.NonTemporaryTableSQLPredicate("tbl")) + "FROM mo_check_constraints() cc "+ + "join __mo_visible_tables check_tbl ON cc.constraint_schema = check_tbl.reldatabase AND cc.table_name = check_tbl.relname", catalog.NonTemporaryTableSQLPredicate("tbl")) - InformationSchemaTableConstraintsLegacyDDL = fmt.Sprintf("CREATE VIEW information_schema.TABLE_CONSTRAINTS AS SELECT "+ + InformationSchemaTableConstraintsLegacyDDL = fmt.Sprintf("CREATE VIEW information_schema.TABLE_CONSTRAINTS AS "+informationSchemaMetadataVisibilityCTE()+"SELECT "+ "'def' AS CONSTRAINT_CATALOG, "+ "tbl.reldatabase AS CONSTRAINT_SCHEMA, "+ "idx.name AS CONSTRAINT_NAME, "+ @@ -579,7 +608,7 @@ var ( "idx.type AS CONSTRAINT_TYPE, "+ "'YES' AS ENFORCED "+ "FROM mo_catalog.mo_indexes idx "+ - "join mo_catalog.mo_tables tbl on idx.table_id = tbl.rel_id "+ + "join __mo_visible_tables tbl on idx.table_id = tbl.rel_id "+ "where %s", catalog.NonTemporaryTableSQLPredicate("tbl")) InformationSchemaEventsDDL = "CREATE TABLE information_schema.EVENTS (" + diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index eb7b982ff3658..2559e951abbcc 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -50,6 +50,53 @@ func TestInformationSchemaMetadataViewsHideTemporaryTables(t *testing.T) { } } +func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { + tests := []struct { + name string + ddl string + }{ + {name: "tables", ddl: InformationSchemaTablesDDL}, + {name: "columns", ddl: InformationSchemaColumnsDDL}, + {name: "statistics", ddl: InformationSchemaStatisticsDDL}, + {name: "table constraints", ddl: InformationSchemaTableConstraintsDDL}, + {name: "legacy table constraints", ddl: InformationSchemaTableConstraintsLegacyDDL}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + for _, expected := range []string{ + "WITH RECURSIVE __mo_active_roles(role_id)", + "SELECT cast(role_id AS bigint) FROM mo_catalog.mo_role WHERE role_name = current_role()", + "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id", + "__mo_visible_tables AS", + "tbl.account_id = current_account_id()", + "tbl.owner IN (SELECT role_id FROM __mo_active_roles)", + "db.owner = ar.role_id", + "rp.obj_type IN ('table','view')", + "rp.privilege_level = '*.*'", + "rp.privilege_level IN ('d.*','*')", + "rp.privilege_level IN ('d.t','t')", + "rp.privilege_name IN ('show tables','database all','database ownership')", + } { + assert.Contains(t, test.ddl, expected) + } + assert.NotContains(t, test.ddl, "SELECT tbl.*") + + statements, err := mysql.Parse(context.Background(), test.ddl, 1) + assert.NoError(t, err) + for _, statement := range statements { + statement.Free() + } + }) + } + + assert.Contains(t, InformationSchemaTablesDDL, "FROM __mo_visible_tables tbl") + assert.Contains(t, InformationSchemaColumnsDDL, "join __mo_visible_tables mt") + assert.Contains(t, InformationSchemaStatisticsDDL, "join `__mo_visible_tables` `tbl`") + assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables tbl") + assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables check_tbl") +} + func TestInformationSchemaStatisticsDDL_ContainsIdxAlgo(t *testing.T) { assert.True(t, strings.Contains(InformationSchemaStatisticsDDL, "`idx`.`algo` AS `INDEX_TYPE`")) assert.False(t, strings.Contains(InformationSchemaStatisticsDDL, "NULL AS `INDEX_TYPE`")) diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result new file mode 100644 index 0000000000000..b1473951a5a8f --- /dev/null +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result @@ -0,0 +1,150 @@ +set global enable_privilege_cache = off; +drop database if exists metadata_visibility_db; +drop user if exists metadata_visibility_user; +drop role if exists metadata_visibility_primary, metadata_visibility_reader; +create database metadata_visibility_db; +create table metadata_visibility_db.allowed_table ( +id int primary key, +secret varchar(20) unique, +payload int, +constraint ck_allowed_payload check (payload >= 0) +); +create table metadata_visibility_db.hidden_table ( +id int primary key, +secret varchar(20) unique, +payload int, +constraint ck_hidden_payload check (payload >= 0) +); +create role metadata_visibility_primary, metadata_visibility_reader; +create user metadata_visibility_user identified by '123456' default role metadata_visibility_primary; +grant connect on account * to metadata_visibility_primary; +select count(*) = 0 as tables_hidden +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +➤ tables_hidden[-7,1,0] 𝄀 +1 +select count(*) = 0 as columns_hidden +from information_schema.columns +where table_schema = 'metadata_visibility_db'; +➤ columns_hidden[-7,1,0] 𝄀 +1 +select count(*) = 0 as statistics_hidden +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +➤ statistics_hidden[-7,1,0] 𝄀 +1 +select count(*) = 0 as constraints_hidden +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; +➤ constraints_hidden[-7,1,0] 𝄀 +1 +select +(select count(*) > 0 from information_schema.tables +where table_schema = 'information_schema') +and +(select count(*) > 0 from information_schema.columns +where table_schema = 'information_schema') as system_metadata_visible; +➤ system_metadata_visible[-7,1,0] 𝄀 +1 +grant select on table metadata_visibility_db.allowed_table to metadata_visibility_reader; +grant metadata_visibility_reader to metadata_visibility_primary; +select +(select count(*) = 1 from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_visible, +(select count(*) = 0 from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_stays_hidden; +➤ allowed_visible[-7,1,0] ¦ hidden_stays_hidden[-7,1,0] 𝄀 +1 ¦ 1 +select +(select count(*) = 3 from information_schema.columns +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_columns_visible, +(select count(*) = 0 from information_schema.columns +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_columns_hidden; +➤ allowed_columns_visible[-7,1,0] ¦ hidden_columns_hidden[-7,1,0] 𝄀 +1 ¦ 1 +select +(select count(*) > 0 from information_schema.statistics +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_statistics_visible, +(select count(*) = 0 from information_schema.statistics +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_statistics_hidden; +➤ allowed_statistics_visible[-7,1,0] ¦ hidden_statistics_hidden[-7,1,0] 𝄀 +1 ¦ 1 +select +(select count(*) > 0 from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_constraints_visible, +(select count(*) = 1 from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table' +and constraint_name = 'ck_allowed_payload') as allowed_check_visible, +(select count(*) = 0 from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_constraints_hidden; +➤ allowed_constraints_visible[-7,1,0] ¦ allowed_check_visible[-7,1,0] ¦ hidden_constraints_hidden[-7,1,0] 𝄀 +1 ¦ 1 ¦ 1 +set role public; +select count(*) = 0 as tables_hidden_after_role_switch +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +➤ tables_hidden_after_role_switch[-7,1,0] 𝄀 +1 +select count(*) = 0 as columns_hidden_after_role_switch +from information_schema.columns +where table_schema = 'metadata_visibility_db'; +➤ columns_hidden_after_role_switch[-7,1,0] 𝄀 +1 +select count(*) = 0 as statistics_hidden_after_role_switch +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +➤ statistics_hidden_after_role_switch[-7,1,0] 𝄀 +1 +select count(*) = 0 as constraints_hidden_after_role_switch +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; +➤ constraints_hidden_after_role_switch[-7,1,0] 𝄀 +1 +set role metadata_visibility_primary; +grant show tables on database metadata_visibility_db to metadata_visibility_primary; +select count(*) = 2 as database_tables_visible +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +➤ database_tables_visible[-7,1,0] 𝄀 +1 +select count(*) = 6 as database_columns_visible +from information_schema.columns +where table_schema = 'metadata_visibility_db' +and table_name in ('allowed_table', 'hidden_table'); +➤ database_columns_visible[-7,1,0] 𝄀 +1 +select count(*) > 0 as database_statistics_visible +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +➤ database_statistics_visible[-7,1,0] 𝄀 +1 +select count(*) > 0 as database_constraints_visible +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; +➤ database_constraints_visible[-7,1,0] 𝄀 +1 +select count(*) = 2 as admin_tables_visible +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +➤ admin_tables_visible[-7,1,0] 𝄀 +1 +select count(*) = 6 as admin_columns_visible +from information_schema.columns +where table_schema = 'metadata_visibility_db' +and table_name in ('allowed_table', 'hidden_table'); +➤ admin_columns_visible[-7,1,0] 𝄀 +1 +select count(*) > 0 as admin_statistics_visible +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +➤ admin_statistics_visible[-7,1,0] 𝄀 +1 +select count(*) > 0 as admin_constraints_visible +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; +➤ admin_constraints_visible[-7,1,0] 𝄀 +1 +drop database metadata_visibility_db; +drop user metadata_visibility_user; +drop role metadata_visibility_primary, metadata_visibility_reader; +set global enable_privilege_cache = on; diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql new file mode 100644 index 0000000000000..c4767e0c68762 --- /dev/null +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql @@ -0,0 +1,125 @@ +-- @label:bvt +set global enable_privilege_cache = off; + +drop database if exists metadata_visibility_db; +drop user if exists metadata_visibility_user; +drop role if exists metadata_visibility_primary, metadata_visibility_reader; + +create database metadata_visibility_db; +create table metadata_visibility_db.allowed_table ( + id int primary key, + secret varchar(20) unique, + payload int, + constraint ck_allowed_payload check (payload >= 0) +); +create table metadata_visibility_db.hidden_table ( + id int primary key, + secret varchar(20) unique, + payload int, + constraint ck_hidden_payload check (payload >= 0) +); +create role metadata_visibility_primary, metadata_visibility_reader; +create user metadata_visibility_user identified by '123456' default role metadata_visibility_primary; +grant connect on account * to metadata_visibility_primary; + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +select count(*) = 0 as tables_hidden +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +select count(*) = 0 as columns_hidden +from information_schema.columns +where table_schema = 'metadata_visibility_db'; +select count(*) = 0 as statistics_hidden +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +select count(*) = 0 as constraints_hidden +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; +select + (select count(*) > 0 from information_schema.tables + where table_schema = 'information_schema') + and + (select count(*) > 0 from information_schema.columns + where table_schema = 'information_schema') as system_metadata_visible; +-- @session + +grant select on table metadata_visibility_db.allowed_table to metadata_visibility_reader; +grant metadata_visibility_reader to metadata_visibility_primary; + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +select + (select count(*) = 1 from information_schema.tables + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_visible, + (select count(*) = 0 from information_schema.tables + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_stays_hidden; +select + (select count(*) = 3 from information_schema.columns + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_columns_visible, + (select count(*) = 0 from information_schema.columns + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_columns_hidden; +select + (select count(*) > 0 from information_schema.statistics + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_statistics_visible, + (select count(*) = 0 from information_schema.statistics + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_statistics_hidden; +select + (select count(*) > 0 from information_schema.table_constraints + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_constraints_visible, + (select count(*) = 1 from information_schema.table_constraints + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table' + and constraint_name = 'ck_allowed_payload') as allowed_check_visible, + (select count(*) = 0 from information_schema.table_constraints + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_constraints_hidden; + +set role public; +select count(*) = 0 as tables_hidden_after_role_switch +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +select count(*) = 0 as columns_hidden_after_role_switch +from information_schema.columns +where table_schema = 'metadata_visibility_db'; +select count(*) = 0 as statistics_hidden_after_role_switch +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +select count(*) = 0 as constraints_hidden_after_role_switch +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; +set role metadata_visibility_primary; +-- @session + +grant show tables on database metadata_visibility_db to metadata_visibility_primary; + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +select count(*) = 2 as database_tables_visible +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +select count(*) = 6 as database_columns_visible +from information_schema.columns +where table_schema = 'metadata_visibility_db' + and table_name in ('allowed_table', 'hidden_table'); +select count(*) > 0 as database_statistics_visible +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +select count(*) > 0 as database_constraints_visible +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; +-- @session + +select count(*) = 2 as admin_tables_visible +from information_schema.tables +where table_schema = 'metadata_visibility_db'; +select count(*) = 6 as admin_columns_visible +from information_schema.columns +where table_schema = 'metadata_visibility_db' + and table_name in ('allowed_table', 'hidden_table'); +select count(*) > 0 as admin_statistics_visible +from information_schema.statistics +where table_schema = 'metadata_visibility_db'; +select count(*) > 0 as admin_constraints_visible +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db'; + +drop database metadata_visibility_db; +drop user metadata_visibility_user; +drop role metadata_visibility_primary, metadata_visibility_reader; +set global enable_privilege_cache = on; From d488f95c45adc4fa4097dd07af7d2ca34f7f3545 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 27 Aug 2026 11:33:37 +0800 Subject: [PATCH 02/17] fix(sysview): address metadata visibility review --- pkg/sql/plan/mock.go | 15 +++++++ pkg/sql/plan/snapshot_scope_test.go | 6 --- pkg/util/sysview/predefined.go | 2 +- pkg/util/sysview/predefined_test.go | 4 +- test/distributed/cases/dml/show/show.result | 2 +- .../account_restricted.result | 4 +- ...nformation_schema_object_visibility.result | 40 ++++++++++++++----- .../information_schema_object_visibility.sql | 31 +++++++++++--- 8 files changed, 77 insertions(+), 27 deletions(-) diff --git a/pkg/sql/plan/mock.go b/pkg/sql/plan/mock.go index 2757fb8260735..fa1c4b0daec27 100644 --- a/pkg/sql/plan/mock.go +++ b/pkg/sql/plan/mock.go @@ -462,9 +462,11 @@ func NewMockCompilerContext(isDml bool) *MockCompilerContext { moSchema["mo_database"] = &Schema{ cols: []col{ + {"dat_id", types.T_uint64, false, 0, 0}, {"datname", types.T_varchar, false, 50, 0}, {"account_id", types.T_uint32, false, 0, 0}, {"dat_createsql", types.T_varchar, false, 1024, 0}, + {"owner", types.T_uint32, false, 0, 0}, {catalog.Row_ID, types.T_Rowid, false, 16, 0}, }, pks: []int{0}, @@ -490,6 +492,7 @@ func NewMockCompilerContext(isDml bool) *MockCompilerContext { {"rel_version", types.T_uint32, false, 32, 0}, {"catalog_version", types.T_uint32, false, 32, 0}, {"extra_info", types.T_varchar, false, 0, 0}, + {"rel_logical_id", types.T_uint64, false, 0, 0}, {catalog.Row_ID, types.T_Rowid, false, 16, 0}, }, pks: []int{0, 1}, @@ -558,6 +561,18 @@ func NewMockCompilerContext(isDml bool) *MockCompilerContext { {catalog.Row_ID, types.T_Rowid, false, 16, 0}, }, } + moSchema["mo_role_grant"] = &Schema{ + cols: []col{ + {"granted_id", types.T_int32, false, 0, 0}, + {"grantee_id", types.T_int32, false, 0, 0}, + {"operation_role_id", types.T_int32, false, 0, 0}, + {"operation_user_id", types.T_int32, false, 0, 0}, + {"granted_time", types.T_timestamp, false, 0, 0}, + {"with_grant_option", types.T_bool, false, 0, 0}, + {catalog.Row_ID, types.T_Rowid, false, 16, 0}, + }, + pks: []int{0, 1}, + } moSchema["mo_user_defined_function"] = &Schema{ cols: []col{ {"function_id", types.T_int32, false, 50, 0}, diff --git a/pkg/sql/plan/snapshot_scope_test.go b/pkg/sql/plan/snapshot_scope_test.go index 9a3348dee880c..88bc486b9061c 100644 --- a/pkg/sql/plan/snapshot_scope_test.go +++ b/pkg/sql/plan/snapshot_scope_test.go @@ -21,7 +21,6 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/matrixorigin/matrixone/pkg/container/types" planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/pb/timestamp" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" @@ -169,11 +168,6 @@ func TestBuildShowDatabasesRestrictsDatabaseSnapshot(t *testing.T) { }, }, } - ctx.tables["mo_database"].Cols = append(ctx.tables["mo_database"].Cols, &planpb.ColDef{ - Name: "dat_id", - Typ: planpb.Type{Id: int32(types.T_uint64)}, - }) - plan, err := buildShowDatabases(&tree.ShowDatabases{AtTsExpr: &tree.AtTimeStamp{ Type: tree.ATTIMESTAMPSNAPSHOT, SnapshotName: "snapshot", diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index e992b37b77c1c..db784a74fadf0 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -158,7 +158,7 @@ var ( // System schemas remain universally visible for MySQL/tooling compatibility. func informationSchemaMetadataVisibilityCTE() string { return "WITH RECURSIVE __mo_active_roles(role_id) AS (" + - "SELECT cast(role_id AS bigint) FROM mo_catalog.mo_role WHERE role_name = current_role() " + + "SELECT cast(current_role_id() AS bigint) " + "UNION " + "SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg " + "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), " + diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index 2559e951abbcc..8510fb8b8285c 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -66,7 +66,7 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { t.Run(test.name, func(t *testing.T) { for _, expected := range []string{ "WITH RECURSIVE __mo_active_roles(role_id)", - "SELECT cast(role_id AS bigint) FROM mo_catalog.mo_role WHERE role_name = current_role()", + "SELECT cast(current_role_id() AS bigint)", "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id", "__mo_visible_tables AS", "tbl.account_id = current_account_id()", @@ -81,6 +81,8 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { assert.Contains(t, test.ddl, expected) } assert.NotContains(t, test.ddl, "SELECT tbl.*") + assert.NotContains(t, test.ddl, "current_role()") + assert.NotContains(t, test.ddl, "FROM mo_catalog.mo_role ") statements, err := mysql.Parse(context.Background(), test.ddl, 1) assert.NoError(t, err) diff --git a/test/distributed/cases/dml/show/show.result b/test/distributed/cases/dml/show/show.result index 2efe88b850cee..5489b9699ff85 100644 --- a/test/distributed/cases/dml/show/show.result +++ b/test/distributed/cases/dml/show/show.result @@ -525,5 +525,5 @@ create database test; use test; SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join mo_catalog.mo_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH RECURSIVE __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci drop database test; diff --git a/test/distributed/cases/zz_accesscontrol/account_restricted.result b/test/distributed/cases/zz_accesscontrol/account_restricted.result index 29066b428615c..b7c4207cfee47 100644 --- a/test/distributed/cases/zz_accesscontrol/account_restricted.result +++ b/test/distributed/cases/zz_accesscontrol/account_restricted.result @@ -158,7 +158,7 @@ GRANT values ON table *.* `admin`@`localhost` GRANT connect ON account `admin`@`localhost` SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join mo_catalog.mo_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH RECURSIVE __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 @@ -271,7 +271,7 @@ show grants for 'hnadmin'@'localhost'; Grants for hnadmin@localhost SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join mo_catalog.mo_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH RECURSIVE __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result index b1473951a5a8f..18f3b5fa654dc 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result @@ -79,28 +79,48 @@ and constraint_name = 'ck_allowed_payload') as allowed_check_visible, where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_constraints_hidden; ➤ allowed_constraints_visible[-7,1,0] ¦ allowed_check_visible[-7,1,0] ¦ hidden_constraints_hidden[-7,1,0] 𝄀 1 ¦ 1 ¦ 1 -set role public; -select count(*) = 0 as tables_hidden_after_role_switch +alter role metadata_visibility_primary rename to metadata_visibility_primary_renamed; +select count(*) = 1 as table_visible_after_active_role_rename +from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +➤ table_visible_after_active_role_rename[-7,1,0] 𝄀 +1 +select count(*) = 3 as columns_visible_after_active_role_rename +from information_schema.columns +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +➤ columns_visible_after_active_role_rename[-7,1,0] 𝄀 +1 +select count(*) > 0 as statistics_visible_after_active_role_rename +from information_schema.statistics +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +➤ statistics_visible_after_active_role_rename[-7,1,0] 𝄀 +1 +select count(*) > 0 as constraints_visible_after_active_role_rename +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +➤ constraints_visible_after_active_role_rename[-7,1,0] 𝄀 +1 +alter role metadata_visibility_primary_renamed rename to metadata_visibility_primary; +select count(*) = 0 as tables_hidden_with_public_role from information_schema.tables where table_schema = 'metadata_visibility_db'; -➤ tables_hidden_after_role_switch[-7,1,0] 𝄀 +➤ tables_hidden_with_public_role[-7,1,0] 𝄀 1 -select count(*) = 0 as columns_hidden_after_role_switch +select count(*) = 0 as columns_hidden_with_public_role from information_schema.columns where table_schema = 'metadata_visibility_db'; -➤ columns_hidden_after_role_switch[-7,1,0] 𝄀 +➤ columns_hidden_with_public_role[-7,1,0] 𝄀 1 -select count(*) = 0 as statistics_hidden_after_role_switch +select count(*) = 0 as statistics_hidden_with_public_role from information_schema.statistics where table_schema = 'metadata_visibility_db'; -➤ statistics_hidden_after_role_switch[-7,1,0] 𝄀 +➤ statistics_hidden_with_public_role[-7,1,0] 𝄀 1 -select count(*) = 0 as constraints_hidden_after_role_switch +select count(*) = 0 as constraints_hidden_with_public_role from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; -➤ constraints_hidden_after_role_switch[-7,1,0] 𝄀 +➤ constraints_hidden_with_public_role[-7,1,0] 𝄀 1 -set role metadata_visibility_primary; grant show tables on database metadata_visibility_db to metadata_visibility_primary; select count(*) = 2 as database_tables_visible from information_schema.tables diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql index c4767e0c68762..4122464fb25c8 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql @@ -70,21 +70,40 @@ select and constraint_name = 'ck_allowed_payload') as allowed_check_visible, (select count(*) = 0 from information_schema.table_constraints where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_constraints_hidden; +-- @session + +alter role metadata_visibility_primary rename to metadata_visibility_primary_renamed; + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +select count(*) = 1 as table_visible_after_active_role_rename +from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +select count(*) = 3 as columns_visible_after_active_role_rename +from information_schema.columns +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +select count(*) > 0 as statistics_visible_after_active_role_rename +from information_schema.statistics +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +select count(*) > 0 as constraints_visible_after_active_role_rename +from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; +-- @session + +alter role metadata_visibility_primary_renamed rename to metadata_visibility_primary; -set role public; -select count(*) = 0 as tables_hidden_after_role_switch +-- @session:id=3&user=sys:metadata_visibility_user:public&password=123456 +select count(*) = 0 as tables_hidden_with_public_role from information_schema.tables where table_schema = 'metadata_visibility_db'; -select count(*) = 0 as columns_hidden_after_role_switch +select count(*) = 0 as columns_hidden_with_public_role from information_schema.columns where table_schema = 'metadata_visibility_db'; -select count(*) = 0 as statistics_hidden_after_role_switch +select count(*) = 0 as statistics_hidden_with_public_role from information_schema.statistics where table_schema = 'metadata_visibility_db'; -select count(*) = 0 as constraints_hidden_after_role_switch +select count(*) = 0 as constraints_hidden_with_public_role from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; -set role metadata_visibility_primary; -- @session grant show tables on database metadata_visibility_db to metadata_visibility_primary; From 3113ae57fb8acbd0cae607b0b42d93014e7072fd Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 27 Aug 2026 12:08:33 +0800 Subject: [PATCH 03/17] fix(plan): keep current role dynamic in cached plans --- pkg/sql/plan/function/function_test.go | 1 + pkg/sql/plan/function/list_builtIn.go | 5 +- ...nformation_schema_object_visibility.result | 60 ++++++++++++------- .../information_schema_object_visibility.sql | 49 +++++++++++---- 4 files changed, 80 insertions(+), 35 deletions(-) diff --git a/pkg/sql/plan/function/function_test.go b/pkg/sql/plan/function/function_test.go index 849a6a0f2eb48..7c5944fe973bb 100644 --- a/pkg/sql/plan/function/function_test.go +++ b/pkg/sql/plan/function/function_test.go @@ -1067,6 +1067,7 @@ func TestGetFunctionIsVolatileOrRealTimeRelatedByName(t *testing.T) { assert.True(t, GetFunctionIsVolatileOrRealTimeRelatedByName("uuid")) assert.True(t, GetFunctionIsVolatileOrRealTimeRelatedByName("now")) assert.True(t, GetFunctionIsVolatileOrRealTimeRelatedByName("current_timestamp")) + assert.True(t, GetFunctionIsVolatileOrRealTimeRelatedByName("current_role_id")) assert.False(t, GetFunctionIsVolatileOrRealTimeRelatedByName("abs")) assert.False(t, GetFunctionIsVolatileOrRealTimeRelatedByName("unknown_function")) } diff --git a/pkg/sql/plan/function/list_builtIn.go b/pkg/sql/plan/function/list_builtIn.go index 40b2d73cd55b4..9220061930b10 100644 --- a/pkg/sql/plan/function/list_builtIn.go +++ b/pkg/sql/plan/function/list_builtIn.go @@ -13101,8 +13101,9 @@ var supportedOthersBuiltIns = []FuncNew{ Overloads: []overload{ { - overloadId: 0, - args: []types.T{}, + overloadId: 0, + args: []types.T{}, + realTimeRelated: true, retType: func(parameters []types.Type) types.Type { return types.T_uint32.ToType() }, diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result index 18f3b5fa654dc..6c02f72fce583 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result @@ -101,26 +101,46 @@ where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; ➤ constraints_visible_after_active_role_rename[-7,1,0] 𝄀 1 alter role metadata_visibility_primary_renamed rename to metadata_visibility_primary; -select count(*) = 0 as tables_hidden_with_public_role -from information_schema.tables -where table_schema = 'metadata_visibility_db'; -➤ tables_hidden_with_public_role[-7,1,0] 𝄀 -1 -select count(*) = 0 as columns_hidden_with_public_role -from information_schema.columns -where table_schema = 'metadata_visibility_db'; -➤ columns_hidden_with_public_role[-7,1,0] 𝄀 -1 -select count(*) = 0 as statistics_hidden_with_public_role -from information_schema.statistics -where table_schema = 'metadata_visibility_db'; -➤ statistics_hidden_with_public_role[-7,1,0] 𝄀 -1 -select count(*) = 0 as constraints_hidden_with_public_role -from information_schema.table_constraints -where table_schema = 'metadata_visibility_db'; -➤ constraints_hidden_with_public_role[-7,1,0] 𝄀 -1 +prepare metadata_visibility_prepared from "select +(select count(*) from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as table_count, +(select count(*) from information_schema.columns +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as column_count, +(select count(*) from information_schema.statistics +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as statistic_count, +(select count(*) from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count"; +execute metadata_visibility_prepared; +➤ table_count[-5,64,0] ¦ column_count[-5,64,0] ¦ statistic_count[-5,64,0] ¦ constraint_count[-5,64,0] 𝄀 +1 ¦ 3 ¦ 2 ¦ 3 +select +(select count(*) from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as table_count, +(select count(*) from information_schema.columns +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as column_count, +(select count(*) from information_schema.statistics +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as statistic_count, +(select count(*) from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count; +➤ table_count[-5,64,0] ¦ column_count[-5,64,0] ¦ statistic_count[-5,64,0] ¦ constraint_count[-5,64,0] 𝄀 +1 ¦ 3 ¦ 2 ¦ 3 +set role public; +execute metadata_visibility_prepared; +➤ table_count[-5,64,0] ¦ column_count[-5,64,0] ¦ statistic_count[-5,64,0] ¦ constraint_count[-5,64,0] 𝄀 +0 ¦ 0 ¦ 0 ¦ 0 +select +(select count(*) from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as table_count, +(select count(*) from information_schema.columns +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as column_count, +(select count(*) from information_schema.statistics +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as statistic_count, +(select count(*) from information_schema.table_constraints +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count; +➤ table_count[-5,64,0] ¦ column_count[-5,64,0] ¦ statistic_count[-5,64,0] ¦ constraint_count[-5,64,0] 𝄀 +0 ¦ 0 ¦ 0 ¦ 0 +deallocate prepare metadata_visibility_prepared; +set role metadata_visibility_primary; grant show tables on database metadata_visibility_db to metadata_visibility_primary; select count(*) = 2 as database_tables_visible from information_schema.tables diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql index 4122464fb25c8..63d33aadef0c3 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql @@ -91,19 +91,42 @@ where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table'; alter role metadata_visibility_primary_renamed rename to metadata_visibility_primary; --- @session:id=3&user=sys:metadata_visibility_user:public&password=123456 -select count(*) = 0 as tables_hidden_with_public_role -from information_schema.tables -where table_schema = 'metadata_visibility_db'; -select count(*) = 0 as columns_hidden_with_public_role -from information_schema.columns -where table_schema = 'metadata_visibility_db'; -select count(*) = 0 as statistics_hidden_with_public_role -from information_schema.statistics -where table_schema = 'metadata_visibility_db'; -select count(*) = 0 as constraints_hidden_with_public_role -from information_schema.table_constraints -where table_schema = 'metadata_visibility_db'; +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +prepare metadata_visibility_prepared from "select + (select count(*) from information_schema.tables + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as table_count, + (select count(*) from information_schema.columns + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as column_count, + (select count(*) from information_schema.statistics + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as statistic_count, + (select count(*) from information_schema.table_constraints + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count"; +execute metadata_visibility_prepared; +select + (select count(*) from information_schema.tables + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as table_count, + (select count(*) from information_schema.columns + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as column_count, + (select count(*) from information_schema.statistics + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as statistic_count, + (select count(*) from information_schema.table_constraints + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count; +set role public; +-- @session + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +execute metadata_visibility_prepared; +select + (select count(*) from information_schema.tables + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as table_count, + (select count(*) from information_schema.columns + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as column_count, + (select count(*) from information_schema.statistics + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as statistic_count, + (select count(*) from information_schema.table_constraints + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count; +deallocate prepare metadata_visibility_prepared; +set role metadata_visibility_primary; -- @session grant show tables on database metadata_visibility_db to metadata_visibility_primary; From b5946fff2d9bd643e366e250450d4e722538434f Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 27 Aug 2026 17:14:55 +0800 Subject: [PATCH 04/17] fix(sysview): close metadata visibility bypasses --- .../versions/v4_0_6/tenant_upgrade_list.go | 12 ++++ pkg/bootstrap/versions/v4_0_6/upgrade_test.go | 19 ++++- pkg/sql/compile/compile.go | 20 +++++- pkg/sql/compile/compile2_test.go | 19 +++++ pkg/util/sysview/predefined.go | 30 ++++---- pkg/util/sysview/predefined_test.go | 11 +++ ...nformation_schema_object_visibility.result | 71 ++++++++++++++++++- .../information_schema_object_visibility.sql | 63 +++++++++++++++- 8 files changed, 221 insertions(+), 24 deletions(-) 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 859097f2a5a4e..5aacdf4d3c5c4 100644 --- a/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go +++ b/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go @@ -51,6 +51,11 @@ var tenantUpgEntries = []versions.UpgradeEntry{ upgradeInformationSchemaMetadataVisibilityView("COLUMNS", sysview.InformationSchemaColumnsDDL), upgradeInformationSchemaMetadataVisibilityView("STATISTICS", sysview.InformationSchemaStatisticsDDL), upgradeInformationSchemaMetadataVisibilityTableConstraints(), + upgradeInformationSchemaMetadataVisibilityView("KEY_COLUMN_USAGE", sysview.InformationSchemaKeyColumnUsageDDL), + upgradeInformationSchemaMetadataVisibilityView("REFERENTIAL_CONSTRAINTS", sysview.InformationSchemaReferentialConstraintsDDL), + upgradeInformationSchemaMetadataVisibilityCheckConstraints(), + upgradeInformationSchemaMetadataVisibilityView("VIEWS", sysview.InformationSchemaViewsDDL), + upgradeInformationSchemaMetadataVisibilityView("PARTITIONS", sysview.InformationSchemaPartitionsDDL), } func upgradeInformationSchemaMetadataVisibilityView(viewName, viewDDL string) versions.UpgradeEntry { @@ -71,6 +76,13 @@ func upgradeInformationSchemaMetadataVisibilityTableConstraints() versions.Upgra return entry } +func upgradeInformationSchemaMetadataVisibilityCheckConstraints() versions.UpgradeEntry { + entry := upgradeInformationSchemaMetadataVisibilityView( + "CHECK_CONSTRAINTS", sysview.InformationSchemaCheckConstraintsDDL) + entry.RequiredProtocolVersion = defines.MORPCVersion16 + return entry +} + // Keep this as a separate upgrade entry so tenants that already completed // v4.0.6 refresh COLUMNS and expose MySQL-compatible base DATA_TYPE names. func upgradeInformationSchemaColumns() versions.UpgradeEntry { diff --git a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go index 4e447d82827e4..cfa114eeed236 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, 23) + require.Len(t, tenantUpgEntries, 28) require.Len(t, clusterUpgEntries, 3) require.Equal(t, retireKafkaSinkDaemonTasks.UpgSql, clusterUpgEntries[0].UpgSql) require.Equal(t, catalog.MO_VIEW_DEPENDENCIES, clusterUpgEntries[1].TableName) @@ -137,6 +137,12 @@ func TestUpgradeEntries(t *testing.T) { {name: "STATISTICS", ddl: sysview.InformationSchemaStatisticsDDL}, {name: "TABLE_CONSTRAINTS", ddl: sysview.InformationSchemaTableConstraintsDDL, requiredProtocol: defines.MORPCVersion16}, + {name: "KEY_COLUMN_USAGE", ddl: sysview.InformationSchemaKeyColumnUsageDDL}, + {name: "REFERENTIAL_CONSTRAINTS", ddl: sysview.InformationSchemaReferentialConstraintsDDL}, + {name: "CHECK_CONSTRAINTS", ddl: sysview.InformationSchemaCheckConstraintsDDL, + requiredProtocol: defines.MORPCVersion16}, + {name: "VIEWS", ddl: sysview.InformationSchemaViewsDDL}, + {name: "PARTITIONS", ddl: sysview.InformationSchemaPartitionsDDL}, } for i, view := range metadataViews { entry := tenantUpgEntries[19+i] @@ -159,6 +165,11 @@ func TestInformationSchemaMetadataVisibilityUpgradeChecks(t *testing.T) { {name: "COLUMNS", ddl: sysview.InformationSchemaColumnsDDL}, {name: "STATISTICS", ddl: sysview.InformationSchemaStatisticsDDL}, {name: "TABLE_CONSTRAINTS", ddl: sysview.InformationSchemaTableConstraintsDDL}, + {name: "KEY_COLUMN_USAGE", ddl: sysview.InformationSchemaKeyColumnUsageDDL}, + {name: "REFERENTIAL_CONSTRAINTS", ddl: sysview.InformationSchemaReferentialConstraintsDDL}, + {name: "CHECK_CONSTRAINTS", ddl: sysview.InformationSchemaCheckConstraintsDDL}, + {name: "VIEWS", ddl: sysview.InformationSchemaViewsDDL}, + {name: "PARTITIONS", ddl: sysview.InformationSchemaPartitionsDDL}, } checkErr := errors.New("check metadata view definition failed") @@ -238,7 +249,7 @@ func TestUserDefinedFunctionArgumentTypesBackfillRejectsOversizedSignature(t *te } func TestForeignKeyMetadataTenantUpgradeEntries(t *testing.T) { - require.Len(t, tenantUpgEntries, 23) + require.Len(t, tenantUpgEntries, 28) for i, column := range []string{"referenced_index_name", "on_delete_origin", "on_update_origin"} { entry := tenantUpgEntries[2+i] @@ -616,6 +627,10 @@ func TestVersionHandleLifecycleWithNoLegacyDefinitions(t *testing.T) { return true, sysview.InformationSchemaTablesDDL, nil case "STATISTICS": return true, sysview.InformationSchemaStatisticsDDL, nil + case "VIEWS": + return true, sysview.InformationSchemaViewsDDL, nil + case "PARTITIONS": + return true, sysview.InformationSchemaPartitionsDDL, nil default: return false, "", errors.New("unexpected view") } diff --git a/pkg/sql/compile/compile.go b/pkg/sql/compile/compile.go index 9fcd99880ccfa..fcd02eb0b5ebf 100644 --- a/pkg/sql/compile/compile.go +++ b/pkg/sql/compile/compile.go @@ -1261,8 +1261,11 @@ func (c *Compile) compileQuery(qry *plan.Query) ([]*Scope, error) { plan2.CalcQueryDOP(c.pn, ncpu, len(c.cnList), c.execType) c.initAnalyzeModule(qry) - // deal with sink scan first. - for i := len(qry.Steps) - 1; i >= 0; i-- { + firstStep := c.firstStepToCompile(qry) + // Deal with sink scans first. A final literal LIMIT 0 has no demand for + // producer steps; compiling recursive CTE producers would otherwise leave + // their pipelines waiting for consumers that the LIMIT fast path never builds. + for i := len(qry.Steps) - 1; i >= firstStep; i-- { err := c.compileSinkScan(qry, qry.Steps[i]) if err != nil { return nil, err @@ -1275,7 +1278,7 @@ func (c *Compile) compileQuery(qry *plan.Query) ([]*Scope, error) { ReleaseScopes(steps) } }() - for i := len(qry.Steps) - 1; i >= 0; i-- { + for i := len(qry.Steps) - 1; i >= firstStep; i-- { var scopes []*Scope scopes, err = c.compilePlanScope(int32(i), qry.Steps[i], qry.Nodes) if err != nil { @@ -2028,6 +2031,17 @@ func (c *Compile) compilePlanScopeWithUnionAllDemand( } } +func (c *Compile) firstStepToCompile(qry *plan.Query) int { + if qry == nil || len(qry.Steps) == 0 { + return 0 + } + finalStep := qry.Steps[len(qry.Steps)-1] + if finalStep >= 0 && int(finalStep) < len(qry.Nodes) && c.canUseLiteralLimitZeroFastPath(qry.Nodes[finalStep]) { + return len(qry.Steps) - 1 + } + return 0 +} + func (c *Compile) canUseLiteralLimitZeroFastPath(node *plan.Node) bool { if node == nil || node.Limit == nil || c.ownsFoundRows(node) { return false diff --git a/pkg/sql/compile/compile2_test.go b/pkg/sql/compile/compile2_test.go index a0784bd0e0b20..dcaf8ba7f1ead 100644 --- a/pkg/sql/compile/compile2_test.go +++ b/pkg/sql/compile/compile2_test.go @@ -98,6 +98,25 @@ func TestStatementHasSQLCalcFoundRowsPagination(t *testing.T) { } } +func TestLiteralLimitZeroSkipsUnconsumedProducerSteps(t *testing.T) { + c := newLazyUnionAllTestCompile(t) + producer := &plan.Node{NodeType: plan.Node_SINK} + final := &plan.Node{ + NodeType: plan.Node_PROJECT, + Limit: plan2.MakePlan2Uint64ConstExprWithType(0), + } + qry := &plan.Query{Nodes: []*plan.Node{producer, final}, Steps: []int32{0, 1}} + require.Equal(t, 1, c.firstStepToCompile(qry)) + + final.Limit = plan2.MakePlan2Uint64ConstExprWithType(1) + require.Zero(t, c.firstStepToCompile(qry)) + + c.stmt = sqlCalcFoundRowsTestStatement() + c.foundRowsOwnerNode = final + final.Limit = plan2.MakePlan2Uint64ConstExprWithType(0) + require.Zero(t, c.firstStepToCompile(qry)) +} + func TestSQLCalcFoundRowsDisablesLiteralLimitZeroFastPath(t *testing.T) { c := newLazyUnionAllTestCompile(t) node := &plan.Node{Limit: plan2.MakePlan2Uint64ConstExprWithType(0)} diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index db784a74fadf0..b2e2f3349de2f 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -165,7 +165,7 @@ func informationSchemaMetadataVisibilityCTE() string { "__mo_visible_tables AS (" + "SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, " + "tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, " + - "tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl " + + "tbl.owner, tbl.creator, tbl.`constraint` FROM mo_catalog.mo_tables tbl " + "WHERE tbl.account_id = current_account_id() AND (" + "tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') " + "OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) " + @@ -185,7 +185,7 @@ func informationSchemaMetadataVisibilityCTE() string { // They are all Tenant level system tables/system views var ( InformationSchemaKeyColumnUsageDDL = "CREATE VIEW information_schema.KEY_COLUMN_USAGE AS " + - "SELECT " + + informationSchemaMetadataVisibilityCTE() + "SELECT " + "CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, " + "CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, " + "CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, " + @@ -198,7 +198,8 @@ var ( "CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, " + "CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, " + "CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME " + - "FROM mo_catalog.mo_foreign_keys fk" + "FROM mo_catalog.mo_foreign_keys fk " + + "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id" InformationSchemaColumnsDDL = fmt.Sprintf("CREATE VIEW information_schema.COLUMNS AS "+informationSchemaMetadataVisibilityCTE()+"select "+ "'def' as TABLE_CATALOG,"+ @@ -342,7 +343,7 @@ var ( catalog.IndexTableNamePrefix+"%", catalog.NonTemporaryTableSQLPredicate("tbl"), catalog.MO_ACCOUNT_LOCK, catalog.SystemPartitionRel) InformationSchemaPartitionsDDL = "CREATE VIEW information_schema.`PARTITIONS` AS " + - "SELECT " + + informationSchemaMetadataVisibilityCTE() + "SELECT " + "'def' AS `TABLE_CATALOG`," + "`tbl`.`reldatabase` AS `TABLE_SCHEMA`," + "`tbl`.`relname` AS `TABLE_NAME`," + @@ -391,13 +392,13 @@ var ( "'' AS `PARTITION_COMMENT`," + "'default' AS `NODEGROUP`," + "NULL AS `TABLESPACE_NAME` " + - "FROM `mo_catalog`.`mo_tables` `tbl` " + + "FROM `__mo_visible_tables` `tbl` " + "JOIN `mo_catalog`.`mo_partition_metadata` `meta` ON `meta`.`table_id` = `tbl`.`rel_id` " + "JOIN `mo_catalog`.`mo_partition_tables` `pt` ON `pt`.`primary_table_id` = `tbl`.`rel_id` " + "WHERE `tbl`.`account_id` = current_account_id()" InformationSchemaViewsDDL = "CREATE VIEW information_schema.VIEWS AS " + - "SELECT 'def' AS `TABLE_CATALOG`," + + informationSchemaMetadataVisibilityCTE() + "SELECT 'def' AS `TABLE_CATALOG`," + "tbl.reldatabase AS `TABLE_SCHEMA`," + "tbl.relname AS `TABLE_NAME`," + "tbl.rel_createsql AS `VIEW_DEFINITION`," + @@ -407,7 +408,7 @@ var ( "'DEFINER' AS `SECURITY_TYPE`," + "'utf8mb4' AS `CHARACTER_SET_CLIENT`," + "'" + DefaultCollationForCharset("utf8mb4") + "' AS `COLLATION_CONNECTION` " + - "FROM mo_catalog.mo_tables tbl LEFT JOIN mo_catalog.mo_user usr ON tbl.creator = usr.user_id " + + "FROM __mo_visible_tables tbl LEFT JOIN mo_catalog.mo_user usr ON tbl.creator = usr.user_id " + "WHERE tbl.account_id = current_account_id() and tbl.relkind = 'v' and tbl.reldatabase != 'information_schema'" InformationSchemaStatisticsDDL = fmt.Sprintf("CREATE VIEW information_schema.`STATISTICS` AS "+informationSchemaMetadataVisibilityCTE()+ @@ -436,7 +437,7 @@ var ( "where `tbl`.`account_id` = current_account_id() and %s", catalog.NonTemporaryTableSQLPredicate("tbl")) InformationSchemaReferentialConstraintsDDL = "CREATE VIEW information_schema.REFERENTIAL_CONSTRAINTS AS " + - "SELECT " + + informationSchemaMetadataVisibilityCTE() + "SELECT " + "'def' AS CONSTRAINT_CATALOG, " + "fk.db_name AS CONSTRAINT_SCHEMA, " + "fk.constraint_name AS CONSTRAINT_NAME, " + @@ -449,21 +450,24 @@ var ( "fk.table_name AS TABLE_NAME, " + "fk.refer_table_name AS REFERENCED_TABLE_NAME " + "FROM (" + - "SELECT db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name " + + "SELECT table_id, db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name " + "FROM mo_catalog.mo_foreign_keys " + - "GROUP BY db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name" + - ") fk" + "GROUP BY table_id, db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name" + + ") fk " + + "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id" // CHECK_CONSTRAINTS is backed by a table function because CHECK metadata is // stored in the serialized SchemaExtra of each table. The function decodes // that metadata at query time and applies the current tenant's visibility. InformationSchemaCheckConstraintsDDL = "CREATE VIEW information_schema.CHECK_CONSTRAINTS AS " + - "SELECT " + + informationSchemaMetadataVisibilityCTE() + "SELECT " + "cc.constraint_catalog AS CONSTRAINT_CATALOG, " + "cc.constraint_schema AS CONSTRAINT_SCHEMA, " + "cc.constraint_name AS CONSTRAINT_NAME, " + "cc.check_clause AS CHECK_CLAUSE " + - "FROM mo_check_constraints() cc" + "FROM mo_check_constraints() cc " + + "JOIN __mo_visible_tables check_tbl " + + "ON cc.constraint_schema = check_tbl.reldatabase AND cc.table_name = check_tbl.relname" InformationSchemaEnginesDDL = "CREATE TABLE information_schema.ENGINES (" + "ENGINE varchar(64)," + diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index 8510fb8b8285c..6f34103ec4ab5 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -60,6 +60,11 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { {name: "statistics", ddl: InformationSchemaStatisticsDDL}, {name: "table constraints", ddl: InformationSchemaTableConstraintsDDL}, {name: "legacy table constraints", ddl: InformationSchemaTableConstraintsLegacyDDL}, + {name: "key column usage", ddl: InformationSchemaKeyColumnUsageDDL}, + {name: "referential constraints", ddl: InformationSchemaReferentialConstraintsDDL}, + {name: "check constraints", ddl: InformationSchemaCheckConstraintsDDL}, + {name: "views", ddl: InformationSchemaViewsDDL}, + {name: "partitions", ddl: InformationSchemaPartitionsDDL}, } for _, test := range tests { @@ -97,6 +102,12 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { assert.Contains(t, InformationSchemaStatisticsDDL, "join `__mo_visible_tables` `tbl`") assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables tbl") assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables check_tbl") + assert.Contains(t, InformationSchemaKeyColumnUsageDDL, "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id") + assert.Contains(t, InformationSchemaReferentialConstraintsDDL, + "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id") + assert.Contains(t, InformationSchemaCheckConstraintsDDL, "JOIN __mo_visible_tables check_tbl") + assert.Contains(t, InformationSchemaViewsDDL, "FROM __mo_visible_tables tbl") + assert.Contains(t, InformationSchemaPartitionsDDL, "FROM `__mo_visible_tables` `tbl`") } func TestInformationSchemaStatisticsDDL_ContainsIdxAlgo(t *testing.T) { diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result index 6c02f72fce583..7dd44446a1654 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result @@ -8,13 +8,19 @@ id int primary key, secret varchar(20) unique, payload int, constraint ck_allowed_payload check (payload >= 0) +) partition by hash(id) partitions 2; +create table metadata_visibility_db.hidden_parent ( +id int primary key ); create table metadata_visibility_db.hidden_table ( id int primary key, secret varchar(20) unique, payload int, -constraint ck_hidden_payload check (payload >= 0) +constraint ck_hidden_payload check (payload >= 0), +constraint fk_hidden_parent foreign key (id) references metadata_visibility_db.hidden_parent(id) ); +create view metadata_visibility_db.hidden_view as +select id, secret, payload from metadata_visibility_db.hidden_table; create role metadata_visibility_primary, metadata_visibility_reader; create user metadata_visibility_user identified by '123456' default role metadata_visibility_primary; grant connect on account * to metadata_visibility_primary; @@ -39,6 +45,19 @@ where table_schema = 'metadata_visibility_db'; ➤ constraints_hidden[-7,1,0] 𝄀 1 select +(select count(*) = 0 from information_schema.check_constraints +where constraint_schema = 'metadata_visibility_db') as check_constraints_hidden, +(select count(*) = 0 from information_schema.key_column_usage +where table_schema = 'metadata_visibility_db') as key_column_usage_hidden, +(select count(*) = 0 from information_schema.referential_constraints +where constraint_schema = 'metadata_visibility_db') as referential_constraints_hidden, +(select count(*) = 0 from information_schema.views +where table_schema = 'metadata_visibility_db') as views_hidden, +(select count(*) = 0 from information_schema.partitions +where table_schema = 'metadata_visibility_db') as partitions_hidden; +➤ check_constraints_hidden[-7,1,0] ¦ key_column_usage_hidden[-7,1,0] ¦ referential_constraints_hidden[-7,1,0] ¦ views_hidden[-7,1,0] ¦ partitions_hidden[-7,1,0] 𝄀 +1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 +select (select count(*) > 0 from information_schema.tables where table_schema = 'information_schema') and @@ -79,6 +98,24 @@ and constraint_name = 'ck_allowed_payload') as allowed_check_visible, where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_constraints_hidden; ➤ allowed_constraints_visible[-7,1,0] ¦ allowed_check_visible[-7,1,0] ¦ hidden_constraints_hidden[-7,1,0] 𝄀 1 ¦ 1 ¦ 1 +select +(select count(*) = 1 from information_schema.check_constraints +where constraint_schema = 'metadata_visibility_db' and constraint_name = 'ck_allowed_payload') +as allowed_check_metadata_visible, +(select count(*) > 0 from information_schema.partitions +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') +as allowed_partition_metadata_visible, +(select count(*) = 0 from information_schema.key_column_usage +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') +as hidden_fk_columns_hidden, +(select count(*) = 0 from information_schema.referential_constraints +where constraint_schema = 'metadata_visibility_db' and table_name = 'hidden_table') +as hidden_fk_constraint_hidden, +(select count(*) = 0 from information_schema.views +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') +as hidden_view_hidden; +➤ allowed_check_metadata_visible[-7,1,0] ¦ allowed_partition_metadata_visible[-7,1,0] ¦ hidden_fk_columns_hidden[-7,1,0] ¦ hidden_fk_constraint_hidden[-7,1,0] ¦ hidden_view_hidden[-7,1,0] 𝄀 +1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 alter role metadata_visibility_primary rename to metadata_visibility_primary_renamed; select count(*) = 1 as table_visible_after_active_role_rename from information_schema.tables @@ -144,7 +181,8 @@ set role metadata_visibility_primary; grant show tables on database metadata_visibility_db to metadata_visibility_primary; select count(*) = 2 as database_tables_visible from information_schema.tables -where table_schema = 'metadata_visibility_db'; +where table_schema = 'metadata_visibility_db' +and table_name in ('allowed_table', 'hidden_table'); ➤ database_tables_visible[-7,1,0] 𝄀 1 select count(*) = 6 as database_columns_visible @@ -163,9 +201,23 @@ from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; ➤ database_constraints_visible[-7,1,0] 𝄀 1 +select +(select count(*) > 0 from information_schema.check_constraints +where constraint_schema = 'metadata_visibility_db') as database_checks_visible, +(select count(*) > 0 from information_schema.key_column_usage +where table_schema = 'metadata_visibility_db') as database_fk_columns_visible, +(select count(*) > 0 from information_schema.referential_constraints +where constraint_schema = 'metadata_visibility_db') as database_fk_constraints_visible, +(select count(*) = 1 from information_schema.views +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as database_view_visible, +(select count(*) > 0 from information_schema.partitions +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as database_partitions_visible; +➤ database_checks_visible[-7,1,0] ¦ database_fk_columns_visible[-7,1,0] ¦ database_fk_constraints_visible[-7,1,0] ¦ database_view_visible[-7,1,0] ¦ database_partitions_visible[-7,1,0] 𝄀 +1 ¦ 0 ¦ 0 ¦ 1 ¦ 1 select count(*) = 2 as admin_tables_visible from information_schema.tables -where table_schema = 'metadata_visibility_db'; +where table_schema = 'metadata_visibility_db' +and table_name in ('allowed_table', 'hidden_table'); ➤ admin_tables_visible[-7,1,0] 𝄀 1 select count(*) = 6 as admin_columns_visible @@ -184,6 +236,19 @@ from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; ➤ admin_constraints_visible[-7,1,0] 𝄀 1 +select +(select count(*) > 0 from information_schema.check_constraints +where constraint_schema = 'metadata_visibility_db') as admin_checks_visible, +(select count(*) > 0 from information_schema.key_column_usage +where table_schema = 'metadata_visibility_db') as admin_fk_columns_visible, +(select count(*) > 0 from information_schema.referential_constraints +where constraint_schema = 'metadata_visibility_db') as admin_fk_constraints_visible, +(select count(*) = 1 from information_schema.views +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as admin_view_visible, +(select count(*) > 0 from information_schema.partitions +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as admin_partitions_visible; +➤ admin_checks_visible[-7,1,0] ¦ admin_fk_columns_visible[-7,1,0] ¦ admin_fk_constraints_visible[-7,1,0] ¦ admin_view_visible[-7,1,0] ¦ admin_partitions_visible[-7,1,0] 𝄀 +1 ¦ 0 ¦ 0 ¦ 1 ¦ 1 drop database metadata_visibility_db; drop user metadata_visibility_user; drop role metadata_visibility_primary, metadata_visibility_reader; diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql index 63d33aadef0c3..41c9c9129abb4 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql @@ -11,13 +11,19 @@ create table metadata_visibility_db.allowed_table ( secret varchar(20) unique, payload int, constraint ck_allowed_payload check (payload >= 0) +) partition by hash(id) partitions 2; +create table metadata_visibility_db.hidden_parent ( + id int primary key ); create table metadata_visibility_db.hidden_table ( id int primary key, secret varchar(20) unique, payload int, - constraint ck_hidden_payload check (payload >= 0) + constraint ck_hidden_payload check (payload >= 0), + constraint fk_hidden_parent foreign key (id) references metadata_visibility_db.hidden_parent(id) ); +create view metadata_visibility_db.hidden_view as +select id, secret, payload from metadata_visibility_db.hidden_table; create role metadata_visibility_primary, metadata_visibility_reader; create user metadata_visibility_user identified by '123456' default role metadata_visibility_primary; grant connect on account * to metadata_visibility_primary; @@ -35,6 +41,17 @@ where table_schema = 'metadata_visibility_db'; select count(*) = 0 as constraints_hidden from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; +select + (select count(*) = 0 from information_schema.check_constraints + where constraint_schema = 'metadata_visibility_db') as check_constraints_hidden, + (select count(*) = 0 from information_schema.key_column_usage + where table_schema = 'metadata_visibility_db') as key_column_usage_hidden, + (select count(*) = 0 from information_schema.referential_constraints + where constraint_schema = 'metadata_visibility_db') as referential_constraints_hidden, + (select count(*) = 0 from information_schema.views + where table_schema = 'metadata_visibility_db') as views_hidden, + (select count(*) = 0 from information_schema.partitions + where table_schema = 'metadata_visibility_db') as partitions_hidden; select (select count(*) > 0 from information_schema.tables where table_schema = 'information_schema') @@ -70,6 +87,22 @@ select and constraint_name = 'ck_allowed_payload') as allowed_check_visible, (select count(*) = 0 from information_schema.table_constraints where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_constraints_hidden; +select + (select count(*) = 1 from information_schema.check_constraints + where constraint_schema = 'metadata_visibility_db' and constraint_name = 'ck_allowed_payload') + as allowed_check_metadata_visible, + (select count(*) > 0 from information_schema.partitions + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') + as allowed_partition_metadata_visible, + (select count(*) = 0 from information_schema.key_column_usage + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') + as hidden_fk_columns_hidden, + (select count(*) = 0 from information_schema.referential_constraints + where constraint_schema = 'metadata_visibility_db' and table_name = 'hidden_table') + as hidden_fk_constraint_hidden, + (select count(*) = 0 from information_schema.views + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') + as hidden_view_hidden; -- @session alter role metadata_visibility_primary rename to metadata_visibility_primary_renamed; @@ -134,7 +167,8 @@ grant show tables on database metadata_visibility_db to metadata_visibility_prim -- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 select count(*) = 2 as database_tables_visible from information_schema.tables -where table_schema = 'metadata_visibility_db'; +where table_schema = 'metadata_visibility_db' + and table_name in ('allowed_table', 'hidden_table'); select count(*) = 6 as database_columns_visible from information_schema.columns where table_schema = 'metadata_visibility_db' @@ -145,11 +179,23 @@ where table_schema = 'metadata_visibility_db'; select count(*) > 0 as database_constraints_visible from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; +select + (select count(*) > 0 from information_schema.check_constraints + where constraint_schema = 'metadata_visibility_db') as database_checks_visible, + (select count(*) > 0 from information_schema.key_column_usage + where table_schema = 'metadata_visibility_db') as database_fk_columns_visible, + (select count(*) > 0 from information_schema.referential_constraints + where constraint_schema = 'metadata_visibility_db') as database_fk_constraints_visible, + (select count(*) = 1 from information_schema.views + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as database_view_visible, + (select count(*) > 0 from information_schema.partitions + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as database_partitions_visible; -- @session select count(*) = 2 as admin_tables_visible from information_schema.tables -where table_schema = 'metadata_visibility_db'; +where table_schema = 'metadata_visibility_db' + and table_name in ('allowed_table', 'hidden_table'); select count(*) = 6 as admin_columns_visible from information_schema.columns where table_schema = 'metadata_visibility_db' @@ -160,6 +206,17 @@ where table_schema = 'metadata_visibility_db'; select count(*) > 0 as admin_constraints_visible from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; +select + (select count(*) > 0 from information_schema.check_constraints + where constraint_schema = 'metadata_visibility_db') as admin_checks_visible, + (select count(*) > 0 from information_schema.key_column_usage + where table_schema = 'metadata_visibility_db') as admin_fk_columns_visible, + (select count(*) > 0 from information_schema.referential_constraints + where constraint_schema = 'metadata_visibility_db') as admin_fk_constraints_visible, + (select count(*) = 1 from information_schema.views + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as admin_view_visible, + (select count(*) > 0 from information_schema.partitions + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as admin_partitions_visible; drop database metadata_visibility_db; drop user metadata_visibility_user; From 63d8f953e5c3b44066865a2a3157d1ec4cfa4091 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 27 Aug 2026 17:55:48 +0800 Subject: [PATCH 05/17] fix(sysview): preserve foreign key metadata visibility --- pkg/util/sysview/predefined.go | 10 ++++---- pkg/util/sysview/predefined_test.go | 8 ++++--- ...nformation_schema_object_visibility.result | 24 +++++++++++-------- .../information_schema_object_visibility.sql | 20 +++++++++------- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index b2e2f3349de2f..b3e0f8a17eeb6 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -199,7 +199,8 @@ var ( "CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, " + "CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME " + "FROM mo_catalog.mo_foreign_keys fk " + - "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id" + "JOIN __mo_visible_tables fk_tbl " + + "ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname" InformationSchemaColumnsDDL = fmt.Sprintf("CREATE VIEW information_schema.COLUMNS AS "+informationSchemaMetadataVisibilityCTE()+"select "+ "'def' as TABLE_CATALOG,"+ @@ -450,11 +451,12 @@ var ( "fk.table_name AS TABLE_NAME, " + "fk.refer_table_name AS REFERENCED_TABLE_NAME " + "FROM (" + - "SELECT table_id, db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name " + + "SELECT db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name " + "FROM mo_catalog.mo_foreign_keys " + - "GROUP BY table_id, db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name" + + "GROUP BY db_name, table_name, constraint_name, refer_db_name, refer_table_name, on_update, on_delete, referenced_index_name" + ") fk " + - "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id" + "JOIN __mo_visible_tables fk_tbl " + + "ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname" // CHECK_CONSTRAINTS is backed by a table function because CHECK metadata is // stored in the serialized SchemaExtra of each table. The function decodes diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index 6f34103ec4ab5..076df055a9158 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -102,9 +102,11 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { assert.Contains(t, InformationSchemaStatisticsDDL, "join `__mo_visible_tables` `tbl`") assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables tbl") assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables check_tbl") - assert.Contains(t, InformationSchemaKeyColumnUsageDDL, "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id") - assert.Contains(t, InformationSchemaReferentialConstraintsDDL, - "JOIN __mo_visible_tables fk_tbl ON fk.table_id = fk_tbl.rel_id") + fkVisibilityJoin := "ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname" + assert.Contains(t, InformationSchemaKeyColumnUsageDDL, fkVisibilityJoin) + assert.Contains(t, InformationSchemaReferentialConstraintsDDL, fkVisibilityJoin) + assert.NotContains(t, InformationSchemaKeyColumnUsageDDL, "fk.table_id = fk_tbl.rel_id") + assert.NotContains(t, InformationSchemaReferentialConstraintsDDL, "fk.table_id = fk_tbl.rel_id") assert.Contains(t, InformationSchemaCheckConstraintsDDL, "JOIN __mo_visible_tables check_tbl") assert.Contains(t, InformationSchemaViewsDDL, "FROM __mo_visible_tables tbl") assert.Contains(t, InformationSchemaPartitionsDDL, "FROM `__mo_visible_tables` `tbl`") diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result index 7dd44446a1654..3cf3857f1fb70 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result @@ -204,16 +204,18 @@ where table_schema = 'metadata_visibility_db'; select (select count(*) > 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as database_checks_visible, -(select count(*) > 0 from information_schema.key_column_usage -where table_schema = 'metadata_visibility_db') as database_fk_columns_visible, -(select count(*) > 0 from information_schema.referential_constraints -where constraint_schema = 'metadata_visibility_db') as database_fk_constraints_visible, +(select count(*) = 1 from information_schema.key_column_usage +where table_schema = 'metadata_visibility_db' +and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as database_fk_columns_visible, +(select count(*) = 1 from information_schema.referential_constraints +where constraint_schema = 'metadata_visibility_db' +and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as database_fk_constraints_visible, (select count(*) = 1 from information_schema.views where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as database_view_visible, (select count(*) > 0 from information_schema.partitions where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as database_partitions_visible; ➤ database_checks_visible[-7,1,0] ¦ database_fk_columns_visible[-7,1,0] ¦ database_fk_constraints_visible[-7,1,0] ¦ database_view_visible[-7,1,0] ¦ database_partitions_visible[-7,1,0] 𝄀 -1 ¦ 0 ¦ 0 ¦ 1 ¦ 1 +1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 select count(*) = 2 as admin_tables_visible from information_schema.tables where table_schema = 'metadata_visibility_db' @@ -239,16 +241,18 @@ where table_schema = 'metadata_visibility_db'; select (select count(*) > 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as admin_checks_visible, -(select count(*) > 0 from information_schema.key_column_usage -where table_schema = 'metadata_visibility_db') as admin_fk_columns_visible, -(select count(*) > 0 from information_schema.referential_constraints -where constraint_schema = 'metadata_visibility_db') as admin_fk_constraints_visible, +(select count(*) = 1 from information_schema.key_column_usage +where table_schema = 'metadata_visibility_db' +and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as admin_fk_columns_visible, +(select count(*) = 1 from information_schema.referential_constraints +where constraint_schema = 'metadata_visibility_db' +and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as admin_fk_constraints_visible, (select count(*) = 1 from information_schema.views where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as admin_view_visible, (select count(*) > 0 from information_schema.partitions where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as admin_partitions_visible; ➤ admin_checks_visible[-7,1,0] ¦ admin_fk_columns_visible[-7,1,0] ¦ admin_fk_constraints_visible[-7,1,0] ¦ admin_view_visible[-7,1,0] ¦ admin_partitions_visible[-7,1,0] 𝄀 -1 ¦ 0 ¦ 0 ¦ 1 ¦ 1 +1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 drop database metadata_visibility_db; drop user metadata_visibility_user; drop role metadata_visibility_primary, metadata_visibility_reader; diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql index 41c9c9129abb4..d9848c4b51023 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql @@ -182,10 +182,12 @@ where table_schema = 'metadata_visibility_db'; select (select count(*) > 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as database_checks_visible, - (select count(*) > 0 from information_schema.key_column_usage - where table_schema = 'metadata_visibility_db') as database_fk_columns_visible, - (select count(*) > 0 from information_schema.referential_constraints - where constraint_schema = 'metadata_visibility_db') as database_fk_constraints_visible, + (select count(*) = 1 from information_schema.key_column_usage + where table_schema = 'metadata_visibility_db' + and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as database_fk_columns_visible, + (select count(*) = 1 from information_schema.referential_constraints + where constraint_schema = 'metadata_visibility_db' + and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as database_fk_constraints_visible, (select count(*) = 1 from information_schema.views where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as database_view_visible, (select count(*) > 0 from information_schema.partitions @@ -209,10 +211,12 @@ where table_schema = 'metadata_visibility_db'; select (select count(*) > 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as admin_checks_visible, - (select count(*) > 0 from information_schema.key_column_usage - where table_schema = 'metadata_visibility_db') as admin_fk_columns_visible, - (select count(*) > 0 from information_schema.referential_constraints - where constraint_schema = 'metadata_visibility_db') as admin_fk_constraints_visible, + (select count(*) = 1 from information_schema.key_column_usage + where table_schema = 'metadata_visibility_db' + and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as admin_fk_columns_visible, + (select count(*) = 1 from information_schema.referential_constraints + where constraint_schema = 'metadata_visibility_db' + and table_name = 'hidden_table' and constraint_name = 'fk_hidden_parent') as admin_fk_constraints_visible, (select count(*) = 1 from information_schema.views where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as admin_view_visible, (select count(*) > 0 from information_schema.partitions From 96651d9cf0ecde9d9241246c76c4892f25e68913 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 27 Aug 2026 20:37:12 +0800 Subject: [PATCH 06/17] fix(sysview): avoid recursive metadata pipelines --- pkg/util/sysview/predefined.go | 16 ++++++++++------ pkg/util/sysview/predefined_test.go | 10 +++++++--- test/distributed/cases/dml/show/show.result | 2 +- ...fk_information_schema_key_column_usage.result | 2 +- test/distributed/cases/mo_cloud/mo_cloud.result | 2 +- .../zz_accesscontrol/account_restricted.result | 4 ++-- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index b3e0f8a17eeb6..b19042d2cb435 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -154,18 +154,20 @@ var ( ) // informationSchemaMetadataVisibilityCTE limits user-object metadata to the -// objects visible to the session's single active role and its inherited roles. -// System schemas remain universally visible for MySQL/tooling compatibility. +// objects visible to the session's single active role and roles directly +// granted to it. Keeping this relation non-recursive avoids distributed +// recursive pipelines in every information_schema query. System schemas remain +// universally visible for MySQL/tooling compatibility. func informationSchemaMetadataVisibilityCTE() string { - return "WITH RECURSIVE __mo_active_roles(role_id) AS (" + + return "WITH __mo_active_roles(role_id) AS (" + "SELECT cast(current_role_id() AS bigint) " + "UNION " + "SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg " + - "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), " + + "WHERE rg.grantee_id = current_role_id()), " + "__mo_visible_tables AS (" + "SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, " + "tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, " + - "tbl.owner, tbl.creator, tbl.`constraint` FROM mo_catalog.mo_tables tbl " + + "tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl " + "WHERE tbl.account_id = current_account_id() AND (" + "tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') " + "OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) " + @@ -409,7 +411,9 @@ var ( "'DEFINER' AS `SECURITY_TYPE`," + "'utf8mb4' AS `CHARACTER_SET_CLIENT`," + "'" + DefaultCollationForCharset("utf8mb4") + "' AS `COLLATION_CONNECTION` " + - "FROM __mo_visible_tables tbl LEFT JOIN mo_catalog.mo_user usr ON tbl.creator = usr.user_id " + + "FROM mo_catalog.mo_tables tbl " + + "JOIN __mo_visible_tables visible_tbl ON tbl.account_id = visible_tbl.account_id AND tbl.rel_id = visible_tbl.rel_id " + + "LEFT JOIN mo_catalog.mo_user usr ON tbl.creator = usr.user_id " + "WHERE tbl.account_id = current_account_id() and tbl.relkind = 'v' and tbl.reldatabase != 'information_schema'" InformationSchemaStatisticsDDL = fmt.Sprintf("CREATE VIEW information_schema.`STATISTICS` AS "+informationSchemaMetadataVisibilityCTE()+ diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index 076df055a9158..82b4ce045b8a7 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -70,9 +70,9 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { for _, expected := range []string{ - "WITH RECURSIVE __mo_active_roles(role_id)", + "WITH __mo_active_roles(role_id)", "SELECT cast(current_role_id() AS bigint)", - "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id", + "WHERE rg.grantee_id = current_role_id()", "__mo_visible_tables AS", "tbl.account_id = current_account_id()", "tbl.owner IN (SELECT role_id FROM __mo_active_roles)", @@ -85,6 +85,8 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { } { assert.Contains(t, test.ddl, expected) } + assert.NotContains(t, test.ddl, "WITH RECURSIVE") + assert.NotContains(t, test.ddl, "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id") assert.NotContains(t, test.ddl, "SELECT tbl.*") assert.NotContains(t, test.ddl, "current_role()") assert.NotContains(t, test.ddl, "FROM mo_catalog.mo_role ") @@ -103,12 +105,14 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables tbl") assert.Contains(t, InformationSchemaTableConstraintsDDL, "join __mo_visible_tables check_tbl") fkVisibilityJoin := "ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname" + assert.Contains(t, InformationSchemaKeyColumnUsageDDL, "JOIN __mo_visible_tables fk_tbl") assert.Contains(t, InformationSchemaKeyColumnUsageDDL, fkVisibilityJoin) + assert.Contains(t, InformationSchemaReferentialConstraintsDDL, "JOIN __mo_visible_tables fk_tbl") assert.Contains(t, InformationSchemaReferentialConstraintsDDL, fkVisibilityJoin) assert.NotContains(t, InformationSchemaKeyColumnUsageDDL, "fk.table_id = fk_tbl.rel_id") assert.NotContains(t, InformationSchemaReferentialConstraintsDDL, "fk.table_id = fk_tbl.rel_id") assert.Contains(t, InformationSchemaCheckConstraintsDDL, "JOIN __mo_visible_tables check_tbl") - assert.Contains(t, InformationSchemaViewsDDL, "FROM __mo_visible_tables tbl") + assert.Contains(t, InformationSchemaViewsDDL, "JOIN __mo_visible_tables visible_tbl") assert.Contains(t, InformationSchemaPartitionsDDL, "FROM `__mo_visible_tables` `tbl`") } diff --git a/test/distributed/cases/dml/show/show.result b/test/distributed/cases/dml/show/show.result index 5489b9699ff85..75d5d485dc9fb 100644 --- a/test/distributed/cases/dml/show/show.result +++ b/test/distributed/cases/dml/show/show.result @@ -525,5 +525,5 @@ create database test; use test; SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH RECURSIVE __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci drop database test; diff --git a/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result b/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result index 33bb07467a0f4..05b7e872c447c 100644 --- a/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result +++ b/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result @@ -61,5 +61,5 @@ referenced_table_name ¦ VARCHAR(64) ¦ NO ¦ ¦ null ¦ ¦ 𝄀 referenced_column_name ¦ VARCHAR(64) ¦ NO ¦ ¦ null ¦ ¦ show create table information_schema.KEY_COLUMN_USAGE; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk ¦ utf8mb4 ¦ utf8mb4_general_ci +key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci drop database fk_information_schema_key_column_usage; diff --git a/test/distributed/cases/mo_cloud/mo_cloud.result b/test/distributed/cases/mo_cloud/mo_cloud.result index fa7a50562924a..d5ca765c323a2 100644 --- a/test/distributed/cases/mo_cloud/mo_cloud.result +++ b/test/distributed/cases/mo_cloud/mo_cloud.result @@ -228,7 +228,7 @@ engines ¦ CREATE TABLE `engines` ( ) SHOW CREATE TABLE information_schema.key_column_usage; ➤ View[12,16,0] ¦ Create View[12,786,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 -key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk ¦ utf8mb4 ¦ utf8mb4_general_ci +key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci SHOW CREATE TABLE information_schema.keywords; ➤ Table[12,8,0] ¦ Create Table[12,101,0] 𝄀 keywords ¦ CREATE TABLE `keywords` ( diff --git a/test/distributed/cases/zz_accesscontrol/account_restricted.result b/test/distributed/cases/zz_accesscontrol/account_restricted.result index b7c4207cfee47..86c017368cf43 100644 --- a/test/distributed/cases/zz_accesscontrol/account_restricted.result +++ b/test/distributed/cases/zz_accesscontrol/account_restricted.result @@ -158,7 +158,7 @@ GRANT values ON table *.* `admin`@`localhost` GRANT connect ON account `admin`@`localhost` SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH RECURSIVE __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 @@ -271,7 +271,7 @@ show grants for 'hnadmin'@'localhost'; Grants for hnadmin@localhost SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH RECURSIVE __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' then 'PRI' when mo_show_col_unique(mt.`constraint`, mc.attname) then 'UNI' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 From 6553e90ead909a95e5d712e86298a49c6c73ff6e Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 28 Aug 2026 00:16:11 +0800 Subject: [PATCH 07/17] fix(sysview): close inherited role and schema visibility gaps --- .../versions/v4_0_6/tenant_upgrade_list.go | 22 ++-- pkg/bootstrap/versions/v4_0_6/upgrade_test.go | 21 +-- pkg/defines/const.go | 3 +- .../colexec/table_function/current_roles.go | 124 ++++++++++++++++++ .../table_function/current_roles_test.go | 102 ++++++++++++++ .../colexec/table_function/table_function.go | 2 + pkg/sql/plan/apply_indices.go | 4 + pkg/sql/plan/current_roles.go | 89 +++++++++++++ pkg/sql/plan/current_roles_test.go | 56 ++++++++ pkg/sql/plan/query_builder.go | 2 + pkg/util/sysview/predefined.go | 31 +++-- pkg/util/sysview/predefined_test.go | 11 +- test/distributed/cases/dml/show/show.result | 2 +- ...information_schema_key_column_usage.result | 2 +- .../cases/mo_cloud/mo_cloud.result | 2 +- .../account_restricted.result | 4 +- ...nformation_schema_object_visibility.result | 56 ++++++-- .../information_schema_object_visibility.sql | 44 ++++++- 18 files changed, 521 insertions(+), 56 deletions(-) create mode 100644 pkg/sql/colexec/table_function/current_roles.go create mode 100644 pkg/sql/colexec/table_function/current_roles_test.go create mode 100644 pkg/sql/plan/current_roles.go create mode 100644 pkg/sql/plan/current_roles_test.go 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 5aacdf4d3c5c4..236c16cbb29b7 100644 --- a/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go +++ b/pkg/bootstrap/versions/v4_0_6/tenant_upgrade_list.go @@ -56,31 +56,29 @@ var tenantUpgEntries = []versions.UpgradeEntry{ upgradeInformationSchemaMetadataVisibilityCheckConstraints(), upgradeInformationSchemaMetadataVisibilityView("VIEWS", sysview.InformationSchemaViewsDDL), upgradeInformationSchemaMetadataVisibilityView("PARTITIONS", sysview.InformationSchemaPartitionsDDL), + upgradeInformationSchemaMetadataVisibilityView("SCHEMATA", sysview.InformationSchemaSchemataDDL), } func upgradeInformationSchemaMetadataVisibilityView(viewName, viewDDL string) versions.UpgradeEntry { return versions.UpgradeEntry{ - Schema: sysview.InformationDBConst, - TableName: viewName, - UpgType: versions.MODIFY_VIEW, - UpgSql: viewDDL, - CheckFunc: checkViewDefinition(viewName, viewDDL), - PreSql: fmt.Sprintf("DROP VIEW IF EXISTS %s.%s;", sysview.InformationDBConst, viewName), + Schema: sysview.InformationDBConst, + TableName: viewName, + UpgType: versions.MODIFY_VIEW, + UpgSql: viewDDL, + CheckFunc: checkViewDefinition(viewName, viewDDL), + PreSql: fmt.Sprintf("DROP VIEW IF EXISTS %s.%s;", sysview.InformationDBConst, viewName), + RequiredProtocolVersion: defines.MORPCVersion33, } } func upgradeInformationSchemaMetadataVisibilityTableConstraints() versions.UpgradeEntry { - entry := upgradeInformationSchemaMetadataVisibilityView( + return upgradeInformationSchemaMetadataVisibilityView( "TABLE_CONSTRAINTS", sysview.InformationSchemaTableConstraintsDDL) - entry.RequiredProtocolVersion = defines.MORPCVersion16 - return entry } func upgradeInformationSchemaMetadataVisibilityCheckConstraints() versions.UpgradeEntry { - entry := upgradeInformationSchemaMetadataVisibilityView( + return upgradeInformationSchemaMetadataVisibilityView( "CHECK_CONSTRAINTS", sysview.InformationSchemaCheckConstraintsDDL) - entry.RequiredProtocolVersion = defines.MORPCVersion16 - return entry } // Keep this as a separate upgrade entry so tenants that already completed diff --git a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go index cfa114eeed236..2db3a05f33d65 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, 28) + require.Len(t, tenantUpgEntries, 29) require.Len(t, clusterUpgEntries, 3) require.Equal(t, retireKafkaSinkDaemonTasks.UpgSql, clusterUpgEntries[0].UpgSql) require.Equal(t, catalog.MO_VIEW_DEPENDENCIES, clusterUpgEntries[1].TableName) @@ -128,21 +128,19 @@ func TestUpgradeEntries(t *testing.T) { "drop view if exists information_schema.collation_character_set_applicability") metadataViews := []struct { - name string - ddl string - requiredProtocol int64 + name string + ddl string }{ {name: "TABLES", ddl: sysview.InformationSchemaTablesDDL}, {name: "COLUMNS", ddl: sysview.InformationSchemaColumnsDDL}, {name: "STATISTICS", ddl: sysview.InformationSchemaStatisticsDDL}, - {name: "TABLE_CONSTRAINTS", ddl: sysview.InformationSchemaTableConstraintsDDL, - requiredProtocol: defines.MORPCVersion16}, + {name: "TABLE_CONSTRAINTS", ddl: sysview.InformationSchemaTableConstraintsDDL}, {name: "KEY_COLUMN_USAGE", ddl: sysview.InformationSchemaKeyColumnUsageDDL}, {name: "REFERENTIAL_CONSTRAINTS", ddl: sysview.InformationSchemaReferentialConstraintsDDL}, - {name: "CHECK_CONSTRAINTS", ddl: sysview.InformationSchemaCheckConstraintsDDL, - requiredProtocol: defines.MORPCVersion16}, + {name: "CHECK_CONSTRAINTS", ddl: sysview.InformationSchemaCheckConstraintsDDL}, {name: "VIEWS", ddl: sysview.InformationSchemaViewsDDL}, {name: "PARTITIONS", ddl: sysview.InformationSchemaPartitionsDDL}, + {name: "SCHEMATA", ddl: sysview.InformationSchemaSchemataDDL}, } for i, view := range metadataViews { entry := tenantUpgEntries[19+i] @@ -150,7 +148,7 @@ func TestUpgradeEntries(t *testing.T) { require.Equal(t, view.name, entry.TableName) require.Equal(t, versions.MODIFY_VIEW, entry.UpgType) require.Equal(t, view.ddl, entry.UpgSql) - require.Equal(t, view.requiredProtocol, entry.RequiredProtocolVersion) + require.Equal(t, int64(defines.MORPCVersion33), entry.RequiredProtocolVersion) require.Contains(t, strings.ToLower(entry.PreSql), "drop view if exists information_schema."+strings.ToLower(view.name)) } @@ -170,6 +168,7 @@ func TestInformationSchemaMetadataVisibilityUpgradeChecks(t *testing.T) { {name: "CHECK_CONSTRAINTS", ddl: sysview.InformationSchemaCheckConstraintsDDL}, {name: "VIEWS", ddl: sysview.InformationSchemaViewsDDL}, {name: "PARTITIONS", ddl: sysview.InformationSchemaPartitionsDDL}, + {name: "SCHEMATA", ddl: sysview.InformationSchemaSchemataDDL}, } checkErr := errors.New("check metadata view definition failed") @@ -249,7 +248,7 @@ func TestUserDefinedFunctionArgumentTypesBackfillRejectsOversizedSignature(t *te } func TestForeignKeyMetadataTenantUpgradeEntries(t *testing.T) { - require.Len(t, tenantUpgEntries, 28) + require.Len(t, tenantUpgEntries, 29) for i, column := range []string{"referenced_index_name", "on_delete_origin", "on_update_origin"} { entry := tenantUpgEntries[2+i] @@ -631,6 +630,8 @@ func TestVersionHandleLifecycleWithNoLegacyDefinitions(t *testing.T) { return true, sysview.InformationSchemaViewsDDL, nil case "PARTITIONS": return true, sysview.InformationSchemaPartitionsDDL, nil + case "SCHEMATA": + return true, sysview.InformationSchemaSchemataDDL, nil default: return false, "", errors.New("unexpected view") } diff --git a/pkg/defines/const.go b/pkg/defines/const.go index 075959eb5d5ab..d37e86ec6d903 100644 --- a/pkg/defines/const.go +++ b/pkg/defines/const.go @@ -68,7 +68,8 @@ const ( MORPCVersion30 int64 = 30 // prepared numeric-prefix common-type casts MORPCVersion31 int64 = 31 // batched multi-table remote transaction unlock MORPCVersion32 int64 = 32 // cross-transaction logical-plan generation snapshot - MORPCLatestVersion = MORPCVersion32 + MORPCVersion33 int64 = 33 // cycle-safe current-role closure table function + MORPCLatestVersion = MORPCVersion33 ) // DefaultLockWaitTimeoutSeconds is shared by the frontend default and by diff --git a/pkg/sql/colexec/table_function/current_roles.go b/pkg/sql/colexec/table_function/current_roles.go new file mode 100644 index 0000000000000..524c37a17ebbf --- /dev/null +++ b/pkg/sql/colexec/table_function/current_roles.go @@ -0,0 +1,124 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package table_function + +import ( + "sort" + + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/matrixorigin/matrixone/pkg/defines" + "github.com/matrixorigin/matrixone/pkg/util/executor" + "github.com/matrixorigin/matrixone/pkg/vectorindex/sqlexec" + "github.com/matrixorigin/matrixone/pkg/vm/process" +) + +const currentRoleGrantQuery = "SELECT cast(granted_id AS bigint), cast(grantee_id AS bigint) FROM mo_catalog.mo_role_grant" + +func runCurrentRolesSQL(proc *process.Process) (executor.Result, error) { + return sqlexec.RunSql(sqlexec.NewSqlProcess(proc), currentRoleGrantQuery) +} + +type roleGrantEdge struct { + grantedID int64 + granteeID int64 +} + +type currentRolesState struct { + simpleOneBatchState + loadEdges func(*process.Process) ([]roleGrantEdge, error) +} + +func currentRolesPrepare(_ *process.Process, _ *TableFunction) (tvfState, error) { + return ¤tRolesState{loadEdges: loadCurrentRoleGrantEdges}, nil +} + +func currentRoleClosure(root int64, edges []roleGrantEdge) []int64 { + grants := make(map[int64][]int64) + for _, edge := range edges { + grants[edge.granteeID] = append(grants[edge.granteeID], edge.grantedID) + } + + visited := map[int64]struct{}{root: {}} + queue := []int64{root} + for len(queue) > 0 { + roleID := queue[0] + queue = queue[1:] + for _, grantedID := range grants[roleID] { + if _, ok := visited[grantedID]; ok { + continue + } + visited[grantedID] = struct{}{} + queue = append(queue, grantedID) + } + } + + roles := make([]int64, 0, len(visited)) + for roleID := range visited { + roles = append(roles, roleID) + } + sort.Slice(roles, func(i, j int) bool { return roles[i] < roles[j] }) + return roles +} + +func decodeCurrentRoleGrantEdges(result executor.Result) []roleGrantEdge { + edges := make([]roleGrantEdge, 0) + result.ReadRows(func(rows int, cols []*vector.Vector) bool { + grantedIDs := vector.MustFixedColWithTypeCheck[int64](cols[0]) + granteeIDs := vector.MustFixedColWithTypeCheck[int64](cols[1]) + for i := 0; i < rows; i++ { + edges = append(edges, roleGrantEdge{ + grantedID: grantedIDs[i], + granteeID: granteeIDs[i], + }) + } + return true + }) + return edges +} + +func loadCurrentRoleGrantEdges(proc *process.Process) ([]roleGrantEdge, error) { + result, err := runCurrentRolesSQL(proc) + if err != nil { + return nil, err + } + defer result.Close() + return decodeCurrentRoleGrantEdges(result), nil +} + +func (s *currentRolesState) start( + tf *TableFunction, + proc *process.Process, + nthRow int, + _ process.Analyzer, +) error { + s.startPreamble(tf, proc, nthRow) + if nthRow != 0 { + s.batch.SetRowCount(0) + return nil + } + + edges, err := s.loadEdges(proc) + if err != nil { + return err + } + roles := currentRoleClosure(int64(defines.GetRoleId(proc.Ctx)), edges) + for _, roleID := range roles { + if err := vector.AppendFixed(s.batch.Vecs[0], roleID, false, proc.Mp()); err != nil { + return err + } + } + s.batch.SetRowCount(len(roles)) + return nil +} diff --git a/pkg/sql/colexec/table_function/current_roles_test.go b/pkg/sql/colexec/table_function/current_roles_test.go new file mode 100644 index 0000000000000..d688b16535438 --- /dev/null +++ b/pkg/sql/colexec/table_function/current_roles_test.go @@ -0,0 +1,102 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package table_function + +import ( + "errors" + "testing" + + "github.com/matrixorigin/matrixone/pkg/common/mpool" + "github.com/matrixorigin/matrixone/pkg/common/runtime" + "github.com/matrixorigin/matrixone/pkg/container/batch" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/matrixorigin/matrixone/pkg/defines" + planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" + "github.com/matrixorigin/matrixone/pkg/testutil" + "github.com/matrixorigin/matrixone/pkg/util/executor" + "github.com/matrixorigin/matrixone/pkg/vm" + "github.com/matrixorigin/matrixone/pkg/vm/process" + "github.com/stretchr/testify/require" +) + +func TestCurrentRoleClosure(t *testing.T) { + edges := []roleGrantEdge{ + {grantedID: 20, granteeID: 10}, + {grantedID: 30, granteeID: 20}, + {grantedID: 40, granteeID: 30}, + {grantedID: 10, granteeID: 40}, // cycle + {grantedID: 50, granteeID: 20}, + {grantedID: 99, granteeID: 98}, // disconnected + {grantedID: 30, granteeID: 20}, // duplicate + } + + require.Equal(t, []int64{10, 20, 30, 40, 50}, currentRoleClosure(10, edges)) + require.Equal(t, []int64{98, 99}, currentRoleClosure(98, edges)) + require.Equal(t, []int64{77}, currentRoleClosure(77, edges)) +} + +func TestDecodeCurrentRoleGrantEdges(t *testing.T) { + mp := mpool.MustNewZero() + bat := batch.NewWithSize(2) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + bat.Vecs[1] = vector.NewVec(types.T_int64.ToType()) + for _, edge := range []roleGrantEdge{{20, 10}, {30, 20}} { + require.NoError(t, vector.AppendFixed(bat.Vecs[0], edge.grantedID, false, mp)) + require.NoError(t, vector.AppendFixed(bat.Vecs[1], edge.granteeID, false, mp)) + } + bat.SetRowCount(2) + result := executor.Result{Mp: mp, Batches: []*batch.Batch{bat}} + defer result.Close() + + require.Equal(t, []roleGrantEdge{{20, 10}, {30, 20}}, decodeCurrentRoleGrantEdges(result)) +} + +func TestCurrentRolesState(t *testing.T) { + runtime.RunTest("", func(runtime.Runtime) { + proc := testutil.NewProc(t) + proc.Ctx = defines.AttachRoleId(proc.Ctx, 10) + tf := &TableFunction{ + FuncName: "mo_current_roles", + Attrs: []string{"role_id"}, + Rets: []*planpb.ColDef{{ + Name: "role_id", + Typ: planpb.Type{Id: int32(types.T_int64)}, + }}, + OperatorBase: vm.OperatorBase{OperatorInfo: vm.OperatorInfo{Idx: 0}}, + } + require.NoError(t, tf.Prepare(proc)) + state := tf.ctr.state.(*currentRolesState) + state.loadEdges = func(*process.Process) ([]roleGrantEdge, error) { + return []roleGrantEdge{{20, 10}, {30, 20}, {10, 30}}, nil + } + + require.NoError(t, state.start(tf, proc, 0, nil)) + require.Equal(t, []int64{10, 20, 30}, vector.MustFixedColWithTypeCheck[int64](state.batch.Vecs[0])) + result, err := state.call(tf, proc) + require.NoError(t, err) + require.Equal(t, 3, result.Batch.RowCount()) + _, err = state.call(tf, proc) + require.NoError(t, err) + + require.NoError(t, state.start(tf, proc, 1, nil)) + require.Zero(t, state.batch.RowCount()) + + expected := errors.New("role grant read failed") + state.loadEdges = func(*process.Process) ([]roleGrantEdge, error) { return nil, expected } + require.ErrorIs(t, state.start(tf, proc, 0, nil), expected) + tf.Free(proc, false, nil) + }) +} diff --git a/pkg/sql/colexec/table_function/table_function.go b/pkg/sql/colexec/table_function/table_function.go index 2b133677a1e5e..83e9a2e063b53 100644 --- a/pkg/sql/colexec/table_function/table_function.go +++ b/pkg/sql/colexec/table_function/table_function.go @@ -174,6 +174,8 @@ func (tableFunction *TableFunction) Prepare(proc *process.Process) error { tblArg.ctr.state, err = moCachePrepare(proc, tblArg) case "mo_check_constraints": tblArg.ctr.state, err = checkConstraintsPrepare(proc, tblArg) + case "mo_current_roles": + tblArg.ctr.state, err = currentRolesPrepare(proc, tblArg) case "fulltext_index_scan": tblArg.ctr.state, err = fulltextIndexScanPrepare(proc, tblArg) case "fulltext_index_tokenize": diff --git a/pkg/sql/plan/apply_indices.go b/pkg/sql/plan/apply_indices.go index f3d6f7ae5c95c..b3a9a5623249f 100644 --- a/pkg/sql/plan/apply_indices.go +++ b/pkg/sql/plan/apply_indices.go @@ -4567,6 +4567,10 @@ func (builder *QueryBuilder) applyIndicesForJoins(nodeID int32, node *plan.Node, hashSlot++ } + if leftChild.TableDef == nil || leftChild.TableDef.Pkey == nil { + return nodeID, nil + } + joinOnPK := true for _, part := range leftChild.TableDef.Pkey.Names { colIdx := leftChild.TableDef.Name2ColIndex[part] diff --git a/pkg/sql/plan/current_roles.go b/pkg/sql/plan/current_roles.go new file mode 100644 index 0000000000000..68154533e8568 --- /dev/null +++ b/pkg/sql/plan/current_roles.go @@ -0,0 +1,89 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plan + +import ( + "context" + + "github.com/matrixorigin/matrixone/pkg/common/moerr" + moruntime "github.com/matrixorigin/matrixone/pkg/common/runtime" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/defines" + planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" + "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" + "github.com/matrixorigin/matrixone/pkg/vm/process" +) + +var currentRolesColDefs = []*planpb.ColDef{ + { + Name: "role_id", + Typ: planpb.Type{ + Id: int32(types.T_int64), + }, + }, +} + +func requireCurrentRolesProtocol(ctx context.Context, proc *process.Process) error { + if proc == nil { + return nil + } + rt := moruntime.ServiceRuntime(proc.GetService()) + if rt == nil { + return moerr.NewNotSupported( + ctx, + "mo_current_roles requires all CNs to support protocol version 33", + ) + } + value, ok := rt.GetGlobalVariables(moruntime.MOProtocolVersion) + version, valid := value.(int64) + if !ok || !valid || version < defines.MORPCVersion33 { + return moerr.NewNotSupported( + ctx, + "mo_current_roles requires all CNs to support protocol version 33", + ) + } + return nil +} + +func (builder *QueryBuilder) buildCurrentRoles( + tbl *tree.TableFunction, + ctx *BindContext, + exprs []*planpb.Expr, + children []int32, +) (int32, error) { + if len(tbl.Func.Exprs) != 0 { + return 0, moerr.NewInvalidArg(builder.GetContext(), + "mo_current_roles function has invalid input args length", len(tbl.Func.Exprs)) + } + if err := requireCurrentRolesProtocol(builder.GetContext(), builder.compCtx.GetProcess()); err != nil { + return 0, err + } + + node := &planpb.Node{ + NodeType: planpb.Node_FUNCTION_SCAN, + Stats: &planpb.Stats{}, + TableDef: &planpb.TableDef{ + TableType: "func_table", + TblFunc: &planpb.TableFunction{ + Name: "mo_current_roles", + }, + Cols: DeepCopyColDefList(currentRolesColDefs), + }, + BindingTags: []int32{builder.genNewBindTag()}, + Children: children, + TblFuncExprList: exprs, + } + return builder.appendNode(node, ctx), nil +} diff --git a/pkg/sql/plan/current_roles_test.go b/pkg/sql/plan/current_roles_test.go new file mode 100644 index 0000000000000..f5a5f0defd6f3 --- /dev/null +++ b/pkg/sql/plan/current_roles_test.go @@ -0,0 +1,56 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plan + +import ( + "testing" + + "github.com/matrixorigin/matrixone/pkg/common/runtime" + "github.com/matrixorigin/matrixone/pkg/defines" + planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" + "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" + "github.com/stretchr/testify/require" +) + +func TestBuildCurrentRolesProtocolGate(t *testing.T) { + mock := NewMockOptimizer(false) + builder := NewQueryBuilder(planpb.Query_SELECT, mock.CurrentContext(), false, true) + ctx := NewBindContext(builder, nil) + proc := builder.compCtx.GetProcess() + rt := runtime.ServiceRuntime(proc.GetService()) + original, hadOriginal := rt.GetGlobalVariables(runtime.MOProtocolVersion) + t.Cleanup(func() { + if hadOriginal { + rt.SetGlobalVariables(runtime.MOProtocolVersion, original) + } else { + rt.SetGlobalVariables(runtime.MOProtocolVersion, defines.MORPCLatestVersion) + } + }) + + tf := &tree.TableFunction{Func: &tree.FuncExpr{}} + rt.SetGlobalVariables(runtime.MOProtocolVersion, defines.MORPCVersion32) + _, err := builder.buildCurrentRoles(tf, ctx, nil, nil) + require.ErrorContains(t, err, "protocol version 33") + + rt.SetGlobalVariables(runtime.MOProtocolVersion, defines.MORPCVersion33) + nodeID, err := builder.buildCurrentRoles(tf, ctx, nil, nil) + require.NoError(t, err) + require.Equal(t, planpb.Node_FUNCTION_SCAN, builder.qry.Nodes[nodeID].NodeType) + require.Equal(t, "role_id", builder.qry.Nodes[nodeID].TableDef.Cols[0].Name) + + tf.Func.Exprs = tree.Exprs{tree.NewNumVal(int64(1), "1", false, tree.P_int64)} + _, err = builder.buildCurrentRoles(tf, ctx, nil, nil) + require.ErrorContains(t, err, "invalid input args length") +} diff --git a/pkg/sql/plan/query_builder.go b/pkg/sql/plan/query_builder.go index 9bd5e8f32c9fc..665f77ce36890 100644 --- a/pkg/sql/plan/query_builder.go +++ b/pkg/sql/plan/query_builder.go @@ -12361,6 +12361,8 @@ func (builder *QueryBuilder) buildTableFunction(tbl *tree.TableFunction, ctx *Bi nodeId, err = builder.buildMoCache(tbl, ctx, exprs, nil) case "mo_check_constraints": nodeId, err = builder.buildCheckConstraints(tbl, ctx, exprs, nil) + case "mo_current_roles": + nodeId, err = builder.buildCurrentRoles(tbl, ctx, exprs, nil) case "fulltext_index_scan": nodeId, err = builder.buildFullTextIndexScan(tbl, ctx, exprs, nil) case "fulltext_index_tokenize": diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index a1a6cb6e7fef2..0ee8a2fab3332 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -154,16 +154,13 @@ var ( ) // informationSchemaMetadataVisibilityCTE limits user-object metadata to the -// objects visible to the session's single active role and roles directly -// granted to it. Keeping this relation non-recursive avoids distributed -// recursive pipelines in every information_schema query. System schemas remain -// universally visible for MySQL/tooling compatibility. +// objects visible to the session's active role and its full inherited-role +// closure. The closure is produced locally by mo_current_roles(), avoiding +// distributed recursive pipelines in every information_schema query. System +// schemas remain universally visible for MySQL/tooling compatibility. func informationSchemaMetadataVisibilityCTE() string { return "WITH __mo_active_roles(role_id) AS (" + - "SELECT cast(current_role_id() AS bigint) " + - "UNION " + - "SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg " + - "WHERE rg.grantee_id = current_role_id()), " + + "SELECT role_id FROM mo_current_roles() role_closure), " + "__mo_visible_tables AS (" + "SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, " + "tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, " + @@ -180,7 +177,18 @@ func informationSchemaMetadataVisibilityCTE() string { "OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) " + "OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND (" + "(rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) " + - "OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) " + "OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), " + + "__mo_visible_databases AS (" + + "SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db " + + "WHERE (db.account_id = current_account_id() AND (" + + "db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') " + + "OR db.owner IN (SELECT role_id FROM __mo_active_roles) " + + "OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) " + + "OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id " + + "WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND (" + + "(rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) " + + "OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) " + + "OR (db.account_id = 0 AND db.datname = 'mo_catalog')) " } // `information_schema` database @@ -298,14 +306,15 @@ var ( "IS_GRANTABLE varchar(3) NOT NULL DEFAULT ''" + ")" - InformationSchemaSchemataDDL = "CREATE VIEW information_schema.SCHEMATA AS SELECT " + + InformationSchemaSchemataDDL = "CREATE VIEW information_schema.SCHEMATA AS " + + informationSchemaMetadataVisibilityCTE() + "SELECT " + "'def' AS CATALOG_NAME," + "datname AS SCHEMA_NAME," + "'utf8mb4' AS DEFAULT_CHARACTER_SET_NAME," + "'" + DefaultCollationForCharset("utf8mb4") + "' AS DEFAULT_COLLATION_NAME," + "if(true, NULL, '') AS SQL_PATH," + "cast('NO' as varchar(3)) AS DEFAULT_ENCRYPTION " + - "FROM mo_catalog.mo_database where account_id = current_account_id() or (account_id = 0 and datname in ('mo_catalog'))" + "FROM __mo_visible_databases" InformationSchemaCharacterSetsDDL = "CREATE TABLE information_schema.CHARACTER_SETS (" + "CHARACTER_SET_NAME varchar(64)," + diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index ad326dd5e3841..b0c29da52cd5e 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -66,15 +66,16 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { {name: "check constraints", ddl: InformationSchemaCheckConstraintsDDL}, {name: "views", ddl: InformationSchemaViewsDDL}, {name: "partitions", ddl: InformationSchemaPartitionsDDL}, + {name: "schemata", ddl: InformationSchemaSchemataDDL}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { for _, expected := range []string{ "WITH __mo_active_roles(role_id)", - "SELECT cast(current_role_id() AS bigint)", - "WHERE rg.grantee_id = current_role_id()", + "SELECT role_id FROM mo_current_roles() role_closure", "__mo_visible_tables AS", + "__mo_visible_databases AS", "tbl.account_id = current_account_id()", "tbl.owner IN (SELECT role_id FROM __mo_active_roles)", "db.owner = ar.role_id", @@ -87,7 +88,7 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { assert.Contains(t, test.ddl, expected) } assert.NotContains(t, test.ddl, "WITH RECURSIVE") - assert.NotContains(t, test.ddl, "JOIN __mo_active_roles ar ON rg.grantee_id = ar.role_id") + assert.NotContains(t, test.ddl, "mo_catalog.mo_role_grant") assert.NotContains(t, test.ddl, "SELECT tbl.*") assert.NotContains(t, test.ddl, "current_role()") assert.NotContains(t, test.ddl, "FROM mo_catalog.mo_role ") @@ -115,6 +116,10 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { assert.Contains(t, InformationSchemaCheckConstraintsDDL, "JOIN __mo_visible_tables check_tbl") assert.Contains(t, InformationSchemaViewsDDL, "JOIN __mo_visible_tables visible_tbl") assert.Contains(t, InformationSchemaPartitionsDDL, "FROM `__mo_visible_tables` `tbl`") + assert.Contains(t, InformationSchemaSchemataDDL, "FROM __mo_visible_databases") + assert.Contains(t, InformationSchemaSchemataDDL, "db.owner IN (SELECT role_id FROM __mo_active_roles)") + assert.Contains(t, InformationSchemaSchemataDDL, + "EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id)") } func TestInformationSchemaStatisticsDDL_ContainsIdxAlgo(t *testing.T) { diff --git a/test/distributed/cases/dml/show/show.result b/test/distributed/cases/dml/show/show.result index 5eaa0f13041f2..b720c786d905e 100644 --- a/test/distributed/cases/dml/show/show.result +++ b/test/distributed/cases/dml/show/show.result @@ -525,5 +525,5 @@ create database test; use test; SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci drop database test; diff --git a/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result b/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result index 171b14edd5095..a55aa8482d350 100644 --- a/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result +++ b/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result @@ -62,5 +62,5 @@ referenced_table_name ¦ VARCHAR(64) ¦ YES ¦ ¦ null ¦ ¦ referenced_column_name ¦ VARCHAR(64) ¦ YES ¦ ¦ null ¦ ¦ show create table information_schema.KEY_COLUMN_USAGE; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci +key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci drop database fk_information_schema_key_column_usage; diff --git a/test/distributed/cases/mo_cloud/mo_cloud.result b/test/distributed/cases/mo_cloud/mo_cloud.result index 2838b743456aa..8506243396a5d 100644 --- a/test/distributed/cases/mo_cloud/mo_cloud.result +++ b/test/distributed/cases/mo_cloud/mo_cloud.result @@ -228,7 +228,7 @@ engines ¦ CREATE TABLE `engines` ( ) SHOW CREATE TABLE information_schema.key_column_usage; ➤ View[12,16,0] ¦ Create View[12,786,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 -key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci +key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci SHOW CREATE TABLE information_schema.keywords; ➤ Table[12,8,0] ¦ Create Table[12,101,0] 𝄀 keywords ¦ CREATE TABLE `keywords` ( diff --git a/test/distributed/cases/zz_accesscontrol/account_restricted.result b/test/distributed/cases/zz_accesscontrol/account_restricted.result index d5270edab96a2..a511e515c34da 100644 --- a/test/distributed/cases/zz_accesscontrol/account_restricted.result +++ b/test/distributed/cases/zz_accesscontrol/account_restricted.result @@ -158,7 +158,7 @@ GRANT values ON table *.* `admin`@`localhost` GRANT connect ON account `admin`@`localhost` SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 @@ -271,7 +271,7 @@ show grants for 'hnadmin'@'localhost'; Grants for hnadmin@localhost SHOW CREATE TABLE information_schema.columns; ➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT cast(current_role_id() AS bigint) UNION SELECT cast(rg.granted_id AS bigint) FROM mo_catalog.mo_role_grant rg WHERE rg.grantee_id = current_role_id()), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result index 3cf3857f1fb70..d9f947327eeb3 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result @@ -1,7 +1,7 @@ set global enable_privilege_cache = off; drop database if exists metadata_visibility_db; drop user if exists metadata_visibility_user; -drop role if exists metadata_visibility_primary, metadata_visibility_reader; +drop role if exists metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; create database metadata_visibility_db; create table metadata_visibility_db.allowed_table ( id int primary key, @@ -21,7 +21,7 @@ constraint fk_hidden_parent foreign key (id) references metadata_visibility_db.h ); create view metadata_visibility_db.hidden_view as select id, secret, payload from metadata_visibility_db.hidden_table; -create role metadata_visibility_primary, metadata_visibility_reader; +create role metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; create user metadata_visibility_user identified by '123456' default role metadata_visibility_primary; grant connect on account * to metadata_visibility_primary; select count(*) = 0 as tables_hidden @@ -44,6 +44,11 @@ from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; ➤ constraints_hidden[-7,1,0] 𝄀 1 +select count(*) = 0 as schema_hidden +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; +➤ schema_hidden[-7,1,0] 𝄀 +1 select (select count(*) = 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as check_constraints_hidden, @@ -62,18 +67,25 @@ select where table_schema = 'information_schema') and (select count(*) > 0 from information_schema.columns -where table_schema = 'information_schema') as system_metadata_visible; +where table_schema = 'information_schema') +and +(select count(*) = 1 from information_schema.schemata +where schema_name = 'information_schema') as system_metadata_visible; ➤ system_metadata_visible[-7,1,0] 𝄀 1 grant select on table metadata_visibility_db.allowed_table to metadata_visibility_reader; -grant metadata_visibility_reader to metadata_visibility_primary; +grant metadata_visibility_reader to metadata_visibility_middle; +grant metadata_visibility_middle to metadata_visibility_primary; +grant select on table metadata_visibility_db.hidden_parent to metadata_visibility_primary; select (select count(*) = 1 from information_schema.tables -where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_visible, +where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as inherited_table_visible, +(select count(*) = 1 from information_schema.tables +where table_schema = 'metadata_visibility_db' and table_name = 'hidden_parent') as direct_table_visible, (select count(*) = 0 from information_schema.tables where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_stays_hidden; -➤ allowed_visible[-7,1,0] ¦ hidden_stays_hidden[-7,1,0] 𝄀 -1 ¦ 1 +➤ inherited_table_visible[-7,1,0] ¦ direct_table_visible[-7,1,0] ¦ hidden_stays_hidden[-7,1,0] 𝄀 +1 ¦ 1 ¦ 1 select (select count(*) = 3 from information_schema.columns where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_columns_visible, @@ -116,6 +128,11 @@ where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as hidden_view_hidden; ➤ allowed_check_metadata_visible[-7,1,0] ¦ allowed_partition_metadata_visible[-7,1,0] ¦ hidden_fk_columns_hidden[-7,1,0] ¦ hidden_fk_constraint_hidden[-7,1,0] ¦ hidden_view_hidden[-7,1,0] 𝄀 1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 +select count(*) = 1 as inherited_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; +➤ inherited_schema_visible[-7,1,0] 𝄀 +1 alter role metadata_visibility_primary rename to metadata_visibility_primary_renamed; select count(*) = 1 as table_visible_after_active_role_rename from information_schema.tables @@ -176,9 +193,27 @@ where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count; ➤ table_count[-5,64,0] ¦ column_count[-5,64,0] ¦ statistic_count[-5,64,0] ¦ constraint_count[-5,64,0] 𝄀 0 ¦ 0 ¦ 0 ¦ 0 +select count(*) = 0 as schema_hidden_after_set_role +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; +➤ schema_hidden_after_set_role[-7,1,0] 𝄀 +1 deallocate prepare metadata_visibility_prepared; set role metadata_visibility_primary; +grant create database on account * to metadata_visibility_primary; +create database metadata_visibility_owned_db; +select count(*) = 1 as owned_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_owned_db'; +➤ owned_schema_visible[-7,1,0] 𝄀 +1 +drop database metadata_visibility_owned_db; grant show tables on database metadata_visibility_db to metadata_visibility_primary; +select count(*) = 1 as database_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; +➤ database_schema_visible[-7,1,0] 𝄀 +1 select count(*) = 2 as database_tables_visible from information_schema.tables where table_schema = 'metadata_visibility_db' @@ -216,6 +251,11 @@ where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as database_partitions_visible; ➤ database_checks_visible[-7,1,0] ¦ database_fk_columns_visible[-7,1,0] ¦ database_fk_constraints_visible[-7,1,0] ¦ database_view_visible[-7,1,0] ¦ database_partitions_visible[-7,1,0] 𝄀 1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 +select count(*) = 1 as admin_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; +➤ admin_schema_visible[-7,1,0] 𝄀 +1 select count(*) = 2 as admin_tables_visible from information_schema.tables where table_schema = 'metadata_visibility_db' @@ -255,5 +295,5 @@ where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') 1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 drop database metadata_visibility_db; drop user metadata_visibility_user; -drop role metadata_visibility_primary, metadata_visibility_reader; +drop role metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; set global enable_privilege_cache = on; diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql index d9848c4b51023..78a1b081d20ab 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql @@ -3,7 +3,7 @@ set global enable_privilege_cache = off; drop database if exists metadata_visibility_db; drop user if exists metadata_visibility_user; -drop role if exists metadata_visibility_primary, metadata_visibility_reader; +drop role if exists metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; create database metadata_visibility_db; create table metadata_visibility_db.allowed_table ( @@ -24,7 +24,7 @@ create table metadata_visibility_db.hidden_table ( ); create view metadata_visibility_db.hidden_view as select id, secret, payload from metadata_visibility_db.hidden_table; -create role metadata_visibility_primary, metadata_visibility_reader; +create role metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; create user metadata_visibility_user identified by '123456' default role metadata_visibility_primary; grant connect on account * to metadata_visibility_primary; @@ -41,6 +41,9 @@ where table_schema = 'metadata_visibility_db'; select count(*) = 0 as constraints_hidden from information_schema.table_constraints where table_schema = 'metadata_visibility_db'; +select count(*) = 0 as schema_hidden +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; select (select count(*) = 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as check_constraints_hidden, @@ -57,16 +60,23 @@ select where table_schema = 'information_schema') and (select count(*) > 0 from information_schema.columns - where table_schema = 'information_schema') as system_metadata_visible; + where table_schema = 'information_schema') + and + (select count(*) = 1 from information_schema.schemata + where schema_name = 'information_schema') as system_metadata_visible; -- @session grant select on table metadata_visibility_db.allowed_table to metadata_visibility_reader; -grant metadata_visibility_reader to metadata_visibility_primary; +grant metadata_visibility_reader to metadata_visibility_middle; +grant metadata_visibility_middle to metadata_visibility_primary; +grant select on table metadata_visibility_db.hidden_parent to metadata_visibility_primary; -- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 select (select count(*) = 1 from information_schema.tables - where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as allowed_visible, + where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as inherited_table_visible, + (select count(*) = 1 from information_schema.tables + where table_schema = 'metadata_visibility_db' and table_name = 'hidden_parent') as direct_table_visible, (select count(*) = 0 from information_schema.tables where table_schema = 'metadata_visibility_db' and table_name = 'hidden_table') as hidden_stays_hidden; select @@ -103,6 +113,9 @@ select (select count(*) = 0 from information_schema.views where table_schema = 'metadata_visibility_db' and table_name = 'hidden_view') as hidden_view_hidden; +select count(*) = 1 as inherited_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; -- @session alter role metadata_visibility_primary rename to metadata_visibility_primary_renamed; @@ -158,13 +171,29 @@ select where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as statistic_count, (select count(*) from information_schema.table_constraints where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as constraint_count; +select count(*) = 0 as schema_hidden_after_set_role +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; deallocate prepare metadata_visibility_prepared; set role metadata_visibility_primary; -- @session +grant create database on account * to metadata_visibility_primary; + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +create database metadata_visibility_owned_db; +select count(*) = 1 as owned_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_owned_db'; +-- @session + +drop database metadata_visibility_owned_db; grant show tables on database metadata_visibility_db to metadata_visibility_primary; -- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +select count(*) = 1 as database_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; select count(*) = 2 as database_tables_visible from information_schema.tables where table_schema = 'metadata_visibility_db' @@ -194,6 +223,9 @@ select where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as database_partitions_visible; -- @session +select count(*) = 1 as admin_schema_visible +from information_schema.schemata +where schema_name = 'metadata_visibility_db'; select count(*) = 2 as admin_tables_visible from information_schema.tables where table_schema = 'metadata_visibility_db' @@ -224,5 +256,5 @@ select drop database metadata_visibility_db; drop user metadata_visibility_user; -drop role metadata_visibility_primary, metadata_visibility_reader; +drop role metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; set global enable_privilege_cache = on; From b1df766af70ec4ce7bbba15a2b0f3e508e8e1643 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 28 Aug 2026 00:56:14 +0800 Subject: [PATCH 08/17] fix(sysview): preserve rolling tenant bootstrap --- pkg/frontend/authenticate_test.go | 48 ++++++++++++------- pkg/util/sysview/predefined.go | 24 +++++++++- pkg/util/sysview/predefined_test.go | 36 +++++++++++++- pkg/util/sysview/sysview.go | 30 ++++++++---- test/distributed/cases/dml/show/show.result | 4 +- ...information_schema_key_column_usage.result | 4 +- .../cases/mo_cloud/mo_cloud.result | 4 +- .../account_restricted.result | 8 ++-- ...nformation_schema_object_visibility.result | 20 ++++++++ .../information_schema_object_visibility.sql | 22 +++++++++ 10 files changed, 160 insertions(+), 40 deletions(-) diff --git a/pkg/frontend/authenticate_test.go b/pkg/frontend/authenticate_test.go index 77055df5b8c10..e0a9cf5c41a13 100644 --- a/pkg/frontend/authenticate_test.go +++ b/pkg/frontend/authenticate_test.go @@ -506,24 +506,36 @@ func Test_createTablesInMoCatalogOfGeneralTenant(t *testing.T) { func Test_createTablesInInformationSchemaOfGeneralTenant_UsesProtocolAwareViews(t *testing.T) { tests := []struct { - name string - protocol int64 - wantCheckView bool - wantLatestTable bool - wantLegacyTable bool - wantCheckFunction bool + name string + protocol int64 + wantCheckFunction bool + wantCurrentRoles bool + wantCompatibilityRoles bool + wantCanonicalViews bool }{ { - name: "mixed version protocol uses legacy table constraints", - protocol: defines.MORPCVersion15, - wantLegacyTable: true, + name: "pre check-constraint protocol uses compatibility roles", + protocol: defines.MORPCVersion15, + wantCompatibilityRoles: true, }, { - name: "latest protocol uses check constraints views", - protocol: defines.MORPCVersion16, - wantCheckView: true, - wantLatestTable: true, - wantCheckFunction: true, + name: "protocol 16 uses check constraints and compatibility roles", + protocol: defines.MORPCVersion16, + wantCheckFunction: true, + wantCompatibilityRoles: true, + }, + { + name: "protocol 32 does not install current-role table function views", + protocol: defines.MORPCVersion32, + wantCheckFunction: true, + wantCompatibilityRoles: true, + }, + { + name: "protocol 33 installs canonical full role closure views", + protocol: defines.MORPCVersion33, + wantCheckFunction: true, + wantCurrentRoles: true, + wantCanonicalViews: true, }, } @@ -554,10 +566,12 @@ func Test_createTablesInInformationSchemaOfGeneralTenant_UsesProtocolAwareViews( require.NoError(t, createTablesInInformationSchemaOfGeneralTenant(context.Background(), bh, "")) - require.Equal(t, test.wantCheckView, containsSQL(executed, sysview.InformationSchemaCheckConstraintsDDL)) - require.Equal(t, test.wantLatestTable, containsSQL(executed, sysview.InformationSchemaTableConstraintsDDL)) - require.Equal(t, test.wantLegacyTable, containsSQL(executed, sysview.InformationSchemaTableConstraintsLegacyDDL)) require.Equal(t, test.wantCheckFunction, containsSQLFragment(executed, "mo_check_constraints()")) + require.Equal(t, test.wantCurrentRoles, containsSQLFragment(executed, "mo_current_roles()")) + require.Equal(t, test.wantCompatibilityRoles, + containsSQLFragment(executed, "FROM mo_catalog.mo_role_grant rg")) + require.Equal(t, test.wantCanonicalViews, + containsSQL(executed, sysview.InformationSchemaTableConstraintsDDL)) }) }) } diff --git a/pkg/util/sysview/predefined.go b/pkg/util/sysview/predefined.go index 0ee8a2fab3332..78a5f992a1403 100644 --- a/pkg/util/sysview/predefined.go +++ b/pkg/util/sysview/predefined.go @@ -159,8 +159,25 @@ var ( // distributed recursive pipelines in every information_schema query. System // schemas remain universally visible for MySQL/tooling compatibility. func informationSchemaMetadataVisibilityCTE() string { - return "WITH __mo_active_roles(role_id) AS (" + - "SELECT role_id FROM mo_current_roles() role_closure), " + + return informationSchemaMetadataVisibilityCTEWithActiveRoles( + "SELECT role_id FROM mo_current_roles() role_closure") +} + +// informationSchemaMetadataVisibilityCompatibilityCTE is used only while a +// rolling deployment's common protocol is below the mo_current_roles() +// capability. It keeps tenant bootstrap executable on every CN and remains +// cycle-safe by limiting the compatibility closure to the active role and its +// directly inherited roles. The v33 same-version upgrade replaces these +// definitions with the complete canonical closure after all CNs support it. +func informationSchemaMetadataVisibilityCompatibilityCTE() string { + return informationSchemaMetadataVisibilityCTEWithActiveRoles( + "SELECT current_role_id() UNION " + + "SELECT rg.granted_id FROM mo_catalog.mo_role_grant rg " + + "WHERE rg.grantee_id = current_role_id()") +} + +func informationSchemaMetadataVisibilityCTEWithActiveRoles(activeRolesSQL string) string { + return "WITH __mo_active_roles(role_id) AS (" + activeRolesSQL + "), " + "__mo_visible_tables AS (" + "SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, " + "tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, " + @@ -185,6 +202,9 @@ func informationSchemaMetadataVisibilityCTE() string { "OR db.owner IN (SELECT role_id FROM __mo_active_roles) " + "OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) " + "OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id " + + "WHERE rp.obj_type = 'account' AND rp.privilege_name IN ('show databases','account all') " + + "AND rp.privilege_level = '*' AND rp.obj_id = 0) " + + "OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id " + "WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND (" + "(rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) " + "OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) " + diff --git a/pkg/util/sysview/predefined_test.go b/pkg/util/sysview/predefined_test.go index b0c29da52cd5e..fdbac5738546e 100644 --- a/pkg/util/sysview/predefined_test.go +++ b/pkg/util/sysview/predefined_test.go @@ -120,6 +120,10 @@ func TestInformationSchemaMetadataViewsEnforceObjectPrivileges(t *testing.T) { assert.Contains(t, InformationSchemaSchemataDDL, "db.owner IN (SELECT role_id FROM __mo_active_roles)") assert.Contains(t, InformationSchemaSchemataDDL, "EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id)") + assert.Contains(t, InformationSchemaSchemataDDL, + "rp.obj_type = 'account' AND rp.privilege_name IN ('show databases','account all')") + assert.Contains(t, InformationSchemaSchemataDDL, + "rp.privilege_level = '*' AND rp.obj_id = 0") } func TestInformationSchemaStatisticsDDL_ContainsIdxAlgo(t *testing.T) { @@ -167,16 +171,44 @@ func TestInitInformationSchemaSysTablesForProtocol(t *testing.T) { legacy := InitInformationSchemaSysTablesForProtocol(defines.MORPCVersion15) assert.NotContains(t, legacy, InformationSchemaCheckConstraintsDDL) assert.NotContains(t, legacy, InformationSchemaTableConstraintsDDL) - assert.Contains(t, legacy, InformationSchemaTableConstraintsLegacyDDL) + assert.Contains(t, legacy, + informationSchemaMetadataVisibilityCompatibilityDDL(InformationSchemaTableConstraintsLegacyDDL)) assert.Contains(t, legacy, InformationSchemaCollationCharacterSetApplicabilityDDL) for _, sql := range legacy { assert.NotContains(t, sql, "mo_check_constraints()") + assert.NotContains(t, sql, "mo_current_roles()") + assertInformationSchemaInitSQLParses(t, sql) + } + + for _, protocol := range []int64{defines.MORPCVersion16, defines.MORPCVersion32} { + t.Run(fmt.Sprintf("compatibility-v%d", protocol), func(t *testing.T) { + compatibility := InitInformationSchemaSysTablesForProtocol(protocol) + assert.Len(t, compatibility, len(InitInformationSchemaSysTables)) + assert.Contains(t, compatibility, + informationSchemaMetadataVisibilityCompatibilityDDL(InformationSchemaCheckConstraintsDDL)) + assert.Contains(t, compatibility, + informationSchemaMetadataVisibilityCompatibilityDDL(InformationSchemaTableConstraintsDDL)) + for _, sql := range compatibility { + assert.NotContains(t, sql, "mo_current_roles()") + assertInformationSchemaInitSQLParses(t, sql) + } + assert.Contains(t, strings.Join(compatibility, "\n"), "FROM mo_catalog.mo_role_grant rg") + }) } - latest := InitInformationSchemaSysTablesForProtocol(defines.MORPCVersion16) + latest := InitInformationSchemaSysTablesForProtocol(defines.MORPCVersion33) assert.Equal(t, InitInformationSchemaSysTables, latest) } +func assertInformationSchemaInitSQLParses(t *testing.T, sql string) { + t.Helper() + statements, err := mysql.Parse(context.Background(), sql, 1) + assert.NoError(t, err) + for _, statement := range statements { + statement.Free() + } +} + func TestInformationSchemaStatisticsDDL_RestrictsCatalogJoins(t *testing.T) { assert.True(t, strings.Contains(InformationSchemaStatisticsDDL, "`tcl`.`account_id` = `tbl`.`account_id`")) assert.True(t, strings.Contains(InformationSchemaStatisticsDDL, "`tcl`.`att_database` = `tbl`.`reldatabase`")) diff --git a/pkg/util/sysview/sysview.go b/pkg/util/sysview/sysview.go index 1a28f4a297a46..0148b3f64b668 100644 --- a/pkg/util/sysview/sysview.go +++ b/pkg/util/sysview/sysview.go @@ -17,6 +17,7 @@ package sysview import ( "context" "fmt" + "strings" "time" "github.com/matrixorigin/matrixone/pkg/common/moerr" @@ -75,24 +76,35 @@ var ( ) func InitInformationSchemaSysTablesForProtocol(protocol int64) []string { - if protocol >= defines.MORPCVersion16 { + if protocol >= defines.MORPCVersion33 { return InitInformationSchemaSysTables } - sqls := make([]string, 0, len(InitInformationSchemaSysTables)-1) + includeCheckConstraints := protocol >= defines.MORPCVersion16 + sqls := make([]string, 0, len(InitInformationSchemaSysTables)) for _, sql := range InitInformationSchemaSysTables { - switch sql { - case InformationSchemaCheckConstraintsDDL: - continue - case InformationSchemaTableConstraintsDDL: - sqls = append(sqls, InformationSchemaTableConstraintsLegacyDDL) - default: - sqls = append(sqls, sql) + if !includeCheckConstraints { + switch sql { + case InformationSchemaCheckConstraintsDDL: + continue + case InformationSchemaTableConstraintsDDL: + sql = InformationSchemaTableConstraintsLegacyDDL + } } + sqls = append(sqls, informationSchemaMetadataVisibilityCompatibilityDDL(sql)) } return sqls } +func informationSchemaMetadataVisibilityCompatibilityDDL(sql string) string { + return strings.Replace( + sql, + informationSchemaMetadataVisibilityCTE(), + informationSchemaMetadataVisibilityCompatibilityCTE(), + 1, + ) +} + func InitSchema(ctx context.Context, txn executor.TxnExecutor) error { if err := initMysqlTables(ctx, txn); err != nil { return err diff --git a/test/distributed/cases/dml/show/show.result b/test/distributed/cases/dml/show/show.result index b720c786d905e..018d1dea8f88b 100644 --- a/test/distributed/cases/dml/show/show.result +++ b/test/distributed/cases/dml/show/show.result @@ -524,6 +524,6 @@ drop database if exists test; create database test; use test; SHOW CREATE TABLE information_schema.columns; -➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +➤ View[12,7,0] ¦ Create View[12,6014,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'account' AND rp.privilege_name IN ('show databases','account all') AND rp.privilege_level = '*' AND rp.obj_id = 0) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci drop database test; diff --git a/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result b/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result index a55aa8482d350..bcfb7596fd43c 100644 --- a/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result +++ b/test/distributed/cases/foreign_key/fk_information_schema_key_column_usage.result @@ -61,6 +61,6 @@ referenced_table_schema ¦ VARCHAR(64) ¦ YES ¦ ¦ null ¦ ¦ referenced_table_name ¦ VARCHAR(64) ¦ YES ¦ ¦ null ¦ ¦ 𝄀 referenced_column_name ¦ VARCHAR(64) ¦ YES ¦ ¦ null ¦ ¦ show create table information_schema.KEY_COLUMN_USAGE; -➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci +➤ View[12,16,0] ¦ Create View[12,4530,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 +key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'account' AND rp.privilege_name IN ('show databases','account all') AND rp.privilege_level = '*' AND rp.obj_id = 0) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci drop database fk_information_schema_key_column_usage; diff --git a/test/distributed/cases/mo_cloud/mo_cloud.result b/test/distributed/cases/mo_cloud/mo_cloud.result index 8506243396a5d..80d22eeccdf68 100644 --- a/test/distributed/cases/mo_cloud/mo_cloud.result +++ b/test/distributed/cases/mo_cloud/mo_cloud.result @@ -227,8 +227,8 @@ engines ¦ CREATE TABLE `engines` ( `SAVEPOINTS` varchar(3) DEFAULT NULL ) SHOW CREATE TABLE information_schema.key_column_usage; -➤ View[12,16,0] ¦ Create View[12,786,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 -key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci +➤ View[12,16,0] ¦ Create View[12,4530,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 +key_column_usage ¦ CREATE VIEW information_schema.KEY_COLUMN_USAGE AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'account' AND rp.privilege_name IN ('show databases','account all') AND rp.privilege_level = '*' AND rp.obj_id = 0) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(idx.name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(coalesce(tbl.reldatabase, '') AS varchar(64)) AS TABLE_SCHEMA, CAST(coalesce(tbl.relname, '') AS varchar(64)) AS TABLE_NAME, CAST(idx.column_name AS varchar(64)) AS COLUMN_NAME, CAST(idx.ordinal_position AS int unsigned) AS ORDINAL_POSITION, CAST(NULL AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(NULL AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(NULL AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_indexes idx JOIN __mo_visible_tables tbl ON idx.table_id = tbl.rel_id WHERE tbl.account_id = current_account_id() AND idx.type IN ('PRIMARY', 'UNIQUE') AND NOT startswith(tbl.relname, '__mo_index_') AND not (tbl.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(tbl.relkind, ''), coalesce(tbl.relname, ''), coalesce(tbl.reldatabase, ''), coalesce(tbl.rel_createsql, ''), coalesce(tbl.extra_info, '')) or (coalesce(tbl.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(tbl.relname, '^__mo_tmp_[0-9a-f]{32}_'))) UNION ALL SELECT CAST('def' AS varchar(64)) AS CONSTRAINT_CATALOG, CAST(fk.db_name AS varchar(64)) AS CONSTRAINT_SCHEMA, CAST(fk.constraint_name AS varchar(64)) AS CONSTRAINT_NAME, CAST('def' AS varchar(64)) AS TABLE_CATALOG, CAST(fk.db_name AS varchar(64)) AS TABLE_SCHEMA, CAST(fk.table_name AS varchar(64)) AS TABLE_NAME, CAST(fk.column_name AS varchar(64)) AS COLUMN_NAME, CAST(fk.constraint_id AS int unsigned) AS ORDINAL_POSITION, CAST(fk.constraint_id AS int unsigned) AS POSITION_IN_UNIQUE_CONSTRAINT, CAST(fk.refer_db_name AS varchar(64)) AS REFERENCED_TABLE_SCHEMA, CAST(fk.refer_table_name AS varchar(64)) AS REFERENCED_TABLE_NAME, CAST(fk.refer_column_name AS varchar(64)) AS REFERENCED_COLUMN_NAME FROM mo_catalog.mo_foreign_keys fk JOIN __mo_visible_tables fk_tbl ON fk.db_name = fk_tbl.reldatabase AND fk.table_name = fk_tbl.relname ¦ utf8mb4 ¦ utf8mb4_general_ci SHOW CREATE TABLE information_schema.keywords; ➤ Table[12,8,0] ¦ Create Table[12,101,0] 𝄀 keywords ¦ CREATE TABLE `keywords` ( diff --git a/test/distributed/cases/zz_accesscontrol/account_restricted.result b/test/distributed/cases/zz_accesscontrol/account_restricted.result index a511e515c34da..e3cafc207a7ee 100644 --- a/test/distributed/cases/zz_accesscontrol/account_restricted.result +++ b/test/distributed/cases/zz_accesscontrol/account_restricted.result @@ -157,8 +157,8 @@ GRANT table ownership ON table *.* `admin`@`localhost` GRANT values ON table *.* `admin`@`localhost` GRANT connect ON account `admin`@`localhost` SHOW CREATE TABLE information_schema.columns; -➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +➤ View[12,7,0] ¦ Create View[12,6014,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'account' AND rp.privilege_name IN ('show databases','account all') AND rp.privilege_level = '*' AND rp.obj_id = 0) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 @@ -270,8 +270,8 @@ version_comment MatrixOne show grants for 'hnadmin'@'localhost'; Grants for hnadmin@localhost SHOW CREATE TABLE information_schema.columns; -➤ View[12,-1,0] ¦ Create View[12,-1,0] ¦ character_set_client[12,-1,0] ¦ collation_connection[12,-1,0] 𝄀 -columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci +➤ View[12,7,0] ¦ Create View[12,6014,0] ¦ character_set_client[12,7,0] ¦ collation_connection[12,18,0] 𝄀 +columns ¦ CREATE VIEW information_schema.COLUMNS AS WITH __mo_active_roles(role_id) AS (SELECT role_id FROM mo_current_roles() role_closure), __mo_visible_tables AS (SELECT tbl.account_id, tbl.rel_id, tbl.relname, tbl.reldatabase, tbl.reldatabase_id, tbl.relkind, tbl.rel_createsql, tbl.created_time, tbl.partitioned, tbl.rel_comment, tbl.extra_info, tbl.rel_logical_id, tbl.owner, tbl.`constraint` FROM mo_catalog.mo_tables tbl WHERE tbl.account_id = current_account_id() AND (tbl.reldatabase IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR tbl.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM mo_catalog.mo_database db JOIN __mo_active_roles ar ON db.owner = ar.role_id WHERE db.dat_id = tbl.reldatabase_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE (rp.obj_type IN ('table','view') AND ((rp.privilege_level = '*.*' AND rp.obj_id = 0) OR (rp.privilege_level IN ('d.*','*') AND rp.obj_id = tbl.reldatabase_id) OR (rp.privilege_level IN ('d.t','t') AND rp.obj_id = tbl.rel_logical_id))) OR (rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = tbl.reldatabase_id)))))), __mo_visible_databases AS (SELECT db.account_id, db.dat_id, db.datname, db.owner FROM mo_catalog.mo_database db WHERE (db.account_id = current_account_id() AND (db.datname IN ('mo_catalog','information_schema','mysql','system','system_metrics','mo_task','mo_debug') OR db.owner IN (SELECT role_id FROM __mo_active_roles) OR EXISTS (SELECT 1 FROM __mo_visible_tables tbl WHERE tbl.reldatabase_id = db.dat_id) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'account' AND rp.privilege_name IN ('show databases','account all') AND rp.privilege_level = '*' AND rp.obj_id = 0) OR EXISTS (SELECT 1 FROM mo_catalog.mo_role_privs rp JOIN __mo_active_roles ar ON rp.role_id = ar.role_id WHERE rp.obj_type = 'database' AND rp.privilege_name IN ('show tables','database all','database ownership') AND ((rp.privilege_level IN ('*','*.*') AND rp.obj_id = 0) OR (rp.privilege_level = 'd' AND rp.obj_id = db.dat_id))))) OR (db.account_id = 0 AND db.datname = 'mo_catalog')) select 'def' as TABLE_CATALOG,mc.att_database as TABLE_SCHEMA,mc.att_relname AS TABLE_NAME,mc.attname AS COLUMN_NAME,mc.attnum AS ORDINAL_POSITION,mo_show_visible_bin(mc.att_default,1) as COLUMN_DEFAULT,(case when mc.attnotnull != 0 then 'NO' else 'YES' end) as IS_NULLABLE,lower(case when length(mc.attr_enum) > 0 then (case when mo_show_visible_bin(mc.atttyp,2) = 'GEOMETRY' then upper(case when upper(split_part(mc.attr_enum, ';', 1)) like 'SRID=%' then 'GEOMETRY' else split_part(mc.attr_enum, ';', 1) end) else upper(split_part(mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum), '(', 1)) end) else (case when upper(mo_show_visible_bin(mc.atttyp,2)) = 'BOOL' then 'TINYINT' else split_part(mo_show_visible_bin(mc.atttyp,2), ' ', 1) end) end) as DATA_TYPE,internal_char_length(mc.atttyp) AS CHARACTER_MAXIMUM_LENGTH,internal_char_size(mc.atttyp) AS CHARACTER_OCTET_LENGTH,internal_numeric_precision(mc.atttyp) AS NUMERIC_PRECISION,internal_numeric_scale(mc.atttyp) AS NUMERIC_SCALE,internal_datetime_scale(mc.atttyp) AS DATETIME_PRECISION,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8' WHEN 1 then 'utf8' else NULL end) AS CHARACTER_SET_NAME,(case internal_column_character_set(mc.atttyp) WHEN 0 then 'utf8_bin' WHEN 1 then 'utf8_bin' else NULL end) AS COLLATION_NAME,(case when length(mc.attr_enum) > 0 then mo_show_visible_bin_enum(mc.atttyp, mc.attr_enum) else mo_show_visible_bin(mc.atttyp,3) end) as COLUMN_TYPE,case when mc.att_constraint_type = 'p' or mk.key_priority = 3 then 'PRI' when mk.key_priority = 2 then 'UNI' when mk.key_priority = 1 then 'MUL' else '' end as COLUMN_KEY,cast(case when mc.att_is_auto_increment = 1 then 'auto_increment' when mc.attr_has_generated = 1 then ifnull(mo_show_visible_bin(mc.attr_generated, 6), '') else '' end as varchar(24)) as EXTRA,'select,insert,update,references' as `PRIVILEGES`,mc.att_comment as COLUMN_COMMENT,cast(case when mc.attr_has_generated = 1 then ifnull(cast(mo_show_visible_bin(mc.attr_generated, 5) as varchar(500)), '') else '' end as varchar(500)) as GENERATION_EXPRESSION,(case when upper(mo_show_visible_bin(mc.atttyp,3)) like '% SRID %' then cast(split_part(upper(mo_show_visible_bin(mc.atttyp,3)), ' SRID ', 2) as bigint) else NULL end) as SRS_ID from mo_catalog.mo_columns mc join __mo_visible_tables mt ON mc.account_id = mt.account_id AND mc.att_database = mt.reldatabase AND mc.att_relname = mt.relname left join (select ki.table_id, ki.column_name, max(case when ki.type = 'PRIMARY' then 3 when ki.type = 'UNIQUE' and kp.part_count = 1 then 2 else 1 end) as key_priority from mo_catalog.mo_indexes ki join (select id, count(*) as part_count from mo_catalog.mo_indexes group by id) kp on ki.id = kp.id where (ki.type = 'PRIMARY' or ki.ordinal_position = 1) and ki.type in ('PRIMARY', 'UNIQUE', 'MULTIPLE', 'FULLTEXT', 'SPATIAL') group by ki.table_id, ki.column_name) mk ON mk.table_id = mt.rel_id AND mk.column_name = mc.attname where mc.account_id = current_account_id() and mc.att_is_hidden = 0 and mc.att_relname!='mo_increment_columns' and mc.att_relname not like '__mo_cpkey_%' and mc.attname != '__mo_rowid' and mc.att_relname not like '\%!\%%\%!\%%' and mc.att_relname != '__mo_account_lock' and not startswith(mc.att_relname, '__mo_index_') and not (mt.relkind = 'temporary_table' or mo_is_legacy_temporary_table(coalesce(mt.relkind, ''), coalesce(mt.relname, ''), coalesce(mt.reldatabase, ''), coalesce(mt.rel_createsql, ''), coalesce(mt.extra_info, '')) or (coalesce(mt.relkind, '') not in ('r', 'v', 'e', 'm', 's', 'cluster', 'partition', 'S') and regexp_like(mt.relname, '^__mo_tmp_[0-9a-f]{32}_'))) ¦ utf8mb4 ¦ utf8mb4_general_ci show index from r_test; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Index_params Visible Expression r_test 0 ui 1 c1 A 0 NULL NULL YES YES c1 diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result index d9f947327eeb3..cca0f25a3003b 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.result @@ -1,8 +1,10 @@ set global enable_privilege_cache = off; drop database if exists metadata_visibility_db; +drop database if exists metadata_visibility_show_db; drop user if exists metadata_visibility_user; drop role if exists metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; create database metadata_visibility_db; +create database metadata_visibility_show_db; create table metadata_visibility_db.allowed_table ( id int primary key, secret varchar(20) unique, @@ -49,6 +51,11 @@ from information_schema.schemata where schema_name = 'metadata_visibility_db'; ➤ schema_hidden[-7,1,0] 𝄀 1 +select count(*) = 0 as empty_schema_hidden_without_show_databases +from information_schema.schemata +where schema_name = 'metadata_visibility_show_db'; +➤ empty_schema_hidden_without_show_databases[-7,1,0] 𝄀 +1 select (select count(*) = 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as check_constraints_hidden, @@ -208,6 +215,18 @@ where schema_name = 'metadata_visibility_owned_db'; ➤ owned_schema_visible[-7,1,0] 𝄀 1 drop database metadata_visibility_owned_db; +grant show databases on account * to metadata_visibility_primary; +select count(*) = 1 as empty_schema_visible_with_show_databases +from information_schema.schemata +where schema_name = 'metadata_visibility_show_db'; +➤ empty_schema_visible_with_show_databases[-7,1,0] 𝄀 +1 +revoke show databases on account * from metadata_visibility_primary; +select count(*) = 0 as empty_schema_hidden_after_show_databases_revoke +from information_schema.schemata +where schema_name = 'metadata_visibility_show_db'; +➤ empty_schema_hidden_after_show_databases_revoke[-7,1,0] 𝄀 +1 grant show tables on database metadata_visibility_db to metadata_visibility_primary; select count(*) = 1 as database_schema_visible from information_schema.schemata @@ -294,6 +313,7 @@ where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') ➤ admin_checks_visible[-7,1,0] ¦ admin_fk_columns_visible[-7,1,0] ¦ admin_fk_constraints_visible[-7,1,0] ¦ admin_view_visible[-7,1,0] ¦ admin_partitions_visible[-7,1,0] 𝄀 1 ¦ 1 ¦ 1 ¦ 1 ¦ 1 drop database metadata_visibility_db; +drop database metadata_visibility_show_db; drop user metadata_visibility_user; drop role metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; set global enable_privilege_cache = on; diff --git a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql index 78a1b081d20ab..144b576bc6284 100644 --- a/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql +++ b/test/distributed/cases/zz_accesscontrol/information_schema_object_visibility.sql @@ -2,10 +2,12 @@ set global enable_privilege_cache = off; drop database if exists metadata_visibility_db; +drop database if exists metadata_visibility_show_db; drop user if exists metadata_visibility_user; drop role if exists metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; create database metadata_visibility_db; +create database metadata_visibility_show_db; create table metadata_visibility_db.allowed_table ( id int primary key, secret varchar(20) unique, @@ -44,6 +46,9 @@ where table_schema = 'metadata_visibility_db'; select count(*) = 0 as schema_hidden from information_schema.schemata where schema_name = 'metadata_visibility_db'; +select count(*) = 0 as empty_schema_hidden_without_show_databases +from information_schema.schemata +where schema_name = 'metadata_visibility_show_db'; select (select count(*) = 0 from information_schema.check_constraints where constraint_schema = 'metadata_visibility_db') as check_constraints_hidden, @@ -188,6 +193,22 @@ where schema_name = 'metadata_visibility_owned_db'; -- @session drop database metadata_visibility_owned_db; +grant show databases on account * to metadata_visibility_primary; + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +select count(*) = 1 as empty_schema_visible_with_show_databases +from information_schema.schemata +where schema_name = 'metadata_visibility_show_db'; +-- @session + +revoke show databases on account * from metadata_visibility_primary; + +-- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 +select count(*) = 0 as empty_schema_hidden_after_show_databases_revoke +from information_schema.schemata +where schema_name = 'metadata_visibility_show_db'; +-- @session + grant show tables on database metadata_visibility_db to metadata_visibility_primary; -- @session:id=2&user=sys:metadata_visibility_user:metadata_visibility_primary&password=123456 @@ -255,6 +276,7 @@ select where table_schema = 'metadata_visibility_db' and table_name = 'allowed_table') as admin_partitions_visible; drop database metadata_visibility_db; +drop database metadata_visibility_show_db; drop user metadata_visibility_user; drop role metadata_visibility_primary, metadata_visibility_middle, metadata_visibility_reader; set global enable_privilege_cache = on; From 5dfc476baf7948d9d84adf56214ee258a2e1b343 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 28 Aug 2026 12:37:32 +0800 Subject: [PATCH 09/17] test(upgrade): provide current protocol for metadata views --- pkg/bootstrap/versions/v4_0_6/upgrade_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go index 0a99eb27369a4..98918b9cf14b8 100644 --- a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go +++ b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go @@ -687,6 +687,10 @@ func TestKeyColumnUsageViewUpgradeIsOrderedAndIdempotent(t *testing.T) { var executed []string txnExecutor := newVersionTxnExecutor(t, func(sql string) (executor.Result, error) { + if strings.Contains(strings.ToLower(sql), "getprotocolversion") { + return newProtocolVersionResultValue(t, + `{"method":"GETPROTOCOLVERSION","result":"cn-a:35,cn-b:35"}`), nil + } executed = append(executed, sql) if sql == entry.PostSql { upgraded = true From 66efe77d13d1a9147b55eaef0830d15bd4cbd54d Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 28 Aug 2026 14:57:03 +0800 Subject: [PATCH 10/17] test(upgrade): remove unused protocol result helper --- pkg/bootstrap/versions/v4_0_6/upgrade_test.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go index 98918b9cf14b8..23510a6057d6a 100644 --- a/pkg/bootstrap/versions/v4_0_6/upgrade_test.go +++ b/pkg/bootstrap/versions/v4_0_6/upgrade_test.go @@ -1235,18 +1235,6 @@ func newHistoricalCreateSQLResult(t *testing.T, createSQL string) executor.Resul return result.GetResult() } -func newProtocolVersionResult(t *testing.T) executor.Result { - t.Helper() - mp := mpool.MustNewZeroNoFixed() - t.Cleanup(func() { mpool.DeleteMPool(mp) }) - result := executor.NewMemResult([]types.Type{types.T_varchar.ToType()}, mp) - result.NewBatchWithRowCount(1) - if err := executor.AppendStringRows(result, 0, []string{`{"method":"GETPROTOCOLVERSION","result":"cn-a:13, cn-b:13"}`}); err != nil { - t.Fatalf("append protocol version result: %v", err) - } - return result.GetResult() -} - func newLegacyForeignKeyIndexResult(t *testing.T, rows [][]string) executor.Result { t.Helper() mp := mpool.MustNewZeroNoFixed() From f19d675424bc2ef476f38232e23019a271f5439f Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 28 Aug 2026 21:59:06 +0800 Subject: [PATCH 11/17] perf(plan): share current-role closure per statement --- .../table_function/current_roles_test.go | 41 +++++++++ pkg/sql/plan/cte_lazy_binding_test.go | 92 ++++++++++++++++++- pkg/sql/plan/cte_reuse.go | 63 ++++++++++++- 3 files changed, 192 insertions(+), 4 deletions(-) diff --git a/pkg/sql/colexec/table_function/current_roles_test.go b/pkg/sql/colexec/table_function/current_roles_test.go index 7fd7fca5d0310..00364e01f79bb 100644 --- a/pkg/sql/colexec/table_function/current_roles_test.go +++ b/pkg/sql/colexec/table_function/current_roles_test.go @@ -126,6 +126,47 @@ func BenchmarkCurrentRoleClosureLargeDisconnectedGraph(b *testing.B) { } } +func BenchmarkCurrentRoleClosureInternalSQLBoundary(b *testing.B) { + mp := mpool.MustNewZero() + defer mpool.DeleteMPool(mp) + run := func(_ *process.Process, sql string) (executor.Result, error) { + bat := batch.NewWithSize(1) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + var grantedID int64 + switch { + case strings.HasSuffix(sql, "(10)"): + grantedID = 20 + case strings.HasSuffix(sql, "(20)"): + grantedID = 30 + case strings.HasSuffix(sql, "(30)"): + default: + return executor.Result{}, errors.New("unexpected role-grant query") + } + if grantedID != 0 { + if err := vector.AppendFixed(bat.Vecs[0], grantedID, false, mp); err != nil { + return executor.Result{}, err + } + bat.SetRowCount(1) + } + return executor.Result{Mp: mp, Batches: []*batch.Batch{bat}}, nil + } + expand := func(proc *process.Process, frontier []int64, visit func(int64)) error { + return expandCurrentRoleFrontierWithRunner(proc, frontier, visit, run) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + roles, err := currentRoleClosure(nil, 10, expand) + if err != nil { + b.Fatal(err) + } + if len(roles) != 3 { + b.Fatalf("expected three roles, got %d", len(roles)) + } + } +} + func TestBuildCurrentRoleGrantQuery(t *testing.T) { require.Equal(t, "SELECT cast(granted_id AS bigint) FROM mo_catalog.mo_role_grant WHERE grantee_id IN (10,20,30)", diff --git a/pkg/sql/plan/cte_lazy_binding_test.go b/pkg/sql/plan/cte_lazy_binding_test.go index 67cb037445c12..fd2a67527108d 100644 --- a/pkg/sql/plan/cte_lazy_binding_test.go +++ b/pkg/sql/plan/cte_lazy_binding_test.go @@ -16,12 +16,15 @@ package plan import ( "math" + "strings" "testing" "github.com/matrixorigin/matrixone/pkg/container/types" planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/internal/materialized" + "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect/mysql" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" + "github.com/matrixorigin/matrixone/pkg/util/sysview" "github.com/stretchr/testify/require" ) @@ -105,6 +108,18 @@ func countReachableNodeType(query *Query, nodeType planpb.Node_NodeType) int { return count } +func countReachableTableFunction(query *Query, name string) int { + count := 0 + for nodeID := range cteReachablePlanNodes(query) { + node := query.Nodes[nodeID] + if node.NodeType == planpb.Node_FUNCTION_SCAN && node.TableDef != nil && + node.TableDef.TblFunc != nil && node.TableDef.TblFunc.Name == name { + count++ + } + } + return count +} + func requireSharedCTEGroupingFlags(t *testing.T, logicPlan *Plan, expected [][]bool) { t.Helper() query := logicPlan.GetQuery() @@ -673,7 +688,6 @@ func TestCTEReuseMemoryGuard(t *testing.T) { func TestCTEReuseRejectsExternalAndSideEffectingNodes(t *testing.T) { for _, nodeType := range []planpb.Node_NodeType{ - planpb.Node_FUNCTION_SCAN, planpb.Node_EXTERNAL_SCAN, planpb.Node_EXTERNAL_FUNCTION, planpb.Node_LOCK_OP, @@ -692,6 +706,82 @@ func TestCTEReuseRejectsExternalAndSideEffectingNodes(t *testing.T) { } } +func TestCTEReuseSharesOnlyStatementStableCurrentRolesFunction(t *testing.T) { + currentRoles := func() *planpb.Node { + return &planpb.Node{ + NodeType: planpb.Node_FUNCTION_SCAN, + TableDef: &planpb.TableDef{TblFunc: &planpb.TableFunction{Name: "mo_current_roles"}}, + } + } + + builder := &QueryBuilder{qry: &Query{Nodes: []*Node{currentRoles()}}} + require.True(t, builder.cteSubtreeIsDeterministic(0, make(map[int32]bool))) + require.True(t, builder.cteSubtreeContainsStatementStableFunction(0, make(map[int32]bool))) + + builder.qry.Nodes[0].TableDef.TblFunc.Name = "generate_series" + require.False(t, builder.cteSubtreeIsDeterministic(0, make(map[int32]bool))) + + builder.qry.Nodes[0] = currentRoles() + builder.qry.Nodes[0].TblFuncExprList = []*planpb.Expr{{}} + require.False(t, builder.cteSubtreeIsDeterministic(0, make(map[int32]bool))) + + builder.qry.Nodes = append(builder.qry.Nodes, &planpb.Node{NodeType: planpb.Node_VALUE_SCAN}) + builder.qry.Nodes[0] = currentRoles() + builder.qry.Nodes[0].Children = []int32{1} + require.False(t, builder.cteSubtreeIsDeterministic(0, make(map[int32]bool))) +} + +func TestInformationSchemaMetadataPlansShareCurrentRolesOnce(t *testing.T) { + views := []struct { + name string + ddl string + }{ + {name: "TABLES", ddl: sysview.InformationSchemaTablesDDL}, + {name: "COLUMNS", ddl: sysview.InformationSchemaColumnsDDL}, + {name: "STATISTICS", ddl: sysview.InformationSchemaStatisticsDDL}, + {name: "CHECK_CONSTRAINTS", ddl: sysview.InformationSchemaCheckConstraintsDDL}, + {name: "VIEWS", ddl: sysview.InformationSchemaViewsDDL}, + {name: "SCHEMATA", ddl: sysview.InformationSchemaSchemataDDL}, + } + for _, view := range views { + t.Run(view.name, func(t *testing.T) { + as := strings.Index(view.ddl, " AS ") + require.Greater(t, as, 0) + logicPlan, err := runOneStmt(NewMockOptimizer(false), t, view.ddl[as+4:]) + require.NoError(t, err) + query := logicPlan.GetQuery() + require.Equal(t, 1, countReachableTableFunction(query, "mo_current_roles")) + require.Equal(t, 1, countReachableNodeType(query, planpb.Node_SINK), + "the role closure must have one statement-local producer") + }) + } +} + +func BenchmarkInformationSchemaSchemataPlanSharesCurrentRoles(b *testing.B) { + as := strings.Index(sysview.InformationSchemaSchemataDDL, " AS ") + if as <= 0 { + b.Fatal("SCHEMATA DDL has no AS clause") + } + sql := sysview.InformationSchemaSchemataDDL[as+4:] + ctx := NewMockOptimizer(false).CurrentContext() + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + statements, err := mysql.Parse(ctx.GetContext(), sql, 1) + if err != nil { + b.Fatal(err) + } + logicPlan, err := BuildPlan(ctx, statements[0], false) + statements[0].Free() + if err != nil { + b.Fatal(err) + } + if scans := countReachableTableFunction(logicPlan.GetQuery(), "mo_current_roles"); scans != 1 { + b.Fatalf("expected one reachable mo_current_roles scan, got %d", scans) + } + } +} + func TestCTEReuseRecognizesGuardedRuntimeFilterExpression(t *testing.T) { col := func(pos int32) *planpb.Expr { return &planpb.Expr{ diff --git a/pkg/sql/plan/cte_reuse.go b/pkg/sql/plan/cte_reuse.go index 6fa9e9fc97b59..7a901f52fde41 100644 --- a/pkg/sql/plan/cte_reuse.go +++ b/pkg/sql/plan/cte_reuse.go @@ -53,19 +53,34 @@ func (builder *QueryBuilder) reuseMultiReferenceCTEs(rootID int32) int32 { } func (builder *QueryBuilder) canReuseCTE(cteRef *CTERef, rootID int32) bool { - if cteRef == nil || cteRef.isRecursive || len(cteRef.occurrences) < 2 || - cteRef.hasNestedRef || cteRef.hasNestedUse { + if cteRef == nil || cteRef.isRecursive || len(cteRef.occurrences) < 2 || cteRef.hasNestedRef { return false } first := cteRef.occurrences[0] + allOccurrencesContainStatementStableFunction := true for _, occurrence := range cteRef.occurrences { if occurrence.isCorrelated || !sameCTEOutput(first, occurrence) || !builder.cteSubtreeIsDeterministic(occurrence.rootID, make(map[int32]bool)) { return false } + allOccurrencesContainStatementStableFunction = + allOccurrencesContainStatementStableFunction && + builder.cteSubtreeContainsStatementStableFunction(occurrence.rootID, make(map[int32]bool)) } + if cteRef.hasNestedUse && !allOccurrencesContainStatementStableFunction { + return false + } + if allOccurrencesContainStatementStableFunction { + // Unlike a generic CTE, mo_current_roles already computes its complete + // closure into one fixed-width batch before producing any row. LIMIT and + // SEMI/ANTI consumers therefore cannot save internal SQL work by keeping + // it inline. Require every occurrence to be in the rewritten root graph, + // then share one statement-local producer even when a consumer can stop + // early. + return builder.cteOccurrencesReachable(rootID, cteRef.occurrences) + } if !builder.cteConsumersFullyDrain(rootID, cteRef.occurrences) { return false } @@ -154,6 +169,33 @@ func samePlanType(left, right planpb.Type) bool { left.Enumvalues == right.Enumvalues && left.Charset == right.Charset } +func statementStableFunctionScan(node *planpb.Node) bool { + return node != nil && node.NodeType == planpb.Node_FUNCTION_SCAN && + node.TableDef != nil && node.TableDef.TblFunc != nil && + node.TableDef.TblFunc.Name == "mo_current_roles" && + len(node.TblFuncExprList) == 0 && len(node.Children) == 0 +} + +func (builder *QueryBuilder) cteSubtreeContainsStatementStableFunction( + nodeID int32, + seen map[int32]bool, +) bool { + if seen[nodeID] { + return false + } + seen[nodeID] = true + node := builder.qry.Nodes[nodeID] + if statementStableFunctionScan(node) { + return true + } + for _, childID := range node.Children { + if builder.cteSubtreeContainsStatementStableFunction(childID, seen) { + return true + } + } + return false +} + func (builder *QueryBuilder) cteSubtreeIsDeterministic(nodeID int32, seen map[int32]bool) bool { if seen[nodeID] { return true @@ -161,7 +203,11 @@ func (builder *QueryBuilder) cteSubtreeIsDeterministic(nodeID int32, seen map[in seen[nodeID] = true node := builder.qry.Nodes[nodeID] switch node.NodeType { - case planpb.Node_FUNCTION_SCAN, planpb.Node_EXTERNAL_SCAN, + case planpb.Node_FUNCTION_SCAN: + if !statementStableFunctionScan(node) { + return false + } + case planpb.Node_EXTERNAL_SCAN, planpb.Node_EXTERNAL_FUNCTION, planpb.Node_LOCK_OP, planpb.Node_INSERT, planpb.Node_DELETE, planpb.Node_MULTI_UPDATE, planpb.Node_POSTDML, planpb.Node_RECURSIVE_CTE, planpb.Node_RECURSIVE_SCAN, planpb.Node_SINK, @@ -240,6 +286,17 @@ func (builder *QueryBuilder) cteSubtreeIsDeterministic(nodeID int32, seen map[in return true } +func (builder *QueryBuilder) cteOccurrencesReachable(rootID int32, occurrences []cteOccurrence) bool { + reachable := make(map[int32]bool) + builder.collectCTEParents(rootID, make(map[int32][]int32), reachable) + for _, occurrence := range occurrences { + if !reachable[occurrence.rootID] { + return false + } + } + return true +} + func (builder *QueryBuilder) cteConsumersFullyDrain(rootID int32, occurrences []cteOccurrence) bool { parents := make(map[int32][]int32) reachable := make(map[int32]bool) From a0bd0097c90d7e0252fe7d8adc39fd1857f55036 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 28 Aug 2026 23:19:37 +0800 Subject: [PATCH 12/17] fix(plan): constrain current-role CTE reuse --- pkg/sql/plan/cte_lazy_binding_test.go | 46 +++++++++++++++++++++++++- pkg/sql/plan/cte_reuse.go | 47 +++++++++++++++------------ 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/pkg/sql/plan/cte_lazy_binding_test.go b/pkg/sql/plan/cte_lazy_binding_test.go index fd2a67527108d..0bc4a44ef0fc6 100644 --- a/pkg/sql/plan/cte_lazy_binding_test.go +++ b/pkg/sql/plan/cte_lazy_binding_test.go @@ -716,7 +716,9 @@ func TestCTEReuseSharesOnlyStatementStableCurrentRolesFunction(t *testing.T) { builder := &QueryBuilder{qry: &Query{Nodes: []*Node{currentRoles()}}} require.True(t, builder.cteSubtreeIsDeterministic(0, make(map[int32]bool))) - require.True(t, builder.cteSubtreeContainsStatementStableFunction(0, make(map[int32]bool))) + require.True(t, builder.cteSubtreeIsCurrentRoleClosure(0, make(map[int32]bool))) + require.True(t, currentRoleClosureOutput([]planpb.Type{{Id: int32(types.T_int64)}})) + require.False(t, currentRoleClosureOutput([]planpb.Type{{Id: int32(types.T_varchar)}})) builder.qry.Nodes[0].TableDef.TblFunc.Name = "generate_series" require.False(t, builder.cteSubtreeIsDeterministic(0, make(map[int32]bool))) @@ -729,6 +731,48 @@ func TestCTEReuseSharesOnlyStatementStableCurrentRolesFunction(t *testing.T) { builder.qry.Nodes[0] = currentRoles() builder.qry.Nodes[0].Children = []int32{1} require.False(t, builder.cteSubtreeIsDeterministic(0, make(map[int32]bool))) + + builder.qry.Nodes = []*Node{ + currentRoles(), + { + NodeType: planpb.Node_PROJECT, + Children: []int32{0}, + ProjectList: []*planpb.Expr{{Typ: planpb.Type{Id: int32(types.T_int64)}}}, + }, + } + require.True(t, builder.cteSubtreeIsCurrentRoleClosure(1, make(map[int32]bool))) + builder.qry.Nodes[1].ProjectList[0].Typ.Id = int32(types.T_varchar) + require.False(t, builder.cteSubtreeIsCurrentRoleClosure(1, make(map[int32]bool))) +} + +func TestCTEReuseCurrentRolesExemptionRejectsAmplifyingSubtree(t *testing.T) { + purePlan, err := runOneStmt(NewMockOptimizer(false), t, ` + WITH c AS (SELECT role_id FROM mo_current_roles() role_closure) + SELECT a.role_id FROM c a JOIN c b ON a.role_id = b.role_id LIMIT 1`) + require.NoError(t, err) + require.Equal(t, 1, countReachableNodeType(purePlan.GetQuery(), planpb.Node_SINK)) + require.Equal(t, 1, countReachableTableFunction(purePlan.GetQuery(), "mo_current_roles")) + + amplifiedPlan, err := runOneStmt(NewMockOptimizer(false), t, ` + WITH c AS ( + SELECT l.l_comment, r.role_id + FROM lineitem l CROSS JOIN mo_current_roles() r + ) + SELECT a.role_id FROM c a JOIN c b ON a.role_id = b.role_id LIMIT 1`) + require.NoError(t, err) + require.Zero(t, countReachableNodeType(amplifiedPlan.GetQuery(), planpb.Node_SINK), + "an early-terminating amplifying subtree must retain the guarded inline plan") + require.Equal(t, 2, countReachableTableFunction(amplifiedPlan.GetQuery(), "mo_current_roles")) + + variableWidthPlan, err := runOneStmt(NewMockOptimizer(false), t, ` + WITH c AS ( + SELECT l.l_comment, r.role_id + FROM lineitem l CROSS JOIN mo_current_roles() r + ) + SELECT count(*) FROM c a JOIN c b ON a.role_id = b.role_id`) + require.NoError(t, err) + require.Zero(t, countReachableNodeType(variableWidthPlan.GetQuery(), planpb.Node_SINK), + "a full-drain variable-width subtree must retain the materialization memory guard") } func TestInformationSchemaMetadataPlansShareCurrentRolesOnce(t *testing.T) { diff --git a/pkg/sql/plan/cte_reuse.go b/pkg/sql/plan/cte_reuse.go index 7a901f52fde41..f4caf85f50224 100644 --- a/pkg/sql/plan/cte_reuse.go +++ b/pkg/sql/plan/cte_reuse.go @@ -58,27 +58,26 @@ func (builder *QueryBuilder) canReuseCTE(cteRef *CTERef, rootID int32) bool { } first := cteRef.occurrences[0] - allOccurrencesContainStatementStableFunction := true + allOccurrencesAreCurrentRoleClosures := currentRoleClosureOutput(first.types) for _, occurrence := range cteRef.occurrences { if occurrence.isCorrelated || !sameCTEOutput(first, occurrence) || !builder.cteSubtreeIsDeterministic(occurrence.rootID, make(map[int32]bool)) { return false } - allOccurrencesContainStatementStableFunction = - allOccurrencesContainStatementStableFunction && - builder.cteSubtreeContainsStatementStableFunction(occurrence.rootID, make(map[int32]bool)) + allOccurrencesAreCurrentRoleClosures = allOccurrencesAreCurrentRoleClosures && + builder.cteSubtreeIsCurrentRoleClosure(occurrence.rootID, make(map[int32]bool)) } - if cteRef.hasNestedUse && !allOccurrencesContainStatementStableFunction { + if cteRef.hasNestedUse && !allOccurrencesAreCurrentRoleClosures { return false } - if allOccurrencesContainStatementStableFunction { - // Unlike a generic CTE, mo_current_roles already computes its complete - // closure into one fixed-width batch before producing any row. LIMIT and - // SEMI/ANTI consumers therefore cannot save internal SQL work by keeping - // it inline. Require every occurrence to be in the rewritten root graph, - // then share one statement-local producer even when a consumer can stop - // early. + if allOccurrencesAreCurrentRoleClosures { + // This exemption is deliberately limited to the one-column closure + // primitive itself. mo_current_roles computes its complete fixed-width + // batch before producing any row, so LIMIT and SEMI/ANTI consumers cannot + // save its internal SQL work. A scan, join, filter, aggregate, variable- + // width projection, or any other surrounding operation must use the + // ordinary full-drain, memory, and profitability guards below. return builder.cteOccurrencesReachable(rootID, cteRef.occurrences) } if !builder.cteConsumersFullyDrain(rootID, cteRef.occurrences) { @@ -176,7 +175,11 @@ func statementStableFunctionScan(node *planpb.Node) bool { len(node.TblFuncExprList) == 0 && len(node.Children) == 0 } -func (builder *QueryBuilder) cteSubtreeContainsStatementStableFunction( +func currentRoleClosureOutput(outputTypes []planpb.Type) bool { + return len(outputTypes) == 1 && outputTypes[0].Id == int32(types.T_int64) +} + +func (builder *QueryBuilder) cteSubtreeIsCurrentRoleClosure( nodeID int32, seen map[int32]bool, ) bool { @@ -186,14 +189,16 @@ func (builder *QueryBuilder) cteSubtreeContainsStatementStableFunction( seen[nodeID] = true node := builder.qry.Nodes[nodeID] if statementStableFunctionScan(node) { - return true - } - for _, childID := range node.Children { - if builder.cteSubtreeContainsStatementStableFunction(childID, seen) { - return true - } - } - return false + return len(node.FilterList) == 0 && node.Limit == nil && node.Offset == nil && + len(node.OrderBy) == 0 && len(node.RuntimeFilterProbeList) == 0 && + len(node.RuntimeFilterBuildList) == 0 + } + return node.NodeType == planpb.Node_PROJECT && len(node.Children) == 1 && + len(node.ProjectList) == 1 && node.ProjectList[0].Typ.Id == int32(types.T_int64) && + len(node.FilterList) == 0 && node.Limit == nil && node.Offset == nil && + len(node.OrderBy) == 0 && len(node.RuntimeFilterProbeList) == 0 && + len(node.RuntimeFilterBuildList) == 0 && + builder.cteSubtreeIsCurrentRoleClosure(node.Children[0], seen) } func (builder *QueryBuilder) cteSubtreeIsDeterministic(nodeID int32, seen map[int32]bool) bool { From ab256b6c2612716062a42cef7ff26affeb6a8d48 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sat, 29 Aug 2026 12:20:20 +0800 Subject: [PATCH 13/17] test(plan): cover early-stop current-role CTE consumers --- pkg/sql/plan/cte_lazy_binding_test.go | 45 +++++++++++++++++++++------ 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/pkg/sql/plan/cte_lazy_binding_test.go b/pkg/sql/plan/cte_lazy_binding_test.go index 0bc4a44ef0fc6..a5e828f9ab108 100644 --- a/pkg/sql/plan/cte_lazy_binding_test.go +++ b/pkg/sql/plan/cte_lazy_binding_test.go @@ -753,16 +753,41 @@ func TestCTEReuseCurrentRolesExemptionRejectsAmplifyingSubtree(t *testing.T) { require.Equal(t, 1, countReachableNodeType(purePlan.GetQuery(), planpb.Node_SINK)) require.Equal(t, 1, countReachableTableFunction(purePlan.GetQuery(), "mo_current_roles")) - amplifiedPlan, err := runOneStmt(NewMockOptimizer(false), t, ` - WITH c AS ( - SELECT l.l_comment, r.role_id - FROM lineitem l CROSS JOIN mo_current_roles() r - ) - SELECT a.role_id FROM c a JOIN c b ON a.role_id = b.role_id LIMIT 1`) - require.NoError(t, err) - require.Zero(t, countReachableNodeType(amplifiedPlan.GetQuery(), planpb.Node_SINK), - "an early-terminating amplifying subtree must retain the guarded inline plan") - require.Equal(t, 2, countReachableTableFunction(amplifiedPlan.GetQuery(), "mo_current_roles")) + earlyStopQueries := []struct { + name string + sql string + }{ + { + name: "limit union", + sql: ` + WITH c AS ( + SELECT l.l_comment, r.role_id + FROM lineitem l CROSS JOIN mo_current_roles() r + ) + (SELECT role_id FROM c LIMIT 1) + UNION ALL + (SELECT role_id FROM c LIMIT 1)`, + }, + { + name: "semi join", + sql: ` + WITH c AS ( + SELECT l.l_comment, r.role_id + FROM lineitem l CROSS JOIN mo_current_roles() r + ) + SELECT role_id FROM c a + WHERE EXISTS (SELECT 1 FROM c b WHERE a.role_id = b.role_id)`, + }, + } + for _, test := range earlyStopQueries { + t.Run(test.name, func(t *testing.T) { + amplifiedPlan, err := runOneStmt(NewMockOptimizer(false), t, test.sql) + require.NoError(t, err) + require.Zero(t, countReachableNodeType(amplifiedPlan.GetQuery(), planpb.Node_SINK), + "an early-terminating amplifying subtree must retain the guarded inline plan") + require.Equal(t, 2, countReachableTableFunction(amplifiedPlan.GetQuery(), "mo_current_roles")) + }) + } variableWidthPlan, err := runOneStmt(NewMockOptimizer(false), t, ` WITH c AS ( From 3d3fd4e8a54e9ed7ac568e836c6b2fe7ff25ec40 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sat, 29 Aug 2026 15:05:03 +0800 Subject: [PATCH 14/17] test(compile): allow later cumulative protocol versions --- pkg/sql/compile/remoterun_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sql/compile/remoterun_test.go b/pkg/sql/compile/remoterun_test.go index 9d1bdc2512407..a0b4e3ce00d11 100644 --- a/pkg/sql/compile/remoterun_test.go +++ b/pkg/sql/compile/remoterun_test.go @@ -967,7 +967,7 @@ func TestCrossDomainStringLiteralRemoteProtocolValidation(t *testing.T) { } func TestRemoteExpressionProtocolValidation(t *testing.T) { - require.Equal(t, defines.MORPCVersion36, defines.MORPCLatestVersion, + require.GreaterOrEqual(t, defines.MORPCLatestVersion, defines.MORPCVersion36, "a capability is unavailable after a full rollout unless latest advertises it") proc := testutil.NewProcess(t) From 954463b0f5b19602408a6cee256359c0df86ad34 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sun, 30 Aug 2026 22:28:16 +0800 Subject: [PATCH 15/17] docs: design information schema metadata visibility --- ..._INFORMATION_SCHEMA_METADATA_VISIBILITY.md | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md diff --git a/docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md b/docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md new file mode 100644 index 0000000000000..4b7b8bb474fe1 --- /dev/null +++ b/docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md @@ -0,0 +1,210 @@ +# Information Schema Metadata Visibility and Active-Role Closure + +- Status: Proposed — awaiting design approval +- Owning issue: [#27656](https://github.com/matrixorigin/matrixone/issues/27656) +- Implementation PR: [#27695](https://github.com/matrixorigin/matrixone/pull/27695) +- Version: 1 +- Last updated: 2026-08-30 + +## 1. Problem and evidence + +A non-admin tenant user can query `information_schema` views and discover database, table, column, index, constraint, partition, and view metadata for objects that the user's active authorization context cannot access. This differs from the authorization boundary enforced by normal object access and commands such as `SHOW DATABASES`. + +The first owner of the authorization decision is the tenant catalog together with the session's active role. An `information_schema` view is a consumer of that decision; it must not invent a weaker visibility rule. + +## 2. Scope and design triggers + +This design covers metadata visibility for `SCHEMATA`, `TABLES`, `COLUMNS`, `STATISTICS`, `TABLE_CONSTRAINTS`, `CHECK_CONSTRAINTS`, `KEY_COLUMN_USAGE`, `REFERENTIAL_CONSTRAINTS`, `VIEWS`, and `PARTITIONS`. + +A design review is mandatory because the implementation crosses frontend bootstrap, tenant upgrades, planner, table-function execution, catalog access, and sysview ownership boundaries. It changes an authorization boundary, rolling-upgrade behavior, statement-local materialization, and a metadata-query hot path. + +Non-goals: + +- changing the privilege grant/revoke model; +- changing role activation semantics; +- changing partition-table, charset/collation, UDF, stored-procedure, or warning compatibility; +- exposing `mo_current_roles()` as a new user-facing authorization mechanism outside this metadata implementation; +- making metadata visibility imply permission to read object data. + +## 3. Authorization invariant and trust boundary + +For tenant account `A`, active role `R`, and metadata object `O`: + +> A protected `information_schema` row is visible if and only if `O` is an intended system object, or the normal authorization context rooted at `R` has an ownership or metadata privilege path that makes `O` visible. + +The role set is the cycle-safe transitive closure of roles granted to the stable active role ID. Role names are not identities and renaming a role must not affect visibility. The closure is evaluated at statement execution time, so `SET ROLE`, prepared-statement reuse, and ordinary plan-cache reuse cannot retain a previous session role. + +A database is visible when one of these conditions holds: + +1. it is an intended system schema; +2. an active/inherited role owns it; +3. an active/inherited role has applicable account- or database-level metadata privilege; +4. it contains an object visible under the table/object rule. + +A table-like object is visible when one of these conditions holds: + +1. it is in an intended system schema; +2. an active/inherited role owns it; +3. its database is owned by an active/inherited role; +4. an applicable account-, database-, table-, or view-level privilege grants metadata visibility. + +Constraint, index, column, partition, and view rows derive visibility by joining to the visible table set. They may not independently enumerate hidden objects. + +The trust boundary is tenant-local: catalog queries execute under the current account and must not expose another tenant's objects. The explicitly retained system-catalog rows are the only cross-account exception already required by system-schema behavior. + +## 4. Role-closure mechanism + +### 4.1 Interface + +`mo_current_roles()` is a zero-argument table function returning one fixed-width `INT64 role_id` column. It emits the active role and every transitively inherited role exactly once. + +The function reads the active role from the execution-time process/session context. Planning must not replace it with a role name or a role ID captured when a prepared/cached plan was built. + +### 4.2 Traversal + +The implementation performs breadth-first frontier expansion: + +1. initialize `visited` and the frontier with the active role ID; +2. query `mo_catalog.mo_role_grant` for grants whose `grantee_id` is in the current frontier; +3. add unseen `granted_id` values to `visited` and the next frontier; +4. repeat until the frontier is empty. + +Frontier SQL is split into batches of at most 256 role IDs. The `grantee_id` catalog index bounds each query to the relevant graph instead of scanning every tenant grant. `visited` makes cycles finite and suppresses duplicates. + +Resource complexity is proportional to the reachable closure and reachable edges, not the tenant's disconnected role graph. Memory ownership is statement-local and released with the table-function operator. There is no background goroutine, global cache, retry loop, or cross-statement mutable state. + +### 4.3 Errors and cancellation + +Every internal executor result is closed by the function. Internal SQL errors and cancellation propagate to the caller; partial closure results are not published as a successful authorization set. Cancellation terminates further frontier expansion. Empty or malformed internal results fail rather than widening visibility. + +No retry is performed because replaying nested catalog work inside the same statement cannot repair an authorization or transaction error and would increase resource use. A failed metadata query may be retried by the normal statement owner. + +## 5. Statement-local sharing and materialization bound + +The canonical visibility CTE references the active-role set multiple times. Inlining every reference would execute the nested closure SQL three times for most protected views and six times for `SCHEMATA`. + +The planner therefore permits statement-local CTE sharing only for the exact bounded shape: + +- one childless, zero-argument `mo_current_roles()` function scan; +- optionally wrapped only by cardinality-preserving projections; +- exactly one fixed-width `INT64` output; +- no join, additional scan, filter, aggregate, limit/offset, or variable-width output. + +That exact producer is evaluated once and consumed through the normal query-scoped sink/source ownership path. Its maximum row count is the reachable role count and its row width is fixed. + +A CTE that merely contains `mo_current_roles()` does not qualify. In particular, joins with user/catalog tables and early-stop `LIMIT`, SEMI, or ANTI consumers retain the normal full-drain, profitability, 32 MiB estimate, and spill gates. This prevents a small authorization optimization from forcing eager materialization of an unrelated large subtree. + +Sink/source cleanup, cancellation wakeups, memory admission, and spill lifecycle remain owned by the existing CTE materialization machinery. This change adds no new lifecycle state. + +## 6. Bootstrap and rolling-upgrade contract + +`mo_current_roles()` and canonical protected views are a cumulative CN capability identified by `MORPCVersion41` in the implementation revision associated with this design. Version 41 follows capabilities v38-v40 already present on `main`; a service advertising v41 therefore includes those earlier contracts and this role-closure capability. + +The capability number is not an independently negotiable feature bit. Before merge, the implementation must merge latest `mo/main` and verify that v41 remains the next unique cumulative version. If another capability lands first, this document and every producer/consumer gate must be revised together to the next version. + +Gated consumers are: + +- planner admission for `mo_current_roles()`; +- tenant bootstrap selection of canonical versus compatibility view definitions; +- same-version tenant upgrade entries for the role-grant index and protected views; +- protocol tests and user-visible compatibility errors. + +Canonical views may be installed only when every participating CN reports at least the required version. Pre-capability bootstrap uses compatibility definitions that do not reference the unavailable function and fail closed relative to the new visibility boundary. + +### Upgrade + +1. deploy binaries that understand the capability while the common protocol remains below it; +2. retain compatibility definitions during the mixed-version phase; +3. once every CN advertises the capability, create the role-grant index and install canonical views; +4. new plans may then use `mo_current_roles()`. + +### Downgrade and rollback + +Do not lower the common protocol while canonical views referencing `mo_current_roles()` remain installed. Operational rollback must first restore compatibility definitions (or complete tenant rollback using the normal upgrade framework), then remove v41-only participants. Catalog data and role grants themselves are unchanged, so no role data migration or destructive rollback is required. + +A restarted old CN cannot safely join a cluster whose common protocol and canonical views require v41; normal protocol admission must reject or hold that mixed state rather than treating the old CN as capable. + +Backup/restore carries ordinary catalog/view definitions. Restoring into a pre-v41 binary set requires compatibility definitions to be selected before tenant queries are admitted. + +## 7. Security and denial-of-service analysis + +The design is fail-closed: protocol uncertainty, internal executor failure, malformed closure output, or cancellation fails the query rather than treating every role/object as visible. + +The closure cannot cross tenant boundaries because catalog access uses the current account context. Stable numeric role identity prevents rename-based visibility drift. Cycle detection prevents malicious or accidental cyclic grants from hanging traversal. + +Disconnected catalog grants do not contribute to query work. Reachable closure size is still proportional to legally reachable roles; batching limits SQL statement size but does not impose a semantic role-depth limit. Existing tenant role/grant governance is the capacity control for a deliberately enormous reachable closure. The operator owns all temporary memory, and cancellation remains available throughout frontier expansion. + +## 8. Alternatives + +### A. Embed recursive SQL in every view + +Rejected. Recursive CTE execution in the affected pipeline previously exposed hang/lifecycle risk, repeats a complex expression across views, and makes execution-time role and plan-cache behavior harder to control. + +### B. Scan all role grants and compute closure in Go + +Rejected. It costs `O(all tenant grants)` CPU and memory even when the active role has no inherited grants. Concurrent metadata discovery can amplify disconnected catalog size into latency and OOM. + +### C. Maintain a global/session role-closure cache + +Rejected for this change. Correct invalidation must cover grants, revokes, active-role changes, transaction visibility, tenant isolation, and restart. It introduces long-lived state and stale-authorization risk. The indexed frontier design keeps ownership statement-local and scales with relevant data. + +### D. Duplicate the frontier closure at every CTE reference + +Correct but rejected for performance. Typical views would execute three to six complete nested closures per statement. + +### E. Materialize every CTE containing `mo_current_roles()` + +Rejected. An arbitrary surrounding join/scan can be unbounded and may have early-stop consumers. Only the exact fixed-width closure shape is admitted specially. + +## 9. Validation map and acceptance criteria + +| Contract | Evidence | +|---|---| +| Active plus complete inherited closure, cycles, duplicates | table-function unit tests | +| Work proportional to reachable graph, disconnected 100k-edge case | focused benchmark/stress test | +| Internal results close on success/error; cancellation propagates | table-function executor tests and ownership review | +| Runtime active role under prepared and ordinary cache reuse | public SQL BVT and planner/compile tests | +| One closure producer per protected metadata query | reachable plan-shape tests for all protected views | +| Amplifying JOIN plus LIMIT/SEMI remains inlined | negative real-planner tests | +| No privilege/direct/inherited/database ownership/admin boundaries | public SQL BVT matrix | +| Sibling views and `SCHEMATA` cannot bypass visibility | public SQL BVT matrix and sysview definition tests | +| Pre-v41 definitions do not reference the function | bootstrap/sysview compatibility tests | +| Canonical upgrades wait for common v41 and are idempotent | v4.0.6 upgrade tests | +| Unique cumulative allocation after latest main | merge-time `MORPCVersion` audit and exact-head CI | + +Acceptance requires all focused owning-package tests, affected BVT, race tests on role/protocol/plan-cache paths, `go vet` for affected packages, and `git diff --check` to pass. Structural one-scan tests prove multiplicity; a real metadata-query latency/allocation benchmark remains desirable performance coverage but is not an authorization-correctness oracle. + +## 10. Risks, rollout, and observability + +Primary risks are privilege overexposure, hidden authorized objects, stale active-role capture, protocol misallocation, repeated nested SQL, and unbounded closure work. The gates and validation above address each risk independently. + +No new metric or background health signal is introduced. Failures surface as normal query/bootstrap errors with the required protocol version. Operators can diagnose rollout state through existing common-protocol reporting and tenant-upgrade logs. + +Rollout is contained by the common-protocol gate: compatibility definitions remain active until all CNs are capable. If scale evidence regresses, rollback restores compatibility definitions before withdrawing capable binaries. + +## 11. Decision log + +- Numeric active role ID is the authorization root; role names are display metadata. +- Complete transitive inheritance is required; limiting traversal depth is not acceptable. +- Indexed frontier expansion is preferred over global scans or a mutable cache. +- Closure evaluation is shared once per statement only for the exact bounded function/projection shape. +- Canonical view installation and planner admission use the same cumulative protocol capability. +- System schemas remain intentionally visible; tenant-private metadata follows ownership and privilege paths. + +## 12. Open decisions + +No known blocking design question remains. The protocol number must be revalidated immediately before merge because concurrent PRs allocate from the same cumulative sequence. + +## 13. Design review record + +To be completed by an authorized reviewer: + +```text +Change scope: information_schema metadata authorization and active-role closure +Trigger: authorization boundary; protocol/upgrade contract; cross-package lifecycle and hot-path change +Design: docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md, version 1, +Blocking findings: +Decision log: +Decision: PASS | REQUEST_CHANGES +Implementation deviations: +``` From 6840dac41aa3c968e036174774a5b5c8b15c3e53 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Mon, 31 Aug 2026 17:47:52 +0800 Subject: [PATCH 16/17] docs: bound current-role closure workspace --- ..._INFORMATION_SCHEMA_METADATA_VISIBILITY.md | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md b/docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md index 4b7b8bb474fe1..1d1390ef56902 100644 --- a/docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md +++ b/docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md @@ -3,8 +3,8 @@ - Status: Proposed — awaiting design approval - Owning issue: [#27656](https://github.com/matrixorigin/matrixone/issues/27656) - Implementation PR: [#27695](https://github.com/matrixorigin/matrixone/pull/27695) -- Version: 1 -- Last updated: 2026-08-30 +- Version: 2 +- Last updated: 2026-08-31 ## 1. Problem and evidence @@ -73,9 +73,13 @@ Frontier SQL is split into batches of at most 256 role IDs. The `grantee_id` cat Resource complexity is proportional to the reachable closure and reachable edges, not the tenant's disconnected role graph. Memory ownership is statement-local and released with the table-function operator. There is no background goroutine, global cache, retry loop, or cross-statement mutable state. +The closure has an explicit fail-closed admission limit of 4,096 distinct roles, including the active role. The limit is checked before a newly discovered role is published to `visited` or a frontier. A statement that would admit role 4,097 returns an error and emits no partial authorization batch. This caps retained Go workspace for `visited`, frontier/next slices, and final role slices. Admission conservatively budgets 128 bytes per role, so closure workspace is bounded to 512 KiB per metadata statement before the output vector. The fixed-width output vector adds at most 32 KiB (`4,096 * 8` bytes) and is charged through the query's existing process mpool. The conservative 128-byte workspace estimate covers an `int64` map key, map bucket/load overhead, and simultaneous frontier/final slice storage without relying on runtime-specific minimum object sizes. + +The role count and byte budget describe one query-owned closure generation. Concurrent metadata queries each have their own generation and cannot share or retain another statement's workspace; aggregate closure workspace is therefore bounded by 512 KiB times already-admitted query concurrency, rather than tenant graph size times concurrency. This design does not add a second global concurrency controller. + ### 4.3 Errors and cancellation -Every internal executor result is closed by the function. Internal SQL errors and cancellation propagate to the caller; partial closure results are not published as a successful authorization set. Cancellation terminates further frontier expansion. Empty or malformed internal results fail rather than widening visibility. +Every internal executor result is closed by the function. Internal SQL errors and cancellation propagate to the caller; partial closure results are not published as a successful authorization set. Cancellation is checked before each frontier query and while admitting returned roles, and terminates further expansion. Empty or malformed internal results fail rather than widening visibility. Capacity rejection follows the same terminal path: the current internal result is closed, temporary workspace becomes unreachable on return, and the table-function batch remains empty. No retry is performed because replaying nested catalog work inside the same statement cannot repair an authorization or transaction error and would increase resource use. A failed metadata query may be retried by the normal statement owner. @@ -132,7 +136,7 @@ The design is fail-closed: protocol uncertainty, internal executor failure, malf The closure cannot cross tenant boundaries because catalog access uses the current account context. Stable numeric role identity prevents rename-based visibility drift. Cycle detection prevents malicious or accidental cyclic grants from hanging traversal. -Disconnected catalog grants do not contribute to query work. Reachable closure size is still proportional to legally reachable roles; batching limits SQL statement size but does not impose a semantic role-depth limit. Existing tenant role/grant governance is the capacity control for a deliberately enormous reachable closure. The operator owns all temporary memory, and cancellation remains available throughout frontier expansion. +Disconnected catalog grants do not contribute to query work. Reachable closure size is proportional to legally reachable roles only until the 4,096-role / 512-KiB admission boundary. A larger closure fails closed before publishing any role set. Batching independently limits SQL statement size and does not weaken the total closure bound. The operator owns all temporary memory, and cancellation remains available throughout frontier expansion. Concurrent queries cannot multiply work by the tenant's full disconnected graph; each query is independently capped at the same closure budget. ## 8. Alternatives @@ -162,7 +166,10 @@ Rejected. An arbitrary surrounding join/scan can be unbounded and may have early |---|---| | Active plus complete inherited closure, cycles, duplicates | table-function unit tests | | Work proportional to reachable graph, disconnected 100k-edge case | focused benchmark/stress test | -| Internal results close on success/error; cancellation propagates | table-function executor tests and ownership review | +| Role 4,096 succeeds; role 4,097 fails before publication | deterministic table-function boundary tests | +| Cancellation before/between frontier queries publishes no partial batch | injected cancellation tests | +| Concurrent closures have independent 512-KiB admission generations and all reject over-limit graphs | barrier-controlled concurrency test | +| Internal results close on success/error/capacity rejection; cancellation propagates | table-function executor tests and ownership review | | Runtime active role under prepared and ordinary cache reuse | public SQL BVT and planner/compile tests | | One closure producer per protected metadata query | reachable plan-shape tests for all protected views | | Amplifying JOIN plus LIMIT/SEMI remains inlined | negative real-planner tests | @@ -176,7 +183,7 @@ Acceptance requires all focused owning-package tests, affected BVT, race tests o ## 10. Risks, rollout, and observability -Primary risks are privilege overexposure, hidden authorized objects, stale active-role capture, protocol misallocation, repeated nested SQL, and unbounded closure work. The gates and validation above address each risk independently. +Primary risks are privilege overexposure, hidden authorized objects, stale active-role capture, protocol misallocation, repeated nested SQL, and excessive closure work. The 4,096-role / 512-KiB query-owned admission boundary makes closure memory finite and fails closed; the remaining gates and validation above address each risk independently. No new metric or background health signal is introduced. Failures surface as normal query/bootstrap errors with the required protocol version. Operators can diagnose rollout state through existing common-protocol reporting and tenant-upgrade logs. @@ -185,7 +192,8 @@ Rollout is contained by the common-protocol gate: compatibility definitions rema ## 11. Decision log - Numeric active role ID is the authorization root; role names are display metadata. -- Complete transitive inheritance is required; limiting traversal depth is not acceptable. +- Complete transitive inheritance is required within the admitted 4,096-role closure; an oversized closure fails the entire metadata query rather than truncating authorization. +- Closure workspace admission is capped at 4,096 roles and a conservative 512 KiB per statement; output-vector memory remains process-mpool-accounted. - Indexed frontier expansion is preferred over global scans or a mutable cache. - Closure evaluation is shared once per statement only for the exact bounded function/projection shape. - Canonical view installation and planner admission use the same cumulative protocol capability. @@ -202,7 +210,7 @@ To be completed by an authorized reviewer: ```text Change scope: information_schema metadata authorization and active-role closure Trigger: authorization boundary; protocol/upgrade contract; cross-package lifecycle and hot-path change -Design: docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md, version 1, +Design: docs/design/CLAUDE_INFORMATION_SCHEMA_METADATA_VISIBILITY.md, version 2, Blocking findings: Decision log: Decision: PASS | REQUEST_CHANGES From deb0c761fad3cb112fce24ca3389398d486e4e4c Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Mon, 31 Aug 2026 22:32:46 +0800 Subject: [PATCH 17/17] fix: bound current role closure workspace --- .../colexec/table_function/current_roles.go | 52 +++++- .../table_function/current_roles_test.go | 157 ++++++++++++++++-- 2 files changed, 190 insertions(+), 19 deletions(-) diff --git a/pkg/sql/colexec/table_function/current_roles.go b/pkg/sql/colexec/table_function/current_roles.go index 96119056e3339..6c4abddd3b1dc 100644 --- a/pkg/sql/colexec/table_function/current_roles.go +++ b/pkg/sql/colexec/table_function/current_roles.go @@ -19,6 +19,7 @@ import ( "strconv" "strings" + "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/defines" "github.com/matrixorigin/matrixone/pkg/util/executor" @@ -29,11 +30,12 @@ import ( const ( currentRoleGrantQueryPrefix = "SELECT cast(granted_id AS bigint) FROM mo_catalog.mo_role_grant WHERE grantee_id IN (" currentRoleGrantFrontierBatchSize = 256 + currentRoleClosureMaxRoles = 4096 ) type currentRoleSQLRunner func(*process.Process, string) (executor.Result, error) -type currentRoleFrontierExpander func(*process.Process, []int64, func(int64)) error +type currentRoleFrontierExpander func(*process.Process, []int64, func(int64) error) error type currentRolesState struct { simpleOneBatchState @@ -62,35 +64,55 @@ func runCurrentRolesSQL(proc *process.Process, sql string) (executor.Result, err return sqlexec.RunSql(sqlexec.NewSqlProcess(proc), sql) } -func visitCurrentRoleGrants(result executor.Result, visit func(int64)) { +func visitCurrentRoleGrants(result executor.Result, visit func(int64) error) error { + var visitErr error result.ReadRows(func(rows int, cols []*vector.Vector) bool { grantedIDs := vector.MustFixedColWithTypeCheck[int64](cols[0]) for i := 0; i < rows; i++ { - visit(grantedIDs[i]) + if visitErr = visit(grantedIDs[i]); visitErr != nil { + return false + } } return true }) + return visitErr +} + +func checkCurrentRoleClosureCanceled(proc *process.Process) error { + if proc == nil || proc.Ctx == nil { + return nil + } + return proc.Ctx.Err() } func expandCurrentRoleFrontierWithRunner( proc *process.Process, frontier []int64, - visit func(int64), + visit func(int64) error, run currentRoleSQLRunner, ) error { for start := 0; start < len(frontier); start += currentRoleGrantFrontierBatchSize { + if err := checkCurrentRoleClosureCanceled(proc); err != nil { + return err + } end := min(start+currentRoleGrantFrontierBatchSize, len(frontier)) result, err := run(proc, buildCurrentRoleGrantQuery(frontier[start:end])) if err != nil { + result.Close() return err } - visitCurrentRoleGrants(result, visit) - result.Close() + visitErr := func() error { + defer result.Close() + return visitCurrentRoleGrants(result, visit) + }() + if visitErr != nil { + return visitErr + } } return nil } -func expandCurrentRoleFrontier(proc *process.Process, frontier []int64, visit func(int64)) error { +func expandCurrentRoleFrontier(proc *process.Process, frontier []int64, visit func(int64) error) error { return expandCurrentRoleFrontierWithRunner(proc, frontier, visit, runCurrentRolesSQL) } @@ -102,13 +124,25 @@ func currentRoleClosure( visited := map[int64]struct{}{root: {}} frontier := []int64{root} for len(frontier) > 0 { + if err := checkCurrentRoleClosureCanceled(proc); err != nil { + return nil, err + } next := make([]int64, 0) - if err := expand(proc, frontier, func(grantedID int64) { + if err := expand(proc, frontier, func(grantedID int64) error { + if err := checkCurrentRoleClosureCanceled(proc); err != nil { + return err + } if _, ok := visited[grantedID]; ok { - return + return nil + } + if len(visited) >= currentRoleClosureMaxRoles { + return moerr.NewInvalidInputNoCtxf( + "current role closure exceeds the %d-role query limit", + currentRoleClosureMaxRoles) } visited[grantedID] = struct{}{} next = append(next, grantedID) + return nil }); err != nil { return nil, err } diff --git a/pkg/sql/colexec/table_function/current_roles_test.go b/pkg/sql/colexec/table_function/current_roles_test.go index 00364e01f79bb..694211620f6aa 100644 --- a/pkg/sql/colexec/table_function/current_roles_test.go +++ b/pkg/sql/colexec/table_function/current_roles_test.go @@ -15,8 +15,10 @@ package table_function import ( + "context" "errors" "strings" + "sync" "testing" "github.com/matrixorigin/matrixone/pkg/common/mpool" @@ -34,13 +36,15 @@ import ( ) func roleGraphExpander(graph map[int64][]int64, expanded *[]int64) currentRoleFrontierExpander { - return func(_ *process.Process, frontier []int64, visit func(int64)) error { + return func(_ *process.Process, frontier []int64, visit func(int64) error) error { for _, roleID := range frontier { if expanded != nil { *expanded = append(*expanded, roleID) } for _, grantedID := range graph[roleID] { - visit(grantedID) + if err := visit(grantedID); err != nil { + return err + } } } return nil @@ -69,6 +73,131 @@ func TestCurrentRoleClosure(t *testing.T) { require.Equal(t, []int64{77}, roles) } +func TestCurrentRoleClosureAdmissionBoundary(t *testing.T) { + chainExpander := func(limit int64) currentRoleFrontierExpander { + return func(_ *process.Process, frontier []int64, visit func(int64) error) error { + for _, roleID := range frontier { + if roleID+1 < limit { + if err := visit(roleID + 1); err != nil { + return err + } + } + } + return nil + } + } + + roles, err := currentRoleClosure(nil, 0, chainExpander(currentRoleClosureMaxRoles)) + require.NoError(t, err) + require.Len(t, roles, currentRoleClosureMaxRoles) + + roles, err = currentRoleClosure(nil, 0, chainExpander(currentRoleClosureMaxRoles+1)) + require.ErrorContains(t, err, "current role closure exceeds the 4096-role query limit") + require.Nil(t, roles, "an over-limit closure must not publish a partial role set") +} + +func TestCurrentRoleClosureCancellation(t *testing.T) { + proc := testutil.NewProc(t) + ctx, cancel := context.WithCancel(proc.Ctx) + proc.Ctx = ctx + cancel() + queries := 0 + expand := func(proc *process.Process, frontier []int64, visit func(int64) error) error { + queries++ + return expandCurrentRoleFrontierWithRunner(proc, frontier, visit, + func(*process.Process, string) (executor.Result, error) { + t.Fatal("cancellation must be checked before internal SQL") + return executor.Result{}, nil + }) + } + roles, err := currentRoleClosure(proc, 10, expand) + require.ErrorIs(t, err, context.Canceled) + require.Nil(t, roles) + require.Zero(t, queries) + + proc = testutil.NewProc(t) + ctx, cancel = context.WithCancel(proc.Ctx) + proc.Ctx = ctx + calls := 0 + expand = func(proc *process.Process, _ []int64, visit func(int64) error) error { + calls++ + if calls == 1 { + require.NoError(t, visit(20)) + cancel() + return nil + } + t.Fatal("cancellation between frontiers must prevent another expansion") + return nil + } + roles, err = currentRoleClosure(proc, 10, expand) + require.ErrorIs(t, err, context.Canceled) + require.Nil(t, roles) + require.Equal(t, 1, calls) +} + +func TestCurrentRoleClosureCapacityRejectionClosesResult(t *testing.T) { + mp := mpool.MustNewZero() + defer mpool.DeleteMPool(mp) + run := func(_ *process.Process, _ string) (executor.Result, error) { + bat := batch.NewWithSize(1) + bat.Vecs[0] = vector.NewVec(types.T_int64.ToType()) + for roleID := int64(1); roleID <= currentRoleClosureMaxRoles; roleID++ { + require.NoError(t, vector.AppendFixed(bat.Vecs[0], roleID, false, mp)) + } + bat.SetRowCount(currentRoleClosureMaxRoles) + return executor.Result{Mp: mp, Batches: []*batch.Batch{bat}}, nil + } + expand := func(proc *process.Process, frontier []int64, visit func(int64) error) error { + return expandCurrentRoleFrontierWithRunner(proc, frontier, visit, run) + } + + roles, err := currentRoleClosure(nil, 0, expand) + require.ErrorContains(t, err, "4096-role query limit") + require.Nil(t, roles) + require.Zero(t, mp.CurrNB(), "capacity rejection must close the current internal executor result") +} + +func TestCurrentRoleClosureConcurrentAdmissionGenerations(t *testing.T) { + const workers = 8 + start := make(chan struct{}) + var ready sync.WaitGroup + ready.Add(workers) + var done sync.WaitGroup + done.Add(workers) + errs := make(chan error, workers) + + for worker := 0; worker < workers; worker++ { + go func() { + defer done.Done() + ready.Done() + <-start + expand := func(_ *process.Process, frontier []int64, visit func(int64) error) error { + for _, roleID := range frontier { + if roleID < currentRoleClosureMaxRoles { + if err := visit(roleID + 1); err != nil { + return err + } + } + } + return nil + } + roles, err := currentRoleClosure(nil, 0, expand) + if roles != nil && err != nil { + errs <- errors.New("over-limit closure published partial roles") + return + } + errs <- err + }() + } + ready.Wait() + close(start) + done.Wait() + close(errs) + for err := range errs { + require.ErrorContains(t, err, "4096-role query limit") + } +} + func TestCurrentRoleClosureDoesNotVisitLargeDisconnectedGraph(t *testing.T) { graph := make(map[int64]int64, 100_002) graph[10] = 20 @@ -78,11 +207,13 @@ func TestCurrentRoleClosureDoesNotVisitLargeDisconnectedGraph(t *testing.T) { } lookups := 0 - expand := func(_ *process.Process, frontier []int64, visit func(int64)) error { + expand := func(_ *process.Process, frontier []int64, visit func(int64) error) error { for _, roleID := range frontier { lookups++ if grantedID, ok := graph[roleID]; ok { - visit(grantedID) + if err := visit(grantedID); err != nil { + return err + } } } return nil @@ -108,10 +239,12 @@ func BenchmarkCurrentRoleClosureLargeDisconnectedGraph(b *testing.B) { for i := int64(0); i < 100_000; i++ { graph[1_000_000+i] = 2_000_000 + i } - expand := func(_ *process.Process, frontier []int64, visit func(int64)) error { + expand := func(_ *process.Process, frontier []int64, visit func(int64) error) error { for _, roleID := range frontier { if grantedID, ok := graph[roleID]; ok { - visit(grantedID) + if err := visit(grantedID); err != nil { + return err + } } } return nil @@ -150,7 +283,7 @@ func BenchmarkCurrentRoleClosureInternalSQLBoundary(b *testing.B) { } return executor.Result{Mp: mp, Batches: []*batch.Batch{bat}}, nil } - expand := func(proc *process.Process, frontier []int64, visit func(int64)) error { + expand := func(proc *process.Process, frontier []int64, visit func(int64) error) error { return expandCurrentRoleFrontierWithRunner(proc, frontier, visit, run) } @@ -186,7 +319,10 @@ func TestVisitCurrentRoleGrants(t *testing.T) { defer result.Close() var roles []int64 - visitCurrentRoleGrants(result, func(roleID int64) { roles = append(roles, roleID) }) + require.NoError(t, visitCurrentRoleGrants(result, func(roleID int64) error { + roles = append(roles, roleID) + return nil + })) require.Equal(t, []int64{20, 30}, roles) } @@ -201,7 +337,7 @@ func TestExpandCurrentRoleFrontierChunksQueries(t *testing.T) { return executor.Result{}, nil } - require.NoError(t, expandCurrentRoleFrontierWithRunner(nil, frontier, func(int64) {}, run)) + require.NoError(t, expandCurrentRoleFrontierWithRunner(nil, frontier, func(int64) error { return nil }, run)) require.Len(t, queries, 2) require.True(t, strings.HasSuffix(queries[0], ",255,256)")) require.Equal(t, currentRoleGrantQueryPrefix+"257,258)", queries[1]) @@ -244,8 +380,9 @@ func TestCurrentRolesState(t *testing.T) { require.Zero(t, state.batch.RowCount()) expected := errors.New("role grant read failed") - state.expandFrontier = func(*process.Process, []int64, func(int64)) error { return expected } + state.expandFrontier = func(*process.Process, []int64, func(int64) error) error { return expected } require.ErrorIs(t, state.start(tf, proc, 0, nil), expected) + require.Zero(t, state.batch.RowCount(), "a failed closure must not publish a partial batch") tf.Free(proc, false, nil) }) }