Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 86 additions & 54 deletions fe/fe-core/src/main/java/com/starrocks/qe/ShowExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -850,56 +850,18 @@ private ShowResultSet showCreateInternalCatalogTable(ShowCreateTableStmt showStm
List<List<String>> rows = Lists.newArrayList();
TableName tableName = new TableName(showStmt.getCatalogName(), showStmt.getDb(), showStmt.getTable());
// Lookup is ConcurrentHashMap-backed (Database.nameToTable) for internal catalog and
// throws SemanticException if the table is missing, so it is safe outside the lock.
Table table = MetaUtils.getSessionAwareTable(connectContext, db, tableName);
// TODO(sync-mv): the (table == null) branch below is currently unreachable -
// MetaUtils.getSessionAwareTable throws SemanticException on miss rather than
// returning null (since #43162, "temporary table part-1"), so the sync-MV
// fallback never runs. As a side effect, SHOW CREATE MATERIALIZED VIEW <sync_mv>
// has been broken since that change (sync MVs live as MaterializedIndexMeta
// inside an OLAP table, not as separately-registered Tables, so they are missed
// by the throwing lookup above). The fallback is kept here as a marker until a
// follow-up commit reworks the lookup to make this path reachable. When that
// happens, the iteration must run under DB READ (or per-table READ) - it reads
// HashMap-backed OlapTable index metadata which is mutated by concurrent
// ALTER/rollup. Today the unlocked iteration is harmless because it never
// executes.
if (table == null) {
// throws SemanticException if the name does not match a registered Table.
Table table;
try {
table = MetaUtils.getSessionAwareTable(connectContext, db, tableName);
} catch (SemanticException e) {
// Sync MVs are mv indexes inside an OLAP table and are not registered as Tables,
// so the lookup misses. Fall through to a DB-wide scan for the MV-typed query;
// for any other type, the miss is a real "table not found".
if (showStmt.getType() != ShowCreateTableStmt.CreateTableType.MATERIALIZED_VIEW) {
ErrorReport.reportSemanticException(ErrorCode.ERR_BAD_TABLE_ERROR, showStmt.getTable());
} else {
// For Sync Materialized View, it is a mv index inside OLAP table,
// so we can not get it from database.
for (Table tbl : GlobalStateMgr.getCurrentState().getLocalMetastore().getTables(db.getId())) {
if (tbl.getType() == Table.TableType.OLAP) {
OlapTable olapTable = (OlapTable) tbl;
List<MaterializedIndexMeta> visibleMaterializedViews =
olapTable.getVisibleIndexMetas();
for (MaterializedIndexMeta mvMeta : visibleMaterializedViews) {
if (olapTable.getIndexNameByMetaId(mvMeta.getIndexMetaId()).equals(showStmt.getTable())) {
if (mvMeta.getOriginStmt() == null) {
String mvName = olapTable.getIndexNameByMetaId(mvMeta.getIndexMetaId());
rows.add(Lists.newArrayList(showStmt.getTable(),
ShowMaterializedViewStatus.buildCreateMVSql(olapTable,
mvName, mvMeta), "utf8", "utf8_general_ci"));
} else {
rows.add(Lists.newArrayList(showStmt.getTable(), mvMeta.getOriginStmt(),
"utf8", "utf8_general_ci"));
}

ShowResultSetMetaData showResultSetMetaData = ShowResultSetMetaData.builder()
.addColumn(new Column("Materialized View",
TypeFactory.createVarcharType(20)))
.addColumn(new Column("Create Materialized View",
TypeFactory.createVarcharType(30)))
.build();
return new ShowResultSet(showResultSetMetaData, rows);
}
}
}
}
ErrorReport.reportSemanticException(ErrorCode.ERR_BAD_TABLE_ERROR, showStmt.getTable());
throw e;
}
return findSyncMaterializedViewCreateStmt(connectContext, db, showStmt);
}
Locker locker = new Locker();
locker.lockTableWithIntensiveDbLock(db.getId(), table.getId(), LockType.READ);
Expand Down Expand Up @@ -948,12 +910,7 @@ private ShowResultSet showCreateInternalCatalogTable(ShowCreateTableStmt showStm
.build();
return new ShowResultSet(showViewResultSetMeta, rows);
} else {
rows.add(Lists.newArrayList(table.getName(), createTableStmt.get(0)));
ShowResultSetMetaData showResultSetMetaData = ShowResultSetMetaData.builder()
.addColumn(new Column("Materialized View", TypeFactory.createVarcharType(20)))
.addColumn(new Column("Create Materialized View", TypeFactory.createVarcharType(30)))
.build();
return new ShowResultSet(showResultSetMetaData, rows);
return buildShowCreateMaterializedViewResult(table.getName(), createTableStmt.get(0));
}
} else {
if (showStmt.getType() != ShowCreateTableStmt.CreateTableType.TABLE) {
Expand All @@ -968,6 +925,81 @@ private ShowResultSet showCreateInternalCatalogTable(ShowCreateTableStmt showStm
}
}

// Shared SHOW CREATE MATERIALIZED VIEW schema for the async and sync MV paths.
private static ShowResultSet buildShowCreateMaterializedViewResult(String mvName, String createSql) {
List<List<String>> rows = Lists.newArrayList();
rows.add(Lists.newArrayList(mvName, createSql));
ShowResultSetMetaData meta = ShowResultSetMetaData.builder()
.addColumn(new Column("Materialized View", TypeFactory.createVarcharType(20)))
.addColumn(new Column("Create Materialized View", TypeFactory.createVarcharType(30)))
.build();
return new ShowResultSet(meta, rows);
}

// Sync MVs live as MaterializedIndexMetas inside an OLAP table, not as
// separate Tables, so a name lookup misses and we scan. The visitor's
// pre-execution auth check resolves the sync MV name to a null BasicTable
// and silently no-ops, so we re-check privileges on the owning OLAP table
// once a match is found; the deny error names the sync MV (not the base
// table) to avoid revealing which table hosts the index.
private ShowResultSet findSyncMaterializedViewCreateStmt(ConnectContext connectContext,
Database db,
ShowCreateTableStmt showStmt) {
// ConcurrentHashMap-backed snapshot: weakly consistent, not a point-in-time
// atomic snapshot. Acceptable here - this is a best-effort fallback.
List<Table> tablesSnapshot = GlobalStateMgr.getCurrentState().getLocalMetastore().getTables(db.getId());
Locker locker = new Locker();
for (Table tbl : tablesSnapshot) {
Comment thread
kevincai marked this conversation as resolved.
// Include CLOUD_NATIVE (shared-data) base tables - sync MVs exist
// there too, see LakeSyncMaterializedViewTest.
if (!tbl.isOlapOrCloudNativeTable()) {
continue;
}
OlapTable olapTable = (OlapTable) tbl;
Comment thread
kevincai marked this conversation as resolved.
locker.lockTableWithIntensiveDbLock(db.getId(), olapTable.getId(), LockType.READ);
try {
Long metaId = olapTable.getIndexMetaIdByName(showStmt.getTable());
// Skip the base index (its name equals the table's current name) - only
// reachable here if a concurrent RENAME slipped between the failed name
// lookup that routed us into this fallback and the snapshot below.
if (metaId == null || metaId == olapTable.getBaseIndexMetaId()) {
continue;
}
// Skip shadow / mid-schema-change indexes.
boolean visible = olapTable.getVisibleIndexMetas().stream()
.anyMatch(m -> m.getIndexMetaId() == metaId);
if (!visible) {
continue;
Comment thread
kevincai marked this conversation as resolved.
}
MaterializedIndexMeta mvMeta = olapTable.getIndexMetaByMetaId(metaId);
if (mvMeta == null) {
continue;
}
TableName baseTableName = new TableName(InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME,
db.getFullName(), olapTable.getName());
try {
Authorizer.checkAnyActionOnTable(connectContext, baseTableName);
} catch (AccessDeniedException denied) {
AccessDeniedException.reportAccessDenied(
InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME,
connectContext.getCurrentUserIdentity(),
connectContext.getCurrentRoleIds(),
PrivilegeType.ANY.name(),
ObjectType.TABLE.name(),
showStmt.getTable());
}
String createSql = mvMeta.getOriginStmt() == null
? ShowMaterializedViewStatus.buildCreateMVSql(olapTable, showStmt.getTable(), mvMeta)
: mvMeta.getOriginStmt();
return buildShowCreateMaterializedViewResult(showStmt.getTable(), createSql);
} finally {
locker.unLockTableWithIntensiveDbLock(db.getId(), olapTable.getId(), LockType.READ);
}
}
ErrorReport.reportSemanticException(ErrorCode.ERR_BAD_TABLE_ERROR, showStmt.getTable());
return null; // unreachable; reportSemanticException always throws
}

private ShowResultSet showCreateExternalCatalogTable(ConnectContext context, ShowCreateTableStmt showStmt,
String catalogName) {
TableRef tableRef = showStmt.getTableRef();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,83 @@ public void testShowSyncMV() throws Exception {
Assertions.assertEquals(connectContext.getState().getStateType(), QueryState.MysqlStateType.EOF);
}

// Regression test: sync MVs are mv indexes inside an OLAP table, not registered as Tables.
// SHOW CREATE MATERIALIZED VIEW <sync_mv_name> must locate the index by name and return its
// CREATE statement instead of failing with "Table not found".
@Test
public void testShowCreateRollupSyncMV() throws Exception {
starRocksAssert.withDatabase("test").useDatabase("test")
.withTable("CREATE TABLE test.tbl_rollup_sync_mv\n" +
"(\n" +
" k1 date,\n" +
" k2 int,\n" +
" v1 int sum\n" +
")\n" +
"DISTRIBUTED BY HASH(k2) BUCKETS 3\n" +
"PROPERTIES('replication_num' = '1');")
.withMaterializedView(
"create materialized view rollup_sync_mv_to_check as " +
"select k2, sum(v1) as total from tbl_rollup_sync_mv group by k2;");
String showSql = "show create materialized view rollup_sync_mv_to_check;";
StatementBase statement = SqlParser.parseSingleStatement(showSql, connectContext.getSessionVariable().getSqlMode());
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, statement);
stmtExecutor.execute();
Assertions.assertEquals(QueryState.MysqlStateType.EOF, connectContext.getState().getStateType());
}

// Fallback scan exhaustion: a sync-MV-shaped lookup with a name that matches
// nothing in any base table runs through the full snapshot and falls through
// to ErrorReport.reportSemanticException(ERR_BAD_TABLE_ERROR, ...).
@Test
public void testShowCreateSyncMVNotFound() throws Exception {
starRocksAssert.withDatabase("test").useDatabase("test")
.withTable("CREATE TABLE test.tbl_sync_mv_not_found\n" +
"(\n" +
" k1 date,\n" +
" k2 int,\n" +
" v1 int sum\n" +
")\n" +
"DISTRIBUTED BY HASH(k2) BUCKETS 3\n" +
"PROPERTIES('replication_num' = '1');");
String showSql = "show create materialized view does_not_exist_anywhere;";
StatementBase statement = SqlParser.parseSingleStatement(showSql, connectContext.getSessionVariable().getSqlMode());
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, statement);
stmtExecutor.execute();
Assertions.assertEquals(QueryState.MysqlStateType.ERR, connectContext.getState().getStateType());
}

// Authorization deny path: once the fallback locates the owning base table and
// calls Authorizer.checkAnyActionOnTable, mock the check to throw and verify
// the deny error fires (reportAccessDenied -> ERR state).
@Test
public void testShowCreateSyncMVAccessDenied() throws Exception {
starRocksAssert.withDatabase("test").useDatabase("test")
.withTable("CREATE TABLE test.tbl_sync_mv_auth\n" +
"(\n" +
" k1 date,\n" +
" k2 int,\n" +
" v1 int sum\n" +
")\n" +
"DISTRIBUTED BY HASH(k2) BUCKETS 3\n" +
"PROPERTIES('replication_num' = '1');")
.withMaterializedView(
"create materialized view sync_mv_auth_deny as " +
"select k2, sum(v1) as total from tbl_sync_mv_auth group by k2;");
new mockit.MockUp<com.starrocks.sql.analyzer.Authorizer>() {
@mockit.Mock
public void checkAnyActionOnTable(ConnectContext context,
com.starrocks.catalog.TableName tableName)
throws com.starrocks.authorization.AccessDeniedException {
throw new com.starrocks.authorization.AccessDeniedException();
}
};
String showSql = "show create materialized view sync_mv_auth_deny;";
StatementBase statement = SqlParser.parseSingleStatement(showSql, connectContext.getSessionVariable().getSqlMode());
StmtExecutor stmtExecutor = new StmtExecutor(connectContext, statement);
stmtExecutor.execute();
Assertions.assertEquals(QueryState.MysqlStateType.ERR, connectContext.getState().getStateType());
}

@Test
public void testAlterMVWithIndex() throws Exception {
starRocksAssert.withDatabase("test").useDatabase("test")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- name: test_show_create_sync_materialized_view @native
create database test_show_create_sync_mv_${uuid0};
-- result:
-- !result
use test_show_create_sync_mv_${uuid0};
-- result:
-- !result
create table sync_mv_base
(
k1 date,
k2 int,
v1 int sum
)
distributed by hash(k2) buckets 3
properties('replication_num' = '1');
-- result:
-- !result
create materialized view sync_mv_rollup as select k2, sum(v1) from sync_mv_base group by k2;
-- result:
-- !result
function: wait_materialized_view_finish()
-- result:
None
-- !result
show create materialized view sync_mv_rollup;
-- result:
[REGEX]sync_mv_rollup\tcreate materialized view sync_mv_rollup as select.*from sync_mv_base group by.*
-- !result
show create table sync_mv_rollup;
-- result:
[REGEX].*Table.*sync_mv_rollup.*is not found.*
-- !result
show create view sync_mv_rollup;
-- result:
[REGEX].*Table.*sync_mv_rollup.*is not found.*
-- !result
drop database test_show_create_sync_mv_${uuid0};
-- result:
-- !result
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- name: test_show_create_sync_materialized_view @native

create database test_show_create_sync_mv_${uuid0};
use test_show_create_sync_mv_${uuid0};

create table sync_mv_base
(
k1 date,
k2 int,
v1 int sum
)
distributed by hash(k2) buckets 3
properties('replication_num' = '1');

-- Sync materialized view (legacy rollup style: no DISTRIBUTED BY, no REFRESH
-- clause). Stored as a MaterializedIndexMeta inside the base OLAP table, not
-- as a separately-registered Table.
create materialized view sync_mv_rollup as select k2, sum(v1) from sync_mv_base group by k2;
function: wait_materialized_view_finish()

-- Regression: SHOW CREATE MATERIALIZED VIEW on a sync MV must locate the
-- MaterializedIndexMeta via the dedicated scan path inside
-- showCreateInternalCatalogTable. Without the fix this returned
-- "Table sync_mv_rollup is not found" because the regular name lookup misses
-- sync MV index names.
show create materialized view sync_mv_rollup;

-- Negative cases: SHOW CREATE TABLE / VIEW on a sync MV name must still fail
-- with "Table not found" (the fallback scan is only entered for MV-typed
-- queries; see ShowExecutor.showCreateInternalCatalogTable).
show create table sync_mv_rollup;
show create view sync_mv_rollup;

drop database test_show_create_sync_mv_${uuid0};
Loading