From 4bd106b535e28ddaf2e3d8d93f75698ccd947d4d Mon Sep 17 00:00:00 2001 From: seawinde Date: Wed, 15 Jan 2025 20:34:13 +0800 Subject: [PATCH 01/26] [test](mtmv) Fix sync mv not partition in rewrite test and some other test problems (#46546) ### What problem does this PR solve? Fix sync mv not partition in rewrite because thought mv is already built wrongly In the `createMV()` method of `Suite.groovy`, it checks whether the most recent materialized view of the current database's last table has been built successfully. If a database has two tables, the synchronization materialized view of the second table may not be completed, which could incorrectly lead to the assumption that the build is complete. To address this issue, modify the test case to ensure that there is at most one table per database. --- .../doris/nereids/stats/StatsCalculator.java | 4 +- .../unique_rewrite.out | 0 .../mv/newMv/multi_slot4.out | 14 ++- .../test_show_create_materialized_view.out | 2 +- .../doris/regression/suite/Suite.groovy | 96 +++++++++++++------ .../suites/auth_call/test_ddl_mv_auth.groovy | 4 +- .../auth_p0/test_select_column_auth.groovy | 2 +- .../test_mv_case/test_mv_case.groovy | 13 ++- .../create_view_use_mv.groovy | 4 +- .../suites/mtmv_p0/test_iceberg_mtmv.groovy | 28 +++--- .../test_paimon_olap_rewrite_mtmv.groovy | 8 ++ .../suites/mv_p0/no_await/no_await.groovy | 39 ++++---- .../unique_rewrite.groovy | 0 .../mv/date_trunc/mv_with_date_trunc.groovy | 2 +- .../mv/nested_mtmv/nested_mtmv.groovy | 6 +- .../mv/newMv/multi_slot4.groovy | 10 +- .../test_show_create_materialized_view.groovy | 7 +- .../test_dup_mv_schema_change.groovy | 4 +- .../test_uniq_mv_schema_change.groovy | 21 +--- 19 files changed, 158 insertions(+), 106 deletions(-) rename regression-test/data/mv_p0/{unique => unique_rewrite}/unique_rewrite.out (100%) rename regression-test/suites/mv_p0/{unique => unique_rewrite}/unique_rewrite.groovy (100%) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java index 64019818f5d1e7..5daf3bb254fc24 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java @@ -501,8 +501,8 @@ private Statistics computeOlapScan(OlapScan olapScan) { // mv is selected, return its estimated stats Optional optStats = cascadesContext.getStatementContext() .getStatistics(((Relation) olapScan).getRelationId()); - LOG.info("computeOlapScan optStats isPresent {}, tableRowCount is {}", - optStats.isPresent(), tableRowCount); + LOG.info("computeOlapScan optStats isPresent {}, tableRowCount is {}, table name is {}", + optStats.isPresent(), tableRowCount, olapTable.getQualifiedName()); if (optStats.isPresent()) { double selectedPartitionsRowCount = getSelectedPartitionRowCount(olapScan, tableRowCount); LOG.info("computeOlapScan optStats is {}, selectedPartitionsRowCount is {}", optStats.get(), diff --git a/regression-test/data/mv_p0/unique/unique_rewrite.out b/regression-test/data/mv_p0/unique_rewrite/unique_rewrite.out similarity index 100% rename from regression-test/data/mv_p0/unique/unique_rewrite.out rename to regression-test/data/mv_p0/unique_rewrite/unique_rewrite.out diff --git a/regression-test/data/nereids_syntax_p0/mv/newMv/multi_slot4.out b/regression-test/data/nereids_syntax_p0/mv/newMv/multi_slot4.out index 5b500067986366..264a653fd3a3d0 100644 --- a/regression-test/data/nereids_syntax_p0/mv/newMv/multi_slot4.out +++ b/regression-test/data/nereids_syntax_p0/mv/newMv/multi_slot4.out @@ -1,14 +1,22 @@ -- This file is automatically generated. You should know what you did if you want to edit this -- !select_star -- -4 -4 -4 d +-4 -4 -4 d +-4 -4 -4 d +1 1 1 a +1 1 1 a 1 1 1 a 2 2 2 b +2 2 2 b +2 2 2 b +3 -3 \N c +3 -3 \N c 3 -3 \N c 3 -3 \N c -- !select_mv -- --3 1 -2 7 -3 9 +-3 3 +2 21 +3 27 4 \N diff --git a/regression-test/data/query_p0/show/test_show_create_materialized_view.out b/regression-test/data/query_p0/show/test_show_create_materialized_view.out index 040ebf56f29fc5..1d874bb14d3c5a 100644 --- a/regression-test/data/query_p0/show/test_show_create_materialized_view.out +++ b/regression-test/data/query_p0/show/test_show_create_materialized_view.out @@ -1,4 +1,4 @@ -- This file is automatically generated. You should know what you did if you want to edit this -- !cmd -- -table_for_mv_test mv_show_create_materialized_view CREATE MATERIALIZED VIEW mv_show_create_materialized_view AS\n SELECT id, name, SUM(value) AS total_value\n FROM table_for_mv_test\n GROUP BY id, name;\n +table_for_mv_test mv_show_create_materialized_view \n CREATE MATERIALIZED VIEW mv_show_create_materialized_view \n AS \n SELECT id, name, SUM(value) AS total_value\n FROM table_for_mv_test\n GROUP BY id, name\n diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy index 5cbed97829c990..155cb5868bce59 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy @@ -742,10 +742,16 @@ class Suite implements GroovyInterceptable { return result } + // Should use create_sync_mv, this method only check the sync mv in current db + // If has multi sync mv in db, may make mistake + @Deprecated void createMV(String sql) { (new CreateMVAction(context, sql)).run() } + // Should use create_sync_mv, this method only check the sync mv in current db + // If has multi sync mv in db, may make mistake + @Deprecated void createMV(String sql, String expection) { (new CreateMVAction(context, sql, expection)).run() } @@ -1475,80 +1481,101 @@ class Suite implements GroovyInterceptable { return debugPoint } - void waitingMTMVTaskFinishedByMvName(String mvName) { + def waitingMTMVTaskFinishedByMvName = { mvName, dbName = context.dbName -> Thread.sleep(2000); - String showTasks = "select TaskId,JobId,JobName,MvId,Status,MvName,MvDatabaseName,ErrorMsg from tasks('type'='mv') where MvName = '${mvName}' order by CreateTime ASC" + String showTasks = "select TaskId,JobId,JobName,MvId,Status,MvName,MvDatabaseName,ErrorMsg from tasks('type'='mv') where MvDatabaseName = '${dbName}' and MvName = '${mvName}' order by CreateTime DESC LIMIT 1" String status = "NULL" List> result long startTime = System.currentTimeMillis() long timeoutTimestamp = startTime + 5 * 60 * 1000 // 5 min - do { + List toCheckTaskRow = new ArrayList<>(); + while (timeoutTimestamp > System.currentTimeMillis() && (status != "SUCCESS")) { result = sql(showTasks) - logger.info("result: " + result.toString()) - if (!result.isEmpty()) { - status = result.last().get(4) - } + logger.info("current db is " + dbName + ", showTasks is " + showTasks) + if (result.isEmpty()) { + logger.info("waitingMTMVTaskFinishedByMvName toCheckTaskRow is empty") + Thread.sleep(1000); + continue; + } + toCheckTaskRow = result.get(0); + status = toCheckTaskRow.get(4) logger.info("The state of ${showTasks} is ${status}") Thread.sleep(1000); - } while (timeoutTimestamp > System.currentTimeMillis() && (status == 'PENDING' || status == 'RUNNING' || status == 'NULL')) + } if (status != "SUCCESS") { logger.info("status is not success") } Assert.assertEquals("SUCCESS", status) def show_tables = sql """ - show tables from ${result.last().get(6)}; + show tables from ${toCheckTaskRow.get(6)}; """ - def db_id = getDbId(result.last().get(6)) - def table_id = getTableId(result.last().get(6), mvName) + def db_id = getDbId(toCheckTaskRow.get(6)) + def table_id = getTableId(toCheckTaskRow.get(6), mvName) logger.info("waitingMTMVTaskFinished analyze mv name is " + mvName - + ", db name is " + result.last().get(6) + + ", db name is " + toCheckTaskRow.get(6) + ", show_tables are " + show_tables + ", db_id is " + db_id + ", table_id " + table_id) - sql "analyze table ${result.last().get(6)}.${mvName} with sync;" + sql "analyze table ${toCheckTaskRow.get(6)}.${mvName} with sync;" } - void waitingMTMVTaskFinishedByMvNameAllowCancel(String mvName) { + def waitingMTMVTaskFinishedByMvNameAllowCancel = {mvName, dbName = context.dbName -> Thread.sleep(2000); - String showTasks = "select TaskId,JobId,JobName,MvId,Status,MvName,MvDatabaseName,ErrorMsg from tasks('type'='mv') where MvName = '${mvName}' order by CreateTime ASC" + String showTasks = "select TaskId,JobId,JobName,MvId,Status,MvName,MvDatabaseName,ErrorMsg from tasks('type'='mv') where MvDatabaseName = '${dbName}' and MvName = '${mvName}' order by CreateTime DESC LIMIT 1" + String status = "NULL" List> result long startTime = System.currentTimeMillis() long timeoutTimestamp = startTime + 5 * 60 * 1000 // 5 min - do { + List toCheckTaskRow = new ArrayList<>(); + while (timeoutTimestamp > System.currentTimeMillis() && (status != "SUCCESS")) { result = sql(showTasks) - logger.info("result: " + result.toString()) - if (!result.isEmpty()) { - status = result.last().get(4) - } + logger.info("current db is " + dbName + ", showTasks result: " + result.toString()) + if (result.isEmpty()) { + logger.info("waitingMTMVTaskFinishedByMvName toCheckTaskRow is empty") + Thread.sleep(1000); + continue; + } + toCheckTaskRow = result.get(0) + status = toCheckTaskRow.get(4) logger.info("The state of ${showTasks} is ${status}") Thread.sleep(1000); - } while (timeoutTimestamp > System.currentTimeMillis() && (status == 'PENDING' || status == 'RUNNING' || status == 'NULL' || status == 'CANCELED')) + } if (status != "SUCCESS") { logger.info("status is not success") assertTrue(result.toString().contains("same table")) } // Need to analyze materialized view for cbo to choose the materialized view accurately - logger.info("waitingMTMVTaskFinished analyze mv name is " + result.last().get(5)) - sql "analyze table ${result.last().get(6)}.${mvName} with sync;" + logger.info("waitingMTMVTaskFinished analyze mv name is " + toCheckTaskRow.get(5)) + sql "analyze table ${toCheckTaskRow.get(6)}.${mvName} with sync;" } - void waitingMVTaskFinishedByMvName(String dbName, String tableName) { + void waitingMVTaskFinishedByMvName(String dbName, String tableName, String indexName) { Thread.sleep(2000) - String showTasks = "SHOW ALTER TABLE MATERIALIZED VIEW from ${dbName} where TableName='${tableName}' ORDER BY CreateTime ASC" + String showTasks = "SHOW ALTER TABLE MATERIALIZED VIEW from ${dbName} where TableName='${tableName}' ORDER BY CreateTime DESC" String status = "NULL" List> result long startTime = System.currentTimeMillis() long timeoutTimestamp = startTime + 5 * 60 * 1000 // 5 min - do { + List toCheckTaskRow = new ArrayList<>(); + while (timeoutTimestamp > System.currentTimeMillis() && (status != 'FINISHED')) { result = sql(showTasks) - logger.info("result: " + result.toString()) - if (!result.isEmpty()) { - status = result.last().get(8) + logger.info("crrent db is " + dbName + ", showTasks result: " + result.toString()) + // just consider current db + for (List taskRow : result) { + if (taskRow.get(5).equals(indexName)) { + toCheckTaskRow = taskRow; + } + } + if (toCheckTaskRow.isEmpty()) { + logger.info("waitingMVTaskFinishedByMvName toCheckTaskRow is empty") + Thread.sleep(1000); + continue; } + status = toCheckTaskRow.get(8) logger.info("The state of ${showTasks} is ${status}") Thread.sleep(1000); - } while (timeoutTimestamp > System.currentTimeMillis() && (status != 'FINISHED')) + } if (status != "FINISHED") { logger.info("status is not success") } @@ -1925,6 +1952,15 @@ class Suite implements GroovyInterceptable { return isReady } + def create_sync_mv = { db, table_name, mv_name, mv_sql -> + sql """DROP MATERIALIZED VIEW IF EXISTS ${mv_name} ON ${table_name};""" + sql""" + CREATE MATERIALIZED VIEW ${mv_name} + AS ${mv_sql} + """ + waitingMVTaskFinishedByMvName(db, table_name, mv_name) + } + def create_async_mv = { db, mv_name, mv_sql -> sql """DROP MATERIALIZED VIEW IF EXISTS ${db}.${mv_name}""" diff --git a/regression-test/suites/auth_call/test_ddl_mv_auth.groovy b/regression-test/suites/auth_call/test_ddl_mv_auth.groovy index 4dbf54fdf0df72..0701abb8a8bdb8 100644 --- a/regression-test/suites/auth_call/test_ddl_mv_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_mv_auth.groovy @@ -80,9 +80,9 @@ suite("test_ddl_mv_auth","p0,auth_call") { connect(user, "${pwd}", context.config.jdbcUrl) { sql """use ${dbName}""" sql """create materialized view ${mvName} as select username from ${dbName}.${tableName};""" - waitingMVTaskFinishedByMvName(dbName, tableName) + waitingMVTaskFinishedByMvName(dbName, tableName, mvName) sql """alter table ${dbName}.${tableName} add rollup ${rollupName}(username)""" - waitingMVTaskFinishedByMvName(dbName, tableName) + waitingMVTaskFinishedByMvName(dbName, tableName, rollupName) def mv_res = sql """desc ${dbName}.${tableName} all;""" logger.info("mv_res: " + mv_res) diff --git a/regression-test/suites/auth_p0/test_select_column_auth.groovy b/regression-test/suites/auth_p0/test_select_column_auth.groovy index 36cc2a0a09cf1c..1e93981966d2bc 100644 --- a/regression-test/suites/auth_p0/test_select_column_auth.groovy +++ b/regression-test/suites/auth_p0/test_select_column_auth.groovy @@ -69,7 +69,7 @@ suite("test_select_column_auth","p0,auth") { (3, "333"); """ sql """refresh MATERIALIZED VIEW ${dbName}.${mtmv_name} auto""" - waitingMTMVTaskFinishedByMvName(mtmv_name) + waitingMTMVTaskFinishedByMvName(mtmv_name, dbName) sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/correctness_p0/test_mv_case/test_mv_case.groovy b/regression-test/suites/correctness_p0/test_mv_case/test_mv_case.groovy index 8e548eb27ea87c..eae3d1fb3591fe 100644 --- a/regression-test/suites/correctness_p0/test_mv_case/test_mv_case.groovy +++ b/regression-test/suites/correctness_p0/test_mv_case/test_mv_case.groovy @@ -16,6 +16,7 @@ // under the License. suite("test_mv_case") { + sql """drop table if exists test_table_aaa2;""" sql """CREATE TABLE `test_table_aaa2` ( `ordernum` varchar(65533) NOT NULL , @@ -29,7 +30,7 @@ suite("test_mv_case") { "replication_allocation" = "tag.location.default: 1" );""" sql """DROP MATERIALIZED VIEW IF EXISTS ods_zn_dnt_max1 ON test_table_aaa2;""" - createMV("""create materialized view ods_zn_dnt_max1 as + create_sync_mv(context.dbName, "test_table_aaa2", "ods_zn_dnt_max1", """ select ordernum,max(dnt) as dnt from test_table_aaa2 group by ordernum ORDER BY ordernum;""") @@ -92,7 +93,7 @@ suite("test_mv_case") { ) """ sql """insert into tb1 select id,map_agg(a, b) from(select 123 id,3 a,'5' b union all select 123 id, 6 a, '8' b) aa group by id""" - createMV ("""CREATE MATERIALIZED VIEW mv1 BUILD IMMEDIATE REFRESH COMPLETE ON SCHEDULE EVERY 10 MINUTE DUPLICATE KEY(info_id) DISTRIBUTED BY HASH(`info_id`) BUCKETS 2 PROPERTIES ( + sql"""CREATE MATERIALIZED VIEW mv1 BUILD IMMEDIATE REFRESH COMPLETE ON SCHEDULE EVERY 10 MINUTE DUPLICATE KEY(info_id) DISTRIBUTED BY HASH(`info_id`) BUCKETS 2 PROPERTIES ( "replication_allocation" = "tag.location.default: 1", "min_load_replica_num" = "-1", "is_being_synced" = "false", @@ -112,8 +113,9 @@ suite("test_mv_case") { cast(a.id as bigint) info_id, map_infos from - tb1 a;""") - createMV ("""CREATE MATERIALIZED VIEW mv2 BUILD IMMEDIATE REFRESH COMPLETE ON SCHEDULE EVERY 10 MINUTE DUPLICATE KEY(info_id) DISTRIBUTED BY HASH(`info_id`) BUCKETS 2 PROPERTIES ( + tb1 a;""" + waitingMTMVTaskFinishedByMvName("mv1") + sql """CREATE MATERIALIZED VIEW mv2 BUILD IMMEDIATE REFRESH COMPLETE ON SCHEDULE EVERY 10 MINUTE DUPLICATE KEY(info_id) DISTRIBUTED BY HASH(`info_id`) BUCKETS 2 PROPERTIES ( "replication_allocation" = "tag.location.default: 1", "min_load_replica_num" = "-1", "is_being_synced" = "false", @@ -132,6 +134,7 @@ suite("test_mv_case") { info_id, map_infos from - mv1 a;""") + mv1 a;""" + waitingMTMVTaskFinishedByMvName("mv2") qt_select_mv """ select * from mv2 """ } diff --git a/regression-test/suites/ddl_p0/create_view_nereids/create_view_use_mv.groovy b/regression-test/suites/ddl_p0/create_view_nereids/create_view_use_mv.groovy index 295b195aa58954..7b6069968f4a0a 100644 --- a/regression-test/suites/ddl_p0/create_view_nereids/create_view_use_mv.groovy +++ b/regression-test/suites/ddl_p0/create_view_nereids/create_view_use_mv.groovy @@ -48,8 +48,8 @@ suite("create_view_use_mv") { (3, 1, 1, 2, 7.5, 8.5, 9.5, 10.5, 'k', 'o', '2023-10-19', null, 'c', 'd', 'xxxxxxxxx', '2023-10-19'), (1, 3, 2, 2, 5.5, 6.5, 7.5, 8.5, 'o', 'k', '2023-10-17', '2023-10-17', 'a', 'b', 'yyyyyyyyy', '2023-10-17');""" - createMV(""" - CREATE MATERIALIZED VIEW t_mv_mv AS select + create_sync_mv(context.dbName, "orders", "t_mv_mv", """ + select o_orderkey, sum(o_totalprice) as sum_total, max(o_totalprice) as max_total, diff --git a/regression-test/suites/mtmv_p0/test_iceberg_mtmv.groovy b/regression-test/suites/mtmv_p0/test_iceberg_mtmv.groovy index 36c0d3f120e109..8dd16ea571f1ec 100644 --- a/regression-test/suites/mtmv_p0/test_iceberg_mtmv.groovy +++ b/regression-test/suites/mtmv_p0/test_iceberg_mtmv.groovy @@ -107,35 +107,35 @@ suite("test_iceberg_mtmv", "p0,external,iceberg,external_docker,external_docker_ sql """insert into ${catalog_name}.${icebergDb}.${icebergTable1} values ('2024-10-26 01:02:03', 1), ('2024-10-27 01:02:03', 2), ('2024-10-27 21:02:03', 3)""" sql """CREATE MATERIALIZED VIEW ${mvName1} BUILD DEFERRED REFRESH AUTO ON MANUAL partition by(`ts`) DISTRIBUTED BY RANDOM BUCKETS 2 PROPERTIES ('replication_num' = '1') as SELECT * FROM ${catalog_name}.${icebergDb}.${icebergTable1}""" sql """REFRESH MATERIALIZED VIEW ${mvName1} complete""" - waitingMTMVTaskFinishedByMvName(mvName1) + waitingMTMVTaskFinishedByMvName(mvName1, dbName) qt_test_ts_refresh1 "select * from ${mvName1} order by value" sql """insert into ${catalog_name}.${icebergDb}.${icebergTable1} values ('2024-10-26 21:02:03', 4)""" sql """REFRESH MATERIALIZED VIEW ${mvName1} auto""" - waitingMTMVTaskFinishedByMvName(mvName1) + waitingMTMVTaskFinishedByMvName(mvName1, dbName) qt_test_ts_refresh2 """select * from ${mvName1} order by value""" sql """insert into ${catalog_name}.${icebergDb}.${icebergTable1} values ('2024-10-26 01:22:03', 5), ('2024-10-27 01:12:03', 6);""" sql """REFRESH MATERIALIZED VIEW ${mvName1} partitions(p_20241026000000_20241027000000);""" - waitingMTMVTaskFinishedByMvName(mvName1) + waitingMTMVTaskFinishedByMvName(mvName1, dbName) qt_test_ts_refresh3 """select * from ${mvName1} order by value""" sql """REFRESH MATERIALIZED VIEW ${mvName1} auto""" - waitingMTMVTaskFinishedByMvName(mvName1) + waitingMTMVTaskFinishedByMvName(mvName1, dbName) qt_test_ts_refresh4 """select * from ${mvName1} order by value""" sql """insert into ${catalog_name}.${icebergDb}.${icebergTable1} values ('2024-10-28 01:22:03', 7);""" sql """REFRESH MATERIALIZED VIEW ${mvName1} partitions(p_20241026000000_20241027000000);""" - waitingMTMVTaskFinishedByMvName(mvName1) + waitingMTMVTaskFinishedByMvName(mvName1, dbName) qt_test_ts_refresh5 """select * from ${mvName1} order by value""" sql """REFRESH MATERIALIZED VIEW ${mvName1} auto""" - waitingMTMVTaskFinishedByMvName(mvName1) + waitingMTMVTaskFinishedByMvName(mvName1, dbName) qt_test_ts_refresh6 """select * from ${mvName1} order by value""" sql """insert into ${catalog_name}.${icebergDb}.${icebergTable1} values (null, 8);""" sql """REFRESH MATERIALIZED VIEW ${mvName1} auto""" - waitingMTMVTaskFinishedByMvName(mvName1) + waitingMTMVTaskFinishedByMvName(mvName1, dbName) qt_test_ts_refresh_null """select * from ${mvName1} order by value""" def showPartitionsResult = sql """show partitions from ${mvName1}""" @@ -176,25 +176,25 @@ suite("test_iceberg_mtmv", "p0,external,iceberg,external_docker,external_docker_ sql """insert into ${catalog_name}.${icebergDb}.${icebergTable2} values ('2024-08-26', 1), ('2024-09-17', 2), ('2024-09-27', 3);""" sql """CREATE MATERIALIZED VIEW ${mvName2} BUILD DEFERRED REFRESH AUTO ON MANUAL partition by(`d`) DISTRIBUTED BY RANDOM BUCKETS 2 PROPERTIES ('replication_num' = '1') as SELECT * FROM ${catalog_name}.${icebergDb}.${icebergTable2}""" sql """REFRESH MATERIALIZED VIEW ${mvName2} complete""" - waitingMTMVTaskFinishedByMvName(mvName2) + waitingMTMVTaskFinishedByMvName(mvName2, dbName) qt_test_d_refresh1 "select * from ${mvName2} order by value" sql """insert into ${catalog_name}.${icebergDb}.${icebergTable2} values ('2024-09-01', 4);""" sql """REFRESH MATERIALIZED VIEW ${mvName2} auto""" - waitingMTMVTaskFinishedByMvName(mvName2) + waitingMTMVTaskFinishedByMvName(mvName2, dbName) qt_test_d_refresh2 "select * from ${mvName2} order by value" sql """insert into ${catalog_name}.${icebergDb}.${icebergTable2} values ('2024-08-22', 5), ('2024-09-30', 6);""" sql """REFRESH MATERIALIZED VIEW ${mvName2} partitions(p_20240801_20240901);""" - waitingMTMVTaskFinishedByMvName(mvName2) + waitingMTMVTaskFinishedByMvName(mvName2, dbName) qt_test_d_refresh3 "select * from ${mvName2} order by value" sql """REFRESH MATERIALIZED VIEW ${mvName2} partitions(p_20240901_20241001);""" - waitingMTMVTaskFinishedByMvName(mvName2) + waitingMTMVTaskFinishedByMvName(mvName2, dbName) qt_test_d_refresh4 "select * from ${mvName2} order by value" sql """insert into ${catalog_name}.${icebergDb}.${icebergTable2} values ('2024-10-28', 7);""" sql """REFRESH MATERIALIZED VIEW ${mvName2} auto""" - waitingMTMVTaskFinishedByMvName(mvName2) + waitingMTMVTaskFinishedByMvName(mvName2, dbName) qt_test_d_refresh5 "select * from ${mvName2} order by value" showPartitionsResult = sql """show partitions from ${mvName2}""" @@ -240,7 +240,7 @@ suite("test_iceberg_mtmv", "p0,external,iceberg,external_docker,external_docker_ // refresh one partiton sql """REFRESH MATERIALIZED VIEW ${mvName} partitions(p_20240101000000_20240102000000);""" - waitingMTMVTaskFinishedByMvName(mvName) + waitingMTMVTaskFinishedByMvName(mvName, dbName) order_qt_refresh_one_partition "SELECT * FROM ${mvName} " def explainOnePartition = sql """ explain ${mvSql} """ logger.info("explainOnePartition: " + explainOnePartition.toString()) @@ -250,7 +250,7 @@ suite("test_iceberg_mtmv", "p0,external,iceberg,external_docker,external_docker_ //refresh auto sql """REFRESH MATERIALIZED VIEW ${mvName} auto""" - waitingMTMVTaskFinishedByMvName(mvName) + waitingMTMVTaskFinishedByMvName(mvName, dbName) order_qt_refresh_auto "SELECT * FROM ${mvName} " def explainAllPartition = sql """ explain ${mvSql}; """ logger.info("explainAllPartition: " + explainAllPartition.toString()) diff --git a/regression-test/suites/mtmv_p0/test_paimon_olap_rewrite_mtmv.groovy b/regression-test/suites/mtmv_p0/test_paimon_olap_rewrite_mtmv.groovy index a3ac1c048d30da..7a77cdc4590a4c 100644 --- a/regression-test/suites/mtmv_p0/test_paimon_olap_rewrite_mtmv.groovy +++ b/regression-test/suites/mtmv_p0/test_paimon_olap_rewrite_mtmv.groovy @@ -87,6 +87,10 @@ suite("test_paimon_olap_rewrite_mtmv", "p0,external,mtmv,external_docker,externa def explainOnePartition = sql """ explain ${mvSql} """ logger.info("explainOnePartition: " + explainOnePartition.toString()) + + def explain_memo_plan = sql """ explain memo plan ${mvSql} """ + logger.info("explain_memo_plan: " + explain_memo_plan.toString()) + assertTrue(explainOnePartition.toString().contains("VUNION")) order_qt_refresh_one_partition_rewrite "${mvSql}" @@ -104,6 +108,10 @@ suite("test_paimon_olap_rewrite_mtmv", "p0,external,mtmv,external_docker,externa def explainAllPartition = sql """ explain ${mvSql}; """ logger.info("explainAllPartition: " + explainAllPartition.toString()) + + def explainMemoPlan = sql """ explain memo plan ${mvSql}; """ + logger.info("explainMemoPlan: " + explainMemoPlan.toString()) + assertTrue(explainAllPartition.toString().contains("VOlapScanNode")) order_qt_refresh_all_partition_rewrite "${mvSql}" diff --git a/regression-test/suites/mv_p0/no_await/no_await.groovy b/regression-test/suites/mv_p0/no_await/no_await.groovy index 866e4fdd5d3546..3eab03aa7e4adc 100644 --- a/regression-test/suites/mv_p0/no_await/no_await.groovy +++ b/regression-test/suites/mv_p0/no_await/no_await.groovy @@ -19,6 +19,8 @@ import org.codehaus.groovy.runtime.IOGroovyMethods suite ("no_await") { + String db = context.config.getDbNameByFile(context.file) + def tblName = "agg_have_dup_base_no_await" def waitDrop = { def try_times = 1000 @@ -58,92 +60,93 @@ suite ("no_await") { """ sql "insert into ${tblName} select e1, -4, -4, 'd' from (select 1 k1) as t lateral view explode_numbers(10000) tmp1 as e1;" // do not await - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") + sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" waitDrop() - sql "create materialized view k12s3m as select k1,sum(k2),max(k2) from ${tblName} group by k1;" + create_sync_mv(db, tblName, "k12s3m", """select k1,sum(k2),max(k2) from ${tblName} group by k1;""") sql "insert into ${tblName} select -4, -4, -4, \'d\'" qt_mv "select sum(k1) from ${tblName}" } diff --git a/regression-test/suites/mv_p0/unique/unique_rewrite.groovy b/regression-test/suites/mv_p0/unique_rewrite/unique_rewrite.groovy similarity index 100% rename from regression-test/suites/mv_p0/unique/unique_rewrite.groovy rename to regression-test/suites/mv_p0/unique_rewrite/unique_rewrite.groovy diff --git a/regression-test/suites/nereids_rules_p0/mv/date_trunc/mv_with_date_trunc.groovy b/regression-test/suites/nereids_rules_p0/mv/date_trunc/mv_with_date_trunc.groovy index 58b2e7bbdd8ca3..e88f4132eacfba 100644 --- a/regression-test/suites/nereids_rules_p0/mv/date_trunc/mv_with_date_trunc.groovy +++ b/regression-test/suites/nereids_rules_p0/mv/date_trunc/mv_with_date_trunc.groovy @@ -1447,7 +1447,7 @@ suite("mv_with_date_trunc") { logger.info("lineitem table stats: " + result) result = sql """show index stats lineitem lineitem""" logger.info("lineitem index stats: " + result) - mv_rewrite_success(query4_0, "mv4_0") + mv_rewrite_success(query4_0, "mv4_0", true, is_partition_statistics_ready(db, ["lineitem", "mv4_0"])) order_qt_query4_0_after "${query4_0}" sql """ DROP MATERIALIZED VIEW IF EXISTS mv4_0""" diff --git a/regression-test/suites/nereids_rules_p0/mv/nested_mtmv/nested_mtmv.groovy b/regression-test/suites/nereids_rules_p0/mv/nested_mtmv/nested_mtmv.groovy index 4870ec99e659c0..1972c2d505bfab 100644 --- a/regression-test/suites/nereids_rules_p0/mv/nested_mtmv/nested_mtmv.groovy +++ b/regression-test/suites/nereids_rules_p0/mv/nested_mtmv/nested_mtmv.groovy @@ -742,10 +742,12 @@ suite("nested_mtmv") { mv_rewrite_any_success(sql_2, [mv_1, mv_2]) compare_res(sql_2 + " order by 1,2,3,4,5,6,7,8,9,10,11,12,13") - mv_rewrite_any_success(sql_3, [mv_3, mv_4]) + // level 1 maybe use mv_1 and mv_2, this also meets expectation + mv_rewrite_any_success(sql_3, [mv_3, mv_4, mv_1, mv_2]) compare_res(sql_3 + " order by 1,2,3,4,5,6,7,8,9,10,11,12,13") - mv_rewrite_any_success(sql_4, [mv_3, mv_4]) + // level 1 maybe use mv_1 and mv_2, this also meets expectation + mv_rewrite_any_success(sql_4, [mv_3, mv_4, mv_1, mv_2]) compare_res(sql_4 + " order by 1,2,3,4,5,6,7,8,9,10,11,12,13") mv_rewrite_any_success(sql_5, [mv_3, mv_4, mv_5]) diff --git a/regression-test/suites/nereids_syntax_p0/mv/newMv/multi_slot4.groovy b/regression-test/suites/nereids_syntax_p0/mv/newMv/multi_slot4.groovy index 7a49dbc4b37118..32e1684fdc9073 100644 --- a/regression-test/suites/nereids_syntax_p0/mv/newMv/multi_slot4.groovy +++ b/regression-test/suites/nereids_syntax_p0/mv/newMv/multi_slot4.groovy @@ -33,21 +33,29 @@ suite ("multi_slot4") { """ sql "insert into multi_slot4 select 1,1,1,'a';" + sql "insert into multi_slot4 select 1,1,1,'a';" + sql "insert into multi_slot4 select 1,1,1,'a';" + sql "insert into multi_slot4 select 2,2,2,'b';" + sql "insert into multi_slot4 select 2,2,2,'b';" sql "insert into multi_slot4 select 2,2,2,'b';" sql "insert into multi_slot4 select 3,-3,null,'c';" sql "insert into multi_slot4 select 3,-3,null,'c';" + sql "insert into multi_slot4 select 3,-3,null,'c';" + sql "insert into multi_slot4 select 3,-3,null,'c';" createMV ("create materialized view k1p2ap3ps as select k1+1,sum(abs(k2+2)+k3+3) from multi_slot4 group by k1+1;") sleep(3000) + sql "insert into multi_slot4 select -4,-4,-4,'d';" + sql "insert into multi_slot4 select -4,-4,-4,'d';" sql "insert into multi_slot4 select -4,-4,-4,'d';" sql "SET experimental_enable_nereids_planner=true" sql "SET enable_fallback_to_original_planner=false" sql "analyze table multi_slot4 with sync;" - sql """alter table multi_slot4 modify column k1 set stats ('row_count'='5');""" + sql """alter table multi_slot4 modify column k1 set stats ('row_count'='13');""" sql """set enable_stats=false;""" diff --git a/regression-test/suites/query_p0/show/test_show_create_materialized_view.groovy b/regression-test/suites/query_p0/show/test_show_create_materialized_view.groovy index 9550a7fec3dbd2..56f5d655255049 100644 --- a/regression-test/suites/query_p0/show/test_show_create_materialized_view.groovy +++ b/regression-test/suites/query_p0/show/test_show_create_materialized_view.groovy @@ -30,12 +30,11 @@ suite("test_show_create_materialized_view", "query,arrow_flight_sql") { DISTRIBUTED BY HASH(id) BUCKETS 5 PROPERTIES ("replication_num" = "1"); """ - - createMV("""CREATE MATERIALIZED VIEW ${mvName} AS + + create_sync_mv(context.dbName, tableName, mvName, """ SELECT id, name, SUM(value) AS total_value FROM ${tableName} - GROUP BY id, name; - """) + GROUP BY id, name""") checkNereidsExecute("""SHOW CREATE MATERIALIZED VIEW ${mvName} ON ${tableName};""") qt_cmd("""SHOW CREATE MATERIALIZED VIEW ${mvName} ON ${tableName};""") diff --git a/regression-test/suites/schema_change_p0/test_dup_mv_schema_change.groovy b/regression-test/suites/schema_change_p0/test_dup_mv_schema_change.groovy index 713c470436e7fb..a6ad20ec623048 100644 --- a/regression-test/suites/schema_change_p0/test_dup_mv_schema_change.groovy +++ b/regression-test/suites/schema_change_p0/test_dup_mv_schema_change.groovy @@ -70,7 +70,7 @@ suite ("test_dup_mv_schema_change") { """ //add materialized view - createMV("create materialized view mv1 as select date, user_id, city, age from ${tableName};") + create_sync_mv(context.dbName, tableName, "mv1", """select date, user_id, city, age from ${tableName}""") // alter and test light schema change if (!isCloudMode()) { @@ -78,7 +78,7 @@ suite ("test_dup_mv_schema_change") { } //add materialized view - createMV("create materialized view mv2 as select date, user_id, city, age, cost from ${tableName};") + create_sync_mv(context.dbName, tableName, "mv2", """select date, user_id, city, age, cost from ${tableName}""") sql """ INSERT INTO ${tableName} VALUES (2, '2017-10-01', 'Beijing', 10, 1, '2020-01-02', '2020-01-02', '2020-01-02', 1, 31, 21) diff --git a/regression-test/suites/schema_change_p0/test_uniq_mv_schema_change.groovy b/regression-test/suites/schema_change_p0/test_uniq_mv_schema_change.groovy index eba6036c30a628..1d8fdd4d1e7192 100644 --- a/regression-test/suites/schema_change_p0/test_uniq_mv_schema_change.groovy +++ b/regression-test/suites/schema_change_p0/test_uniq_mv_schema_change.groovy @@ -21,20 +21,7 @@ import org.awaitility.Awaitility suite ("test_uniq_mv_schema_change") { def tableName = "schema_change_uniq_mv_regression_test" - def getMVJobState = { tbName -> - def jobStateResult = sql """ SHOW ALTER TABLE MATERIALIZED VIEW WHERE TableName='${tbName}' ORDER BY CreateTime DESC LIMIT 1 """ - return jobStateResult[0][8] - } - def waitForJob = (tbName, timeout) -> { - Awaitility.await().atMost(timeout, TimeUnit.SECONDS).with().pollDelay(100, TimeUnit.MILLISECONDS).await().until(() -> { - String result = getMVJobState(tbName) - if (result == "FINISHED") { - return true; - } - return false; - }); - // when timeout awaitlity will raise a exception. - } + try { String backend_id; @@ -78,8 +65,7 @@ suite ("test_uniq_mv_schema_change") { //add materialized view def mvName = "mv1" - sql "create materialized view ${mvName} as select user_id, date, city, age, sex from ${tableName};" - waitForJob(tableName, 3000) + create_sync_mv(context.dbName, tableName, mvName, """select user_id, date, city, age, sex from ${tableName}""") // alter and test light schema change if (!isCloudMode()) { @@ -88,8 +74,7 @@ suite ("test_uniq_mv_schema_change") { //add materialized view def mvName2 = "mv2" - sql "create materialized view ${mvName2} as select user_id, date, city, age, sex, cost from ${tableName};" - waitForJob(tableName, 3000) + create_sync_mv(context.dbName, tableName, mvName2, """select user_id, date, city, age, sex, cost from ${tableName};""") sql """ INSERT INTO ${tableName} VALUES (2, '2017-10-01', 'Beijing', 10, 1, '2020-01-02', '2020-01-02', '2020-01-02', 1, 31, 21) From e35e01920d5d895b9333ac89e2278ed426a51f7b Mon Sep 17 00:00:00 2001 From: Xin Liao Date: Wed, 15 Jan 2025 21:32:32 +0800 Subject: [PATCH 02/26] [opt](load) Add config to control commit lock scope for tables (#46996) Problem Summary: Previously, all tables required commit locks during transaction commit, which helped reduce conflicts at the MetaService level. However, this approach may not be optimal for all scenarios since only MOW (Merge-on-Write) tables truly need strict concurrency control. This PR adds a new config `enable_commit_lock_for_all_tables` (default: true) to control the commit lock strategy: - When enabled (default): All tables will acquire commit locks during transaction commit, which helps reduce conflicts at MetaService level by queueing transactions at FE level - When disabled: Only MOW tables will acquire commit locks, which may improve concurrency for non-MOW tables but could increase conflicts at MetaService level The default setting maintains the original behavior to avoid potential performance impact from increased MetaService conflicts, while providing flexibility to optimize for different deployment scenarios. --- .../java/org/apache/doris/common/Config.java | 8 ++++ .../CloudGlobalTransactionMgr.java | 40 +++++++++++-------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java index 4a6833455560b3..8bda7f6e754c2f 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java @@ -3281,6 +3281,14 @@ public static int metaServiceRpcRetryTimes() { @ConfField(mutable = true, description = {"存算分离模式下commit阶段等锁超时时间,默认5s"}) public static int try_commit_lock_timeout_seconds = 5; + @ConfField(mutable = true, description = {"是否在事务提交时对所有表启用提交锁。设置为 true 时,所有表都会使用提交锁。" + + "设置为 false 时,仅对 Merge-On-Write 表使用提交锁。默认值为 true。", + "Whether to enable commit lock for all tables during transaction commit." + + "If true, commit lock will be applied to all tables." + + "If false, commit lock will only be applied to Merge-On-Write tables." + + "Default value is true." }) + public static boolean enable_commit_lock_for_all_tables = true; + @ConfField(mutable = true, description = {"存算分离模式下是否开启大事务提交,默认false"}) public static boolean enable_cloud_txn_lazy_commit = false; diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java index 0084e677d21da9..e1e722443e40f4 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java @@ -1166,7 +1166,21 @@ private void commitTransactionWithSubTxns(long dbId, List tableList, long executeCommitTxnRequest(commitTxnRequest, transactionId, false, null); } - // add some log and get commit lock, mainly used for mow tables + private List
getTablesNeedCommitLock(List
tableList) { + if (Config.enable_commit_lock_for_all_tables) { + // If enabled, lock all tables + return tableList.stream() + .sorted(Comparator.comparingLong(Table::getId)) + .collect(Collectors.toList()); + } else { + // If disabled, only lock MOW tables + return tableList.stream() + .filter(table -> table instanceof OlapTable && ((OlapTable) table).getEnableUniqueKeyMergeOnWrite()) + .sorted(Comparator.comparingLong(Table::getId)) + .collect(Collectors.toList()); + } + } + private void beforeCommitTransaction(List
tableList, long transactionId, long timeoutMillis) throws UserException { for (int i = 0; i < tableList.size(); i++) { @@ -1180,29 +1194,21 @@ private void beforeCommitTransaction(List
tableList, long transactionId, } } - // Get tables that require commit lock - only MOW tables need this: - // 1. Filter to keep only OlapTables with MOW enabled - // 2. Sort by table ID to maintain consistent locking order and prevent deadlocks - List
mowTableList = tableList.stream() - .filter(table -> table instanceof OlapTable && ((OlapTable) table).getEnableUniqueKeyMergeOnWrite()) - .sorted(Comparator.comparingLong(Table::getId)) - .collect(Collectors.toList()); - increaseWaitingLockCount(mowTableList); - if (!MetaLockUtils.tryCommitLockTables(mowTableList, timeoutMillis, TimeUnit.MILLISECONDS)) { - decreaseWaitingLockCount(mowTableList); + List
tablesToLock = getTablesNeedCommitLock(tableList); + increaseWaitingLockCount(tablesToLock); + if (!MetaLockUtils.tryCommitLockTables(tablesToLock, timeoutMillis, TimeUnit.MILLISECONDS)) { + decreaseWaitingLockCount(tablesToLock); // DELETE_BITMAP_LOCK_ERR will be retried on be throw new UserException(InternalErrorCode.DELETE_BITMAP_LOCK_ERR, "get table cloud commit lock timeout, tableList=(" - + StringUtils.join(mowTableList, ",") + ")"); + + StringUtils.join(tablesToLock, ",") + ")"); } } private void afterCommitTransaction(List
tableList) { - List
mowTableList = tableList.stream() - .filter(table -> table instanceof OlapTable && ((OlapTable) table).getEnableUniqueKeyMergeOnWrite()) - .collect(Collectors.toList()); - decreaseWaitingLockCount(mowTableList); - MetaLockUtils.commitUnlockTables(mowTableList); + List
tablesToUnlock = getTablesNeedCommitLock(tableList); + decreaseWaitingLockCount(tablesToUnlock); + MetaLockUtils.commitUnlockTables(tablesToUnlock); } @Override From b6a866b6b5d0c2c346d4c36e394cff1b9d055922 Mon Sep 17 00:00:00 2001 From: chunping Date: Wed, 15 Jan 2025 22:12:48 +0800 Subject: [PATCH 03/26] =?UTF-8?q?[improvement](test)aovid=20cluster=20cont?= =?UTF-8?q?ain=20keyword=20that=20will=20make=20grant=E2=80=A6=20(#47010)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![image](https://github.com/user-attachments/assets/248d112c-10d4-4790-9d98-a0ec2d58b694) --- .../suites/account_p0/test_nereids_authentication.groovy | 2 +- .../suites/account_p0/test_nereids_row_policy.groovy | 4 ++-- .../auth_call/test_account_management_grant_auth.groovy | 2 +- .../auth_call/test_account_management_role_auth.groovy | 2 +- .../auth_call/test_account_management_user_auth.groovy | 2 +- .../suites/auth_call/test_assistant_command_auth.groovy | 2 +- .../suites/auth_call/test_cluster_management_auth.groovy | 2 +- .../suites/auth_call/test_database_management_auth.groovy | 2 +- .../suites/auth_call/test_ddl_catalog_auth.groovy | 2 +- .../suites/auth_call/test_ddl_database_auth.groovy | 2 +- .../suites/auth_call/test_ddl_encryptkey_auth.groovy | 2 +- .../suites/auth_call/test_ddl_file_auth.groovy | 2 +- .../suites/auth_call/test_ddl_function_auth.groovy | 2 +- .../suites/auth_call/test_ddl_index_auth.groovy | 2 +- regression-test/suites/auth_call/test_ddl_job_auth.groovy | 2 +- .../suites/auth_call/test_ddl_mask_view_auth.groovy | 2 +- .../suites/auth_call/test_ddl_mtmv_auth.groovy | 2 +- regression-test/suites/auth_call/test_ddl_mv_auth.groovy | 2 +- .../suites/auth_call/test_ddl_part_table_auth.groovy | 2 +- .../suites/auth_call/test_ddl_row_policy_auth.groovy | 2 +- .../suites/auth_call/test_ddl_sql_block_rule_auth.groovy | 2 +- .../suites/auth_call/test_ddl_table_auth.groovy | 2 +- .../suites/auth_call/test_ddl_view_auth.groovy | 2 +- .../suites/auth_call/test_dml_analyze_auth.groovy | 2 +- .../suites/auth_call/test_dml_broker_load_auth.groovy | 2 +- .../suites/auth_call/test_dml_cancel_profile_auth.groovy | 2 +- .../suites/auth_call/test_dml_delete_table_auth.groovy | 2 +- .../suites/auth_call/test_dml_export_table_auth.groovy | 2 +- .../suites/auth_call/test_dml_insert_auth.groovy | 2 +- .../auth_call/test_dml_multi_routine_load_auth.groovy | 2 +- .../suites/auth_call/test_dml_mysql_load_auth.groovy | 2 +- .../suites/auth_call/test_dml_outfile_auth.groovy | 2 +- .../suites/auth_call/test_dml_routine_load_auth.groovy | 2 +- .../suites/auth_call/test_dml_select_udf_auth.groovy | 2 +- .../suites/auth_call/test_dml_stream_load_auth.groovy | 2 +- .../suites/auth_call/test_dml_update_table_auth.groovy | 2 +- .../auth_call/test_grant_show_view_priv_auth.groovy | 2 +- .../suites/auth_call/test_hive_base_case_auth.groovy | 2 +- .../suites/auth_call/test_show_backend_auth.groovy | 2 +- .../suites/auth_call/test_show_broker_auth.groovy | 2 +- .../suites/auth_call/test_show_charset_auth.groovy | 2 +- .../auth_call/test_show_convert_light_sc_auth.groovy | 2 +- .../suites/auth_call/test_show_create_table_auth.groovy | 2 +- .../suites/auth_call/test_show_data_auth.groovy | 2 +- .../suites/auth_call/test_show_database_id_auth.groovy | 2 +- .../suites/auth_call/test_show_dynamic_table_auth.groovy | 2 +- .../suites/auth_call/test_show_frontend_auth.groovy | 2 +- .../suites/auth_call/test_show_grant_auth.groovy | 2 +- .../suites/auth_call/test_show_proc_auth.groovy | 2 +- .../suites/auth_call/test_show_query_stats_auth.groovy | 2 +- .../suites/auth_call/test_show_tablet_auth.groovy | 2 +- .../suites/auth_call/test_show_typecast_auth.groovy | 2 +- regression-test/suites/auth_p0/test_backends_auth.groovy | 2 +- regression-test/suites/auth_p0/test_catalogs_auth.groovy | 2 +- regression-test/suites/auth_p0/test_frontends_auth.groovy | 2 +- .../suites/auth_p0/test_frontends_disks_auth.groovy | 2 +- .../auth_p0/test_master_slave_consistency_auth.groovy | 2 +- regression-test/suites/auth_p0/test_mtmv_auth.groovy | 2 +- .../suites/auth_p0/test_partition_values_tvf_auth.groovy | 2 +- .../suites/auth_p0/test_partitions_auth.groovy | 2 +- regression-test/suites/auth_p0/test_query_tvf_auth.groovy | 2 +- .../suites/auth_p0/test_select_column_auth.groovy | 2 +- .../suites/auth_p0/test_select_count_auth.groovy | 2 +- .../suites/auth_p0/test_select_view_auth.groovy | 2 +- .../suites/auth_p0/test_use_encryptkey_auth.groovy | 2 +- regression-test/suites/auth_up_down_p0/load.groovy | 4 ++-- .../suites/external_table_p0/jdbc/test_jdbc_call.groovy | 4 ++-- .../lower_case/test_conflict_name.groovy | 2 +- .../lower_case/test_lower_case_meta_include.groovy | 2 +- .../test_lower_case_meta_show_and_select.groovy | 2 +- ...est_lower_case_meta_with_lower_table_conf_grant.groovy | 4 ++-- ...case_meta_with_lower_table_conf_show_and_select.groovy | 2 +- .../lower_case/test_lower_case_mtmv.groovy | 2 +- .../test_meta_cache_select_without_refresh.groovy | 2 +- .../lower_case/test_meta_names_mapping.groovy | 2 +- .../lower_case/test_timing_refresh_catalog.groovy | 2 +- .../external_table_p0/lower_case/upgrade/load.groovy | 2 +- .../tvf/test_insert_from_tvf_with_common_user.groovy | 2 +- .../tvf/test_s3_tvf_with_resource.groovy | 2 +- .../suites/external_table_p2/tvf/test_iceberg_meta.groovy | 2 +- .../suites/javaudf_p0/test_javaudf_auth.groovy | 2 +- .../routine_load/test_routine_load_with_user.groovy | 2 +- .../suites/load_p0/stream_load/test_stream_load.groovy | 2 +- .../stream_load/test_stream_load_move_memtable.groovy | 2 +- .../suites/manager/test_manager_interface_3.groovy | 8 ++++---- .../nereids_p0/authorization/column_authorization.groovy | 2 +- .../nereids_p0/authorization/view_authorization.groovy | 2 +- .../nereids_p0/cache/parse_sql_from_sql_cache.groovy | 8 ++++---- .../nereids_p0/insert_into_table/insert_auth.groovy | 2 +- .../suites/query_p0/system/test_partitions_schema.groovy | 2 +- .../suites/query_p0/system/test_table_options.groovy | 2 +- .../suites/query_p0/system/test_table_properties.groovy | 2 +- regression-test/suites/query_p0/test_row_policy.groovy | 2 +- .../suites/workload_manager_p0/test_curd_wlg.groovy | 4 ++-- .../suites/workload_manager_p0/test_resource_tag.groovy | 2 +- 95 files changed, 106 insertions(+), 106 deletions(-) diff --git a/regression-test/suites/account_p0/test_nereids_authentication.groovy b/regression-test/suites/account_p0/test_nereids_authentication.groovy index c2ee5f12e8cb1d..4b8c644495c039 100644 --- a/regression-test/suites/account_p0/test_nereids_authentication.groovy +++ b/regression-test/suites/account_p0/test_nereids_authentication.groovy @@ -49,7 +49,7 @@ suite("test_nereids_authentication", "query") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } def tokens = context.config.jdbcUrl.split('/') diff --git a/regression-test/suites/account_p0/test_nereids_row_policy.groovy b/regression-test/suites/account_p0/test_nereids_row_policy.groovy index 38426dc09e6ef4..4463a41c90b4d8 100644 --- a/regression-test/suites/account_p0/test_nereids_row_policy.groovy +++ b/regression-test/suites/account_p0/test_nereids_row_policy.groovy @@ -28,7 +28,7 @@ suite("test_nereids_row_policy") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } def assertQueryResult = { size -> @@ -88,7 +88,7 @@ suite("test_nereids_row_policy") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } dropPolciy "policy0" diff --git a/regression-test/suites/auth_call/test_account_management_grant_auth.groovy b/regression-test/suites/auth_call/test_account_management_grant_auth.groovy index eff62e64f88e8a..a1e1f68916f90a 100644 --- a/regression-test/suites/auth_call/test_account_management_grant_auth.groovy +++ b/regression-test/suites/auth_call/test_account_management_grant_auth.groovy @@ -31,7 +31,7 @@ suite("test_account_management_grant_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_account_management_role_auth.groovy b/regression-test/suites/auth_call/test_account_management_role_auth.groovy index c3b278100815f8..dab9596b8c2ae5 100644 --- a/regression-test/suites/auth_call/test_account_management_role_auth.groovy +++ b/regression-test/suites/auth_call/test_account_management_role_auth.groovy @@ -32,7 +32,7 @@ suite("test_account_management_role_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_account_management_user_auth.groovy b/regression-test/suites/auth_call/test_account_management_user_auth.groovy index fe6d6805f47f9b..9cbb489615b41e 100644 --- a/regression-test/suites/auth_call/test_account_management_user_auth.groovy +++ b/regression-test/suites/auth_call/test_account_management_user_auth.groovy @@ -30,7 +30,7 @@ suite("test_account_management_user_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_assistant_command_auth.groovy b/regression-test/suites/auth_call/test_assistant_command_auth.groovy index 1b47e566ff31e8..ba539f129d7bc9 100644 --- a/regression-test/suites/auth_call/test_assistant_command_auth.groovy +++ b/regression-test/suites/auth_call/test_assistant_command_auth.groovy @@ -31,7 +31,7 @@ suite("test_assistant_command_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_cluster_management_auth.groovy b/regression-test/suites/auth_call/test_cluster_management_auth.groovy index 2061b9dbca773a..f769f29c967abc 100644 --- a/regression-test/suites/auth_call/test_cluster_management_auth.groovy +++ b/regression-test/suites/auth_call/test_cluster_management_auth.groovy @@ -66,7 +66,7 @@ suite ("test_cluster_management_auth","nonConcurrent,p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_database_management_auth.groovy b/regression-test/suites/auth_call/test_database_management_auth.groovy index fb643d9ee089e2..71f1902299654b 100644 --- a/regression-test/suites/auth_call/test_database_management_auth.groovy +++ b/regression-test/suites/auth_call/test_database_management_auth.groovy @@ -29,7 +29,7 @@ suite("test_database_management_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; error_in_cloud = "Unsupported" } diff --git a/regression-test/suites/auth_call/test_ddl_catalog_auth.groovy b/regression-test/suites/auth_call/test_ddl_catalog_auth.groovy index 80f71a2e16f94b..8d6b31d35c9217 100644 --- a/regression-test/suites/auth_call/test_ddl_catalog_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_catalog_auth.groovy @@ -29,7 +29,7 @@ suite("test_ddl_catalog_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """create catalog if not exists ${catalogNameOther} properties ( diff --git a/regression-test/suites/auth_call/test_ddl_database_auth.groovy b/regression-test/suites/auth_call/test_ddl_database_auth.groovy index 2821a375ddb559..bdcf5ff7c39be7 100644 --- a/regression-test/suites/auth_call/test_ddl_database_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_database_auth.groovy @@ -30,7 +30,7 @@ suite("test_ddl_database_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_encryptkey_auth.groovy b/regression-test/suites/auth_call/test_ddl_encryptkey_auth.groovy index 0749bdab71780a..dfa469bfce0028 100644 --- a/regression-test/suites/auth_call/test_ddl_encryptkey_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_encryptkey_auth.groovy @@ -28,7 +28,7 @@ suite("test_ddl_encryptkey_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_file_auth.groovy b/regression-test/suites/auth_call/test_ddl_file_auth.groovy index e32c26ecc220d0..35ac9f2632e5cc 100644 --- a/regression-test/suites/auth_call/test_ddl_file_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_file_auth.groovy @@ -28,7 +28,7 @@ suite("test_ddl_file_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_function_auth.groovy b/regression-test/suites/auth_call/test_ddl_function_auth.groovy index 2fa524bf424bd0..a2e38f0eb6d6c8 100644 --- a/regression-test/suites/auth_call/test_ddl_function_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_function_auth.groovy @@ -28,7 +28,7 @@ suite("test_ddl_function_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_index_auth.groovy b/regression-test/suites/auth_call/test_ddl_index_auth.groovy index 53ba3a0d4b3123..61a727923be553 100644 --- a/regression-test/suites/auth_call/test_ddl_index_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_index_auth.groovy @@ -58,7 +58,7 @@ suite("test_ddl_index_auth","p0,auth_call") { logger.info("cluster:" + clusters) assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """create database ${dbName}""" sql """ diff --git a/regression-test/suites/auth_call/test_ddl_job_auth.groovy b/regression-test/suites/auth_call/test_ddl_job_auth.groovy index 45798191e48568..ef75802637757b 100644 --- a/regression-test/suites/auth_call/test_ddl_job_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_job_auth.groovy @@ -30,7 +30,7 @@ suite("test_ddl_job_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_mask_view_auth.groovy b/regression-test/suites/auth_call/test_ddl_mask_view_auth.groovy index 590e75781f19f2..4a8bc2787dc62c 100644 --- a/regression-test/suites/auth_call/test_ddl_mask_view_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_mask_view_auth.groovy @@ -29,7 +29,7 @@ suite("test_ddl_mask_view_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_mtmv_auth.groovy b/regression-test/suites/auth_call/test_ddl_mtmv_auth.groovy index 4db2177ee6ce18..160b6b840b019b 100644 --- a/regression-test/suites/auth_call/test_ddl_mtmv_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_mtmv_auth.groovy @@ -30,7 +30,7 @@ suite("test_ddl_mtmv_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_mv_auth.groovy b/regression-test/suites/auth_call/test_ddl_mv_auth.groovy index 0701abb8a8bdb8..4b4810604409fe 100644 --- a/regression-test/suites/auth_call/test_ddl_mv_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_mv_auth.groovy @@ -30,7 +30,7 @@ suite("test_ddl_mv_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_part_table_auth.groovy b/regression-test/suites/auth_call/test_ddl_part_table_auth.groovy index 34e4766e19e5e2..5217dc2d051860 100644 --- a/regression-test/suites/auth_call/test_ddl_part_table_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_part_table_auth.groovy @@ -28,7 +28,7 @@ suite("test_ddl_part_table_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_row_policy_auth.groovy b/regression-test/suites/auth_call/test_ddl_row_policy_auth.groovy index af1e074f8d7d8c..f9ac109f7f377f 100644 --- a/regression-test/suites/auth_call/test_ddl_row_policy_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_row_policy_auth.groovy @@ -29,7 +29,7 @@ suite("test_ddl_row_policy_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_sql_block_rule_auth.groovy b/regression-test/suites/auth_call/test_ddl_sql_block_rule_auth.groovy index 3941897a5e9dd5..568ea9723d58c3 100644 --- a/regression-test/suites/auth_call/test_ddl_sql_block_rule_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_sql_block_rule_auth.groovy @@ -29,7 +29,7 @@ suite("test_ddl_sql_block_rule_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_table_auth.groovy b/regression-test/suites/auth_call/test_ddl_table_auth.groovy index c96aeb0d5e2cad..47ac4e07abc6f9 100644 --- a/regression-test/suites/auth_call/test_ddl_table_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_table_auth.groovy @@ -33,7 +33,7 @@ suite("test_ddl_table_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_ddl_view_auth.groovy b/regression-test/suites/auth_call/test_ddl_view_auth.groovy index 05f263ada20066..1a915acdb3cd6f 100644 --- a/regression-test/suites/auth_call/test_ddl_view_auth.groovy +++ b/regression-test/suites/auth_call/test_ddl_view_auth.groovy @@ -29,7 +29,7 @@ suite("test_ddl_view_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_analyze_auth.groovy b/regression-test/suites/auth_call/test_dml_analyze_auth.groovy index 59706f140e69b1..8bc6a070d61524 100644 --- a/regression-test/suites/auth_call/test_dml_analyze_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_analyze_auth.groovy @@ -30,7 +30,7 @@ suite("test_dml_analyze_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_broker_load_auth.groovy b/regression-test/suites/auth_call/test_dml_broker_load_auth.groovy index 3ec26146699bce..0ea44241bfae2f 100644 --- a/regression-test/suites/auth_call/test_dml_broker_load_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_broker_load_auth.groovy @@ -42,7 +42,7 @@ suite("test_dml_broker_load_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_cancel_profile_auth.groovy b/regression-test/suites/auth_call/test_dml_cancel_profile_auth.groovy index f1aa5d0fa95105..9bcb95c2830d9f 100644 --- a/regression-test/suites/auth_call/test_dml_cancel_profile_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_cancel_profile_auth.groovy @@ -28,7 +28,7 @@ suite("test_dml_cancel_profile_auth","p0,auth_call,nonConcurrent") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_delete_table_auth.groovy b/regression-test/suites/auth_call/test_dml_delete_table_auth.groovy index d325250f2ce696..aec3ee4a9947e5 100644 --- a/regression-test/suites/auth_call/test_dml_delete_table_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_delete_table_auth.groovy @@ -30,7 +30,7 @@ suite("test_dml_delete_table_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_export_table_auth.groovy b/regression-test/suites/auth_call/test_dml_export_table_auth.groovy index 12812fdf0dde09..ee5674c940a35c 100644 --- a/regression-test/suites/auth_call/test_dml_export_table_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_export_table_auth.groovy @@ -36,7 +36,7 @@ suite("test_dml_export_table_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_insert_auth.groovy b/regression-test/suites/auth_call/test_dml_insert_auth.groovy index 6cfe66cb10c6d5..5b8a20e18f9f70 100644 --- a/regression-test/suites/auth_call/test_dml_insert_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_insert_auth.groovy @@ -31,7 +31,7 @@ suite("test_dml_insert_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_multi_routine_load_auth.groovy b/regression-test/suites/auth_call/test_dml_multi_routine_load_auth.groovy index 11fd6c43db4602..5b8ecef7e57eae 100644 --- a/regression-test/suites/auth_call/test_dml_multi_routine_load_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_multi_routine_load_auth.groovy @@ -34,7 +34,7 @@ suite("test_dml_multi_routine_load_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_mysql_load_auth.groovy b/regression-test/suites/auth_call/test_dml_mysql_load_auth.groovy index 3963fe9433ef45..97151a0d001e85 100644 --- a/regression-test/suites/auth_call/test_dml_mysql_load_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_mysql_load_auth.groovy @@ -30,7 +30,7 @@ suite("test_dml_mysql_load_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_outfile_auth.groovy b/regression-test/suites/auth_call/test_dml_outfile_auth.groovy index 7edf476883a93b..5f4096344b8624 100644 --- a/regression-test/suites/auth_call/test_dml_outfile_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_outfile_auth.groovy @@ -34,7 +34,7 @@ suite("test_dml_outfile_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_routine_load_auth.groovy b/regression-test/suites/auth_call/test_dml_routine_load_auth.groovy index db6698b01af194..025ac555356aa7 100644 --- a/regression-test/suites/auth_call/test_dml_routine_load_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_routine_load_auth.groovy @@ -33,7 +33,7 @@ suite("test_dml_routine_load_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_select_udf_auth.groovy b/regression-test/suites/auth_call/test_dml_select_udf_auth.groovy index b2f565fc02ef8a..6aa74784969e30 100644 --- a/regression-test/suites/auth_call/test_dml_select_udf_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_select_udf_auth.groovy @@ -34,7 +34,7 @@ suite("test_dml_select_udf_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_stream_load_auth.groovy b/regression-test/suites/auth_call/test_dml_stream_load_auth.groovy index 26ee5526f1ea69..02b0b59ff390f6 100644 --- a/regression-test/suites/auth_call/test_dml_stream_load_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_stream_load_auth.groovy @@ -29,7 +29,7 @@ suite("test_dml_stream_load_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_dml_update_table_auth.groovy b/regression-test/suites/auth_call/test_dml_update_table_auth.groovy index 153dad5026a930..eb258df2df2800 100644 --- a/regression-test/suites/auth_call/test_dml_update_table_auth.groovy +++ b/regression-test/suites/auth_call/test_dml_update_table_auth.groovy @@ -30,7 +30,7 @@ suite("test_dml_update_table_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_grant_show_view_priv_auth.groovy b/regression-test/suites/auth_call/test_grant_show_view_priv_auth.groovy index 3a736fe4703816..c05da39ce2e06c 100644 --- a/regression-test/suites/auth_call/test_grant_show_view_priv_auth.groovy +++ b/regression-test/suites/auth_call/test_grant_show_view_priv_auth.groovy @@ -32,7 +32,7 @@ suite("test_grant_show_view_priv_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_hive_base_case_auth.groovy b/regression-test/suites/auth_call/test_hive_base_case_auth.groovy index 271015bec7c154..0fb0d422ebd2c0 100644 --- a/regression-test/suites/auth_call/test_hive_base_case_auth.groovy +++ b/regression-test/suites/auth_call/test_hive_base_case_auth.groovy @@ -29,7 +29,7 @@ suite("test_hive_base_case_auth", "p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } String enabled = context.config.otherConfigs.get("enableHiveTest") diff --git a/regression-test/suites/auth_call/test_show_backend_auth.groovy b/regression-test/suites/auth_call/test_show_backend_auth.groovy index adccf3637e6e2a..13e1fa4fbe480c 100644 --- a/regression-test/suites/auth_call/test_show_backend_auth.groovy +++ b/regression-test/suites/auth_call/test_show_backend_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_backend_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_broker_auth.groovy b/regression-test/suites/auth_call/test_show_broker_auth.groovy index d8fa9bd10ebe01..a364ad5b33146d 100644 --- a/regression-test/suites/auth_call/test_show_broker_auth.groovy +++ b/regression-test/suites/auth_call/test_show_broker_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_broker_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_charset_auth.groovy b/regression-test/suites/auth_call/test_show_charset_auth.groovy index 3ca23f7f6becf7..14991bc99f9e38 100644 --- a/regression-test/suites/auth_call/test_show_charset_auth.groovy +++ b/regression-test/suites/auth_call/test_show_charset_auth.groovy @@ -27,7 +27,7 @@ suite("test_show_no_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_convert_light_sc_auth.groovy b/regression-test/suites/auth_call/test_show_convert_light_sc_auth.groovy index d54862f1710845..6f8387d892925b 100644 --- a/regression-test/suites/auth_call/test_show_convert_light_sc_auth.groovy +++ b/regression-test/suites/auth_call/test_show_convert_light_sc_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_convert_light_sc_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_create_table_auth.groovy b/regression-test/suites/auth_call/test_show_create_table_auth.groovy index 2a85ea42e6c8da..166179bae5f6be 100644 --- a/regression-test/suites/auth_call/test_show_create_table_auth.groovy +++ b/regression-test/suites/auth_call/test_show_create_table_auth.groovy @@ -28,7 +28,7 @@ suite("test_show_create_table_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_data_auth.groovy b/regression-test/suites/auth_call/test_show_data_auth.groovy index c6f3b6dd1536ba..951ba564d42fcf 100644 --- a/regression-test/suites/auth_call/test_show_data_auth.groovy +++ b/regression-test/suites/auth_call/test_show_data_auth.groovy @@ -29,7 +29,7 @@ suite("test_show_data_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_database_id_auth.groovy b/regression-test/suites/auth_call/test_show_database_id_auth.groovy index e30dc8d0db74a4..d9d131ee7793b4 100644 --- a/regression-test/suites/auth_call/test_show_database_id_auth.groovy +++ b/regression-test/suites/auth_call/test_show_database_id_auth.groovy @@ -28,7 +28,7 @@ suite("test_show_database_id_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_dynamic_table_auth.groovy b/regression-test/suites/auth_call/test_show_dynamic_table_auth.groovy index 727d705664311b..ad560fbf18f79f 100644 --- a/regression-test/suites/auth_call/test_show_dynamic_table_auth.groovy +++ b/regression-test/suites/auth_call/test_show_dynamic_table_auth.groovy @@ -28,7 +28,7 @@ suite("test_show_dynamic_table_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_frontend_auth.groovy b/regression-test/suites/auth_call/test_show_frontend_auth.groovy index f4a9bc15b074ec..87f8e9c7c09a68 100644 --- a/regression-test/suites/auth_call/test_show_frontend_auth.groovy +++ b/regression-test/suites/auth_call/test_show_frontend_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_frontend_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_grant_auth.groovy b/regression-test/suites/auth_call/test_show_grant_auth.groovy index 1223faf6739d4a..efd2e84e38a9f7 100644 --- a/regression-test/suites/auth_call/test_show_grant_auth.groovy +++ b/regression-test/suites/auth_call/test_show_grant_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_grant_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_proc_auth.groovy b/regression-test/suites/auth_call/test_show_proc_auth.groovy index 1daf8d97f703f2..4608c64ca67016 100644 --- a/regression-test/suites/auth_call/test_show_proc_auth.groovy +++ b/regression-test/suites/auth_call/test_show_proc_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_proc_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_query_stats_auth.groovy b/regression-test/suites/auth_call/test_show_query_stats_auth.groovy index ba951a47465ee3..7552038a7432b2 100644 --- a/regression-test/suites/auth_call/test_show_query_stats_auth.groovy +++ b/regression-test/suites/auth_call/test_show_query_stats_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_query_stats_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_tablet_auth.groovy b/regression-test/suites/auth_call/test_show_tablet_auth.groovy index 3ac938d8462228..eb73a7b906be11 100644 --- a/regression-test/suites/auth_call/test_show_tablet_auth.groovy +++ b/regression-test/suites/auth_call/test_show_tablet_auth.groovy @@ -28,7 +28,7 @@ suite("test_show_tablet_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_call/test_show_typecast_auth.groovy b/regression-test/suites/auth_call/test_show_typecast_auth.groovy index 8df4a2e2dc18c2..9e5d7186f01e17 100644 --- a/regression-test/suites/auth_call/test_show_typecast_auth.groovy +++ b/regression-test/suites/auth_call/test_show_typecast_auth.groovy @@ -26,7 +26,7 @@ suite("test_show_typecast_auth","p0,auth_call") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } try_sql("DROP USER ${user}") diff --git a/regression-test/suites/auth_p0/test_backends_auth.groovy b/regression-test/suites/auth_p0/test_backends_auth.groovy index 753ae837c776e9..db76b2740fccaf 100644 --- a/regression-test/suites/auth_p0/test_backends_auth.groovy +++ b/regression-test/suites/auth_p0/test_backends_auth.groovy @@ -29,7 +29,7 @@ suite("test_backends_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_catalogs_auth.groovy b/regression-test/suites/auth_p0/test_catalogs_auth.groovy index 96ebcef7cf81cb..1b67282d8fe206 100644 --- a/regression-test/suites/auth_p0/test_catalogs_auth.groovy +++ b/regression-test/suites/auth_p0/test_catalogs_auth.groovy @@ -36,7 +36,7 @@ suite("test_catalogs_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_frontends_auth.groovy b/regression-test/suites/auth_p0/test_frontends_auth.groovy index 21fff527518e2b..0ac96e5c653827 100644 --- a/regression-test/suites/auth_p0/test_frontends_auth.groovy +++ b/regression-test/suites/auth_p0/test_frontends_auth.groovy @@ -29,7 +29,7 @@ suite("test_frontends_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_frontends_disks_auth.groovy b/regression-test/suites/auth_p0/test_frontends_disks_auth.groovy index 3767fdde0a5e92..f46ead3256a52c 100644 --- a/regression-test/suites/auth_p0/test_frontends_disks_auth.groovy +++ b/regression-test/suites/auth_p0/test_frontends_disks_auth.groovy @@ -29,7 +29,7 @@ suite("test_frontends_disks_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_master_slave_consistency_auth.groovy b/regression-test/suites/auth_p0/test_master_slave_consistency_auth.groovy index 90228ebf3a5130..0fc6b3a4063142 100644 --- a/regression-test/suites/auth_p0/test_master_slave_consistency_auth.groovy +++ b/regression-test/suites/auth_p0/test_master_slave_consistency_auth.groovy @@ -110,7 +110,7 @@ suite ("test_follower_consistent_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } logger.info("url_tmp1:" + url_tmp1) diff --git a/regression-test/suites/auth_p0/test_mtmv_auth.groovy b/regression-test/suites/auth_p0/test_mtmv_auth.groovy index 52ecbebb70b268..a190edaa0224da 100644 --- a/regression-test/suites/auth_p0/test_mtmv_auth.groovy +++ b/regression-test/suites/auth_p0/test_mtmv_auth.groovy @@ -58,7 +58,7 @@ suite("test_mtmv_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_partition_values_tvf_auth.groovy b/regression-test/suites/auth_p0/test_partition_values_tvf_auth.groovy index 3f0ae7ea8d524c..b9ce9f4364619e 100644 --- a/regression-test/suites/auth_p0/test_partition_values_tvf_auth.groovy +++ b/regression-test/suites/auth_p0/test_partition_values_tvf_auth.groovy @@ -44,7 +44,7 @@ suite("test_partition_values_tvf_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_partitions_auth.groovy b/regression-test/suites/auth_p0/test_partitions_auth.groovy index 0b769f11567845..1a398b84b4e84d 100644 --- a/regression-test/suites/auth_p0/test_partitions_auth.groovy +++ b/regression-test/suites/auth_p0/test_partitions_auth.groovy @@ -48,7 +48,7 @@ suite("test_partitions_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_query_tvf_auth.groovy b/regression-test/suites/auth_p0/test_query_tvf_auth.groovy index 746eb47ce5b870..6353ca142a9067 100644 --- a/regression-test/suites/auth_p0/test_query_tvf_auth.groovy +++ b/regression-test/suites/auth_p0/test_query_tvf_auth.groovy @@ -48,7 +48,7 @@ suite("test_query_tvf_auth", "p0,auth,external,external_docker") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${dorisuser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${dorisuser}"""; } sql """grant select_priv on regression_test to ${dorisuser}""" diff --git a/regression-test/suites/auth_p0/test_select_column_auth.groovy b/regression-test/suites/auth_p0/test_select_column_auth.groovy index 1e93981966d2bc..296b47975fb707 100644 --- a/regression-test/suites/auth_p0/test_select_column_auth.groovy +++ b/regression-test/suites/auth_p0/test_select_column_auth.groovy @@ -37,7 +37,7 @@ suite("test_select_column_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """create database ${dbName}""" sql("""use ${dbName}""") diff --git a/regression-test/suites/auth_p0/test_select_count_auth.groovy b/regression-test/suites/auth_p0/test_select_count_auth.groovy index ccea1a4a580098..47a199aaca2291 100644 --- a/regression-test/suites/auth_p0/test_select_count_auth.groovy +++ b/regression-test/suites/auth_p0/test_select_count_auth.groovy @@ -29,7 +29,7 @@ suite("test_select_count_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/auth_p0/test_select_view_auth.groovy b/regression-test/suites/auth_p0/test_select_view_auth.groovy index 9a5020ea163ce6..6932fbbb58ded2 100644 --- a/regression-test/suites/auth_p0/test_select_view_auth.groovy +++ b/regression-test/suites/auth_p0/test_select_view_auth.groovy @@ -37,7 +37,7 @@ suite("test_select_view_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """create database ${dbName}""" sql("""use ${dbName}""") diff --git a/regression-test/suites/auth_p0/test_use_encryptkey_auth.groovy b/regression-test/suites/auth_p0/test_use_encryptkey_auth.groovy index 965bd4b3b162bb..258c1726ebfa62 100644 --- a/regression-test/suites/auth_p0/test_use_encryptkey_auth.groovy +++ b/regression-test/suites/auth_p0/test_use_encryptkey_auth.groovy @@ -32,7 +32,7 @@ suite("test_use_encryptkey_auth","p0,auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """CREATE ENCRYPTKEY ${key} AS 'ABCD123456789'""" diff --git a/regression-test/suites/auth_up_down_p0/load.groovy b/regression-test/suites/auth_up_down_p0/load.groovy index 7ac11b627abe9e..734293185897e5 100644 --- a/regression-test/suites/auth_up_down_p0/load.groovy +++ b/regression-test/suites/auth_up_down_p0/load.groovy @@ -50,8 +50,8 @@ suite("test_upgrade_downgrade_prepare_auth","p0,auth,restart_fe") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user1}"""; - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user2}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user1}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user2}"""; } try_sql """drop table if exists ${dbName}.${tableName1}""" diff --git a/regression-test/suites/external_table_p0/jdbc/test_jdbc_call.groovy b/regression-test/suites/external_table_p0/jdbc/test_jdbc_call.groovy index e9e00b7084fba0..7f4d7403c3d43a 100644 --- a/regression-test/suites/external_table_p0/jdbc/test_jdbc_call.groovy +++ b/regression-test/suites/external_table_p0/jdbc/test_jdbc_call.groovy @@ -118,8 +118,8 @@ suite("test_jdbc_call", "p0,external,doris,external_docker,external_docker_doris def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user1}"""; - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user2}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user1}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user2}"""; } def result1 = connect("${user1}", "", context.config.jdbcUrl) { diff --git a/regression-test/suites/external_table_p0/lower_case/test_conflict_name.groovy b/regression-test/suites/external_table_p0/lower_case/test_conflict_name.groovy index c8ed6c4cbbc888..92fe356dfd6c35 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_conflict_name.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_conflict_name.groovy @@ -32,7 +32,7 @@ suite("test_conflict_name", "p0,external,doris,meta_names_mapping,external_docke def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_include.groovy b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_include.groovy index 63f18e358c71bf..91a56f7f317a69 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_include.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_include.groovy @@ -32,7 +32,7 @@ suite("test_lower_case_meta_include", "p0,external,doris,external_docker,externa def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_show_and_select.groovy b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_show_and_select.groovy index 72e945ea8ffd59..8853d169a13bae 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_show_and_select.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_show_and_select.groovy @@ -48,7 +48,7 @@ suite("test_lower_case_meta_show_and_select", "p0,external,doris,external_docker def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_grant.groovy b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_grant.groovy index ab47a1e23bd63a..78baa9aa438452 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_grant.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_grant.groovy @@ -86,7 +86,7 @@ suite("test_lower_case_meta_with_lower_table_conf_auth", "p0,external,doris,exte def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" @@ -134,7 +134,7 @@ suite("test_lower_case_meta_with_lower_table_conf_auth", "p0,external,doris,exte def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_show_and_select.groovy b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_show_and_select.groovy index 42c6fd08203e4f..13750535628c44 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_show_and_select.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_lower_case_meta_with_lower_table_conf_show_and_select.groovy @@ -48,7 +48,7 @@ suite("test_lower_case_meta_with_lower_table_conf_show_and_select", "p0,external def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_lower_case_mtmv.groovy b/regression-test/suites/external_table_p0/lower_case/test_lower_case_mtmv.groovy index 4cc8593e459a6c..67081ed3afe12c 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_lower_case_mtmv.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_lower_case_mtmv.groovy @@ -32,7 +32,7 @@ suite("test_lower_case_mtmv", "p0,external,doris,external_docker,external_docker def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_meta_cache_select_without_refresh.groovy b/regression-test/suites/external_table_p0/lower_case/test_meta_cache_select_without_refresh.groovy index b677133015c996..c2073f9864cef6 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_meta_cache_select_without_refresh.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_meta_cache_select_without_refresh.groovy @@ -32,7 +32,7 @@ suite("test_meta_cache_select_without_refresh", "p0,external,doris,external_dock def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_meta_names_mapping.groovy b/regression-test/suites/external_table_p0/lower_case/test_meta_names_mapping.groovy index 1ae22e1ba99020..a033b7e59f8691 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_meta_names_mapping.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_meta_names_mapping.groovy @@ -32,7 +32,7 @@ suite("test_meta_names_mapping", "p0,external,doris,meta_names_mapping,external_ def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/test_timing_refresh_catalog.groovy b/regression-test/suites/external_table_p0/lower_case/test_timing_refresh_catalog.groovy index 5e0386330e3a51..a353e20d685074 100644 --- a/regression-test/suites/external_table_p0/lower_case/test_timing_refresh_catalog.groovy +++ b/regression-test/suites/external_table_p0/lower_case/test_timing_refresh_catalog.groovy @@ -53,7 +53,7 @@ suite("test_timing_refresh_catalog", "p0,external,doris,external_docker,external def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/lower_case/upgrade/load.groovy b/regression-test/suites/external_table_p0/lower_case/upgrade/load.groovy index 053fad17785a2f..8be76c3e0f44a4 100644 --- a/regression-test/suites/external_table_p0/lower_case/upgrade/load.groovy +++ b/regression-test/suites/external_table_p0/lower_case/upgrade/load.groovy @@ -48,7 +48,7 @@ suite("test_upgrade_lower_case_catalog_prepare", "p0,external,doris,external_doc def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${jdbcUser}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${jdbcUser}"""; } sql """grant all on *.*.* to ${jdbcUser}""" diff --git a/regression-test/suites/external_table_p0/tvf/test_insert_from_tvf_with_common_user.groovy b/regression-test/suites/external_table_p0/tvf/test_insert_from_tvf_with_common_user.groovy index 103158c224c13f..f0ae51430fd4dd 100644 --- a/regression-test/suites/external_table_p0/tvf/test_insert_from_tvf_with_common_user.groovy +++ b/regression-test/suites/external_table_p0/tvf/test_insert_from_tvf_with_common_user.groovy @@ -93,7 +93,7 @@ suite("test_insert_from_tvf_with_common_user", "p0,external,external_docker") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${common_user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${common_user}"""; } connect("${common_user}", '12345', context.config.jdbcUrl) { diff --git a/regression-test/suites/external_table_p0/tvf/test_s3_tvf_with_resource.groovy b/regression-test/suites/external_table_p0/tvf/test_s3_tvf_with_resource.groovy index 92091c18926bf9..1474ec0890b5fd 100644 --- a/regression-test/suites/external_table_p0/tvf/test_s3_tvf_with_resource.groovy +++ b/regression-test/suites/external_table_p0/tvf/test_s3_tvf_with_resource.groovy @@ -203,7 +203,7 @@ suite("test_s3_tvf_with_resource", "p0,external,external_docker") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } // not have usage priv, can not select tvf with resource connect(user, "${pwd}", url) { diff --git a/regression-test/suites/external_table_p2/tvf/test_iceberg_meta.groovy b/regression-test/suites/external_table_p2/tvf/test_iceberg_meta.groovy index 557eaf5b061d70..3fc898b865fb17 100644 --- a/regression-test/suites/external_table_p2/tvf/test_iceberg_meta.groovy +++ b/regression-test/suites/external_table_p2/tvf/test_iceberg_meta.groovy @@ -63,7 +63,7 @@ suite("test_iceberg_meta", "p2,external,iceberg,external_remote,external_remote_ def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """grant select_priv on regression_test to ${user}""" diff --git a/regression-test/suites/javaudf_p0/test_javaudf_auth.groovy b/regression-test/suites/javaudf_p0/test_javaudf_auth.groovy index 0729f14bb333f1..7f02c9218550ab 100644 --- a/regression-test/suites/javaudf_p0/test_javaudf_auth.groovy +++ b/regression-test/suites/javaudf_p0/test_javaudf_auth.groovy @@ -49,7 +49,7 @@ suite("test_javaudf_auth") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql """USE ${dbName}""" diff --git a/regression-test/suites/load_p0/routine_load/test_routine_load_with_user.groovy b/regression-test/suites/load_p0/routine_load/test_routine_load_with_user.groovy index 73cce57822fab3..73641f46d018c2 100644 --- a/regression-test/suites/load_p0/routine_load/test_routine_load_with_user.groovy +++ b/regression-test/suites/load_p0/routine_load/test_routine_load_with_user.groovy @@ -65,7 +65,7 @@ suite("test_routine_load_with_user","p0") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; try { def storageVaults = (sql " SHOW STORAGE VAULT; ").stream().map(row -> row[0]).collect(Collectors.toSet()) diff --git a/regression-test/suites/load_p0/stream_load/test_stream_load.groovy b/regression-test/suites/load_p0/stream_load/test_stream_load.groovy index 54731a949584be..1bbf6033ef8ca0 100644 --- a/regression-test/suites/load_p0/stream_load/test_stream_load.groovy +++ b/regression-test/suites/load_p0/stream_load/test_stream_load.groovy @@ -1059,7 +1059,7 @@ suite("test_stream_load", "p0") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO common_user"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO common_user"""; } streamLoad { diff --git a/regression-test/suites/load_p0/stream_load/test_stream_load_move_memtable.groovy b/regression-test/suites/load_p0/stream_load/test_stream_load_move_memtable.groovy index bbd532a76259cc..1df859d03d144a 100644 --- a/regression-test/suites/load_p0/stream_load/test_stream_load_move_memtable.groovy +++ b/regression-test/suites/load_p0/stream_load/test_stream_load_move_memtable.groovy @@ -937,7 +937,7 @@ suite("test_stream_load_move_memtable", "p0") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ddd"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ddd"""; } streamLoad { diff --git a/regression-test/suites/manager/test_manager_interface_3.groovy b/regression-test/suites/manager/test_manager_interface_3.groovy index 22af98d5648ca7..b7baa8a869d94a 100644 --- a/regression-test/suites/manager/test_manager_interface_3.groovy +++ b/regression-test/suites/manager/test_manager_interface_3.groovy @@ -89,8 +89,8 @@ suite('test_manager_interface_3',"p0") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user1}"""; - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user2}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user1}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user2}"""; } connect(user1, "${pwd}", url) { @@ -412,7 +412,7 @@ suite('test_manager_interface_3',"p0") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } List> result = sql """ show resources """ @@ -609,7 +609,7 @@ suite('test_manager_interface_3',"p0") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } connect(user, "${pwd}", url) { diff --git a/regression-test/suites/nereids_p0/authorization/column_authorization.groovy b/regression-test/suites/nereids_p0/authorization/column_authorization.groovy index c2e22f10c22678..eea353368c9684 100644 --- a/regression-test/suites/nereids_p0/authorization/column_authorization.groovy +++ b/regression-test/suites/nereids_p0/authorization/column_authorization.groovy @@ -49,7 +49,7 @@ suite("column_authorization") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user1}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user1}"""; } sql 'sync' diff --git a/regression-test/suites/nereids_p0/authorization/view_authorization.groovy b/regression-test/suites/nereids_p0/authorization/view_authorization.groovy index 51503c3cd2d737..fa7f56c1e09f0b 100644 --- a/regression-test/suites/nereids_p0/authorization/view_authorization.groovy +++ b/regression-test/suites/nereids_p0/authorization/view_authorization.groovy @@ -52,7 +52,7 @@ suite("view_authorization") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user1}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user1}"""; } sql 'sync' diff --git a/regression-test/suites/nereids_p0/cache/parse_sql_from_sql_cache.groovy b/regression-test/suites/nereids_p0/cache/parse_sql_from_sql_cache.groovy index e7fb5f3da6c435..765d1208426607 100644 --- a/regression-test/suites/nereids_p0/cache/parse_sql_from_sql_cache.groovy +++ b/regression-test/suites/nereids_p0/cache/parse_sql_from_sql_cache.groovy @@ -297,7 +297,7 @@ suite("parse_sql_from_sql_cache") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO test_cache_user1""" + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_cache_user1""" } createTestTable "test_use_plan_cache12" @@ -340,7 +340,7 @@ suite("parse_sql_from_sql_cache") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO test_cache_user2""" + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_cache_user2""" } createTestTable "test_use_plan_cache13" @@ -397,7 +397,7 @@ suite("parse_sql_from_sql_cache") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO test_cache_user3""" + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_cache_user3""" } createTestTable "test_use_plan_cache14" @@ -460,7 +460,7 @@ suite("parse_sql_from_sql_cache") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO test_cache_user4""" + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_cache_user4""" } sql "sync" diff --git a/regression-test/suites/nereids_p0/insert_into_table/insert_auth.groovy b/regression-test/suites/nereids_p0/insert_into_table/insert_auth.groovy index 4c9968746d13c1..d146885a7bf936 100644 --- a/regression-test/suites/nereids_p0/insert_into_table/insert_auth.groovy +++ b/regression-test/suites/nereids_p0/insert_into_table/insert_auth.groovy @@ -53,7 +53,7 @@ suite('nereids_insert_auth') { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } connect(user, "${pwd}", url) { diff --git a/regression-test/suites/query_p0/system/test_partitions_schema.groovy b/regression-test/suites/query_p0/system/test_partitions_schema.groovy index 98b12c3705c777..84bbeafa7fe349 100644 --- a/regression-test/suites/query_p0/system/test_partitions_schema.groovy +++ b/regression-test/suites/query_p0/system/test_partitions_schema.groovy @@ -176,7 +176,7 @@ suite("test_partitions_schema") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql "GRANT SELECT_PRIV ON information_schema.partitions TO ${user}" diff --git a/regression-test/suites/query_p0/system/test_table_options.groovy b/regression-test/suites/query_p0/system/test_table_options.groovy index fef118a82b280b..fd9cffcedfcad8 100644 --- a/regression-test/suites/query_p0/system/test_table_options.groovy +++ b/regression-test/suites/query_p0/system/test_table_options.groovy @@ -191,7 +191,7 @@ suite("test_table_options") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql "GRANT SELECT_PRIV ON information_schema.table_properties TO ${user}" diff --git a/regression-test/suites/query_p0/system/test_table_properties.groovy b/regression-test/suites/query_p0/system/test_table_properties.groovy index 1861ae4d6280d6..7465497ae76d08 100644 --- a/regression-test/suites/query_p0/system/test_table_properties.groovy +++ b/regression-test/suites/query_p0/system/test_table_properties.groovy @@ -98,7 +98,7 @@ suite("test_table_properties") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } sql "GRANT SELECT_PRIV ON information_schema.table_properties TO ${user}" diff --git a/regression-test/suites/query_p0/test_row_policy.groovy b/regression-test/suites/query_p0/test_row_policy.groovy index 4af498e55e3b46..c416537df15eac 100644 --- a/regression-test/suites/query_p0/test_row_policy.groovy +++ b/regression-test/suites/query_p0/test_row_policy.groovy @@ -34,7 +34,7 @@ suite("test_row_policy") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO ${user}"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO ${user}"""; } connect(user, '123456', url) { diff --git a/regression-test/suites/workload_manager_p0/test_curd_wlg.groovy b/regression-test/suites/workload_manager_p0/test_curd_wlg.groovy index 56a0d7ddbf1f61..ae836fb0c65297 100644 --- a/regression-test/suites/workload_manager_p0/test_curd_wlg.groovy +++ b/regression-test/suites/workload_manager_p0/test_curd_wlg.groovy @@ -348,7 +348,7 @@ suite("test_crud_wlg") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO test_wlg_user"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_wlg_user"""; } connect('test_wlg_user', '12345', context.config.jdbcUrl) { @@ -739,7 +739,7 @@ suite("test_crud_wlg") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO test_wg_priv_user2"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_wg_priv_user2"""; } connect('test_wg_priv_user2', '', context.config.jdbcUrl) { qt_select_wgp_11 "select GRANTEE,WORKLOAD_GROUP_NAME,PRIVILEGE_TYPE,IS_GRANTABLE from information_schema.workload_group_privileges where grantee like '%test_wg_priv%' order by GRANTEE,WORKLOAD_GROUP_NAME,PRIVILEGE_TYPE,IS_GRANTABLE; " diff --git a/regression-test/suites/workload_manager_p0/test_resource_tag.groovy b/regression-test/suites/workload_manager_p0/test_resource_tag.groovy index fa7ba680143248..d643155c0eaeb2 100644 --- a/regression-test/suites/workload_manager_p0/test_resource_tag.groovy +++ b/regression-test/suites/workload_manager_p0/test_resource_tag.groovy @@ -25,7 +25,7 @@ suite("test_resource_tag") { def clusters = sql " SHOW CLUSTERS; " assertTrue(!clusters.isEmpty()) def validCluster = clusters[0][0] - sql """GRANT USAGE_PRIV ON CLUSTER ${validCluster} TO test_rg"""; + sql """GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO test_rg"""; } // test query From 91d3a52f77584e3589fe0d7645b42a51d19d67fb Mon Sep 17 00:00:00 2001 From: huanghaibin Date: Thu, 16 Jan 2025 09:24:50 +0800 Subject: [PATCH 04/26] [fix](cloud-mow) Fix the issue of inaccurate estimation of txn size when updating delete bitmap (#46969) fdb_txn_size is not key size+value size, actually txn size is the triple of key size plus value size at least for writing, so use txn->approximate_bytes() to calculating txn size is more accurate. However, txn->approximate_bytes() is not 100% accurate either, in my test, when txn->approximate_bytes() bingger than 8.3MB, it will meet Transaction exceeds byte limit error. --- cloud/src/common/config.h | 9 ++++ cloud/src/meta-service/meta_service.cpp | 53 +++++++++++++++++------- cloud/test/meta_service_test.cpp | 55 +++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 14 deletions(-) diff --git a/cloud/src/common/config.h b/cloud/src/common/config.h index 78d3358b581ed9..1e58f0d4d74037 100644 --- a/cloud/src/common/config.h +++ b/cloud/src/common/config.h @@ -229,6 +229,15 @@ CONF_mInt64(max_s3_client_retry, "10"); // Max byte getting delete bitmap can return, default is 1GB CONF_mInt64(max_get_delete_bitmap_byte, "1073741824"); +// Max byte txn commit when updating delete bitmap, default is 7MB. +// Because the size of one fdb transaction can't exceed 10MB, and +// fdb does not have an accurate way to estimate the size of txn. +// In my test, when txn->approximate_bytes() bigger than 8MB, +// it may meet Transaction exceeds byte limit error. We'd better +// reserve 1MB of buffer, so setting the default value to 7MB is +// more reasonable. +CONF_mInt64(max_txn_commit_byte, "7340032"); + CONF_Bool(enable_cloud_txn_lazy_commit, "true"); CONF_Int32(txn_lazy_commit_rowsets_thresold, "1000"); CONF_Int32(txn_lazy_commit_num_threads, "8"); diff --git a/cloud/src/meta-service/meta_service.cpp b/cloud/src/meta-service/meta_service.cpp index 17154a24777905..7914bf5db11cf6 100644 --- a/cloud/src/meta-service/meta_service.cpp +++ b/cloud/src/meta-service/meta_service.cpp @@ -1851,25 +1851,40 @@ void MetaServiceImpl::update_delete_bitmap(google::protobuf::RpcController* cont } // 4. Update delete bitmap for curent txn - size_t total_key = 0; - size_t total_size = 0; + size_t current_key_count = 0; + size_t current_value_count = 0; + size_t total_key_count = 0; + size_t total_value_count = 0; + size_t total_txn_put_keys = 0; + size_t total_txn_put_bytes = 0; + size_t total_txn_size = 0; for (size_t i = 0; i < request->rowset_ids_size(); ++i) { auto& key = delete_bitmap_keys.delete_bitmap_keys(i); auto& val = request->segment_delete_bitmaps(i); // Split into multiple fdb transactions, because the size of one fdb // transaction can't exceed 10MB. - if (fdb_txn_size + key.size() + val.size() > 9 * 1024 * 1024) { - LOG(INFO) << "fdb txn size more than 9MB, current size: " << fdb_txn_size - << " lock_id=" << request->lock_id(); + if (txn->approximate_bytes() + key.size() * 3 + val.size() > config::max_txn_commit_byte) { + LOG(INFO) << "fdb txn size more than " << config::max_txn_commit_byte + << ", current size: " << txn->approximate_bytes() + << " lock_id=" << request->lock_id() << ", need to commit"; err = txn->commit(); + total_txn_put_keys += txn->num_put_keys(); + total_txn_put_bytes += txn->put_bytes(); + total_txn_size += txn->approximate_bytes(); if (err != TxnErrorCode::TXN_OK) { code = cast_as(err); - ss << "failed to update delete bitmap, err=" << err; + ss << "failed to update delete bitmap, err=" << err << " tablet_id=" << tablet_id + << " lock_id=" << request->lock_id() + << " delete_bitmap_key=" << current_key_count + << " delete_bitmap_value=" << current_value_count + << " put_size=" << txn->put_bytes() << " num_put_keys=" << txn->num_put_keys() + << " txn_size=" << txn->approximate_bytes(); msg = ss.str(); return; } - fdb_txn_size = 0; + current_key_count = 0; + current_value_count = 0; TxnErrorCode err = txn_kv_->create_txn(&txn); if (err != TxnErrorCode::TXN_OK) { code = cast_as(err); @@ -1888,24 +1903,34 @@ void MetaServiceImpl::update_delete_bitmap(google::protobuf::RpcController* cont } // splitting large values (>90*1000) into multiple KVs cloud::put(txn.get(), key, val, 0); - fdb_txn_size = fdb_txn_size + key.size() + val.size(); - total_key++; - total_size += key.size() + val.size(); + current_key_count++; + current_value_count += val.size(); + total_key_count++; + total_value_count += val.size(); VLOG_DEBUG << "xxx update delete bitmap put delete_bitmap_key=" << hex(key) << " lock_id=" << request->lock_id() << " key_size: " << key.size() << " value_size: " << val.size(); } - err = txn->commit(); + total_txn_put_keys += txn->num_put_keys(); + total_txn_put_bytes += txn->put_bytes(); + total_txn_size += txn->approximate_bytes(); if (err != TxnErrorCode::TXN_OK) { code = cast_as(err); - ss << "failed to update delete bitmap, err=" << err; + ss << "failed to update delete bitmap, err=" << err << " tablet_id=" << tablet_id + << " lock_id=" << request->lock_id() << " delete_bitmap_key=" << current_key_count + << " delete_bitmap_value=" << current_value_count << " put_size=" << txn->put_bytes() + << " num_put_keys=" << txn->num_put_keys() << " txn_size=" << txn->approximate_bytes(); msg = ss.str(); return; } LOG(INFO) << "update_delete_bitmap tablet_id=" << tablet_id << " lock_id=" << request->lock_id() - << " rowset_num=" << request->rowset_ids_size() << " total_key=" << total_key - << " total_size=" << total_size << " unlock=" << unlock; + << " rowset_num=" << request->rowset_ids_size() + << " total_key_count=" << total_key_count + << " total_value_count=" << total_value_count << " unlock=" << unlock + << " total_txn_put_keys=" << total_txn_put_keys + << " total_txn_put_bytes=" << total_txn_put_bytes + << " total_txn_size=" << total_txn_size; } void MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* controller, diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp index b7004716035a46..fb17c29629b04e 100644 --- a/cloud/test/meta_service_test.cpp +++ b/cloud/test/meta_service_test.cpp @@ -104,6 +104,24 @@ std::unique_ptr get_meta_service() { return get_meta_service(true); } +std::unique_ptr get_fdb_meta_service() { + config::fdb_cluster_file_path = "fdb.cluster"; + static auto txn_kv = std::dynamic_pointer_cast(std::make_shared()); + static std::atomic init {false}; + bool tmp = false; + if (init.compare_exchange_strong(tmp, true)) { + int ret = txn_kv->init(); + [&] { + ASSERT_EQ(ret, 0); + ASSERT_NE(txn_kv.get(), nullptr); + }(); + } + auto rs = std::make_shared(txn_kv); + auto rl = std::make_shared(); + auto meta_service = std::make_unique(txn_kv, rs, rl); + return std::make_unique(std::move(meta_service)); +} + static std::string next_rowset_id() { static int cnt = 0; return std::to_string(++cnt); @@ -4857,6 +4875,43 @@ static std::string generate_random_string(int length) { return randomString; } +TEST(MetaServiceTest, UpdateDeleteBitmapWithBigKeys) { + auto meta_service = get_fdb_meta_service(); + // get delete bitmap update lock + brpc::Controller cntl; + GetDeleteBitmapUpdateLockRequest get_lock_req; + GetDeleteBitmapUpdateLockResponse get_lock_res; + get_lock_req.set_cloud_unique_id("test_cloud_unique_id"); + get_lock_req.set_table_id(1999); + get_lock_req.add_partition_ids(123); + get_lock_req.set_expiration(5); + get_lock_req.set_lock_id(-1); + get_lock_req.set_initiator(100); + meta_service->get_delete_bitmap_update_lock( + reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req, + &get_lock_res, nullptr); + ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK); + UpdateDeleteBitmapRequest update_delete_bitmap_req; + UpdateDeleteBitmapResponse update_delete_bitmap_res; + update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id"); + update_delete_bitmap_req.set_table_id(1999); + update_delete_bitmap_req.set_partition_id(123); + update_delete_bitmap_req.set_lock_id(-1); + update_delete_bitmap_req.set_initiator(100); + update_delete_bitmap_req.set_tablet_id(333); + std::string large_value = generate_random_string(300 * 1000 * 3); + for (int i = 0; i < 100000; i++) { + update_delete_bitmap_req.add_rowset_ids("0200000003ea308a3647dbea83220ed4b8897f2288244a91"); + update_delete_bitmap_req.add_segment_ids(0); + update_delete_bitmap_req.add_versions(i); + update_delete_bitmap_req.add_segment_delete_bitmaps("1"); + } + meta_service->update_delete_bitmap(reinterpret_cast(&cntl), + &update_delete_bitmap_req, &update_delete_bitmap_res, + nullptr); + ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK); +} + TEST(MetaServiceTest, UpdateDeleteBitmap) { auto meta_service = get_meta_service(); From d542818fbcbdffb94e44d2f1dc20f0b34f1be95a Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 16 Jan 2025 10:12:04 +0800 Subject: [PATCH 05/26] [UT](runtime filter) Add runtime filter test case (#47035) --- be/src/exprs/runtime_filter.cpp | 1343 ++++++++--------- be/src/exprs/runtime_filter.h | 111 ++ be/src/exprs/runtime_filter_slots.h | 54 +- be/src/pipeline/exec/operator.cpp | 2 +- be/src/pipeline/pipeline_fragment_context.cpp | 3 +- be/src/pipeline/pipeline_fragment_context.h | 3 +- be/src/pipeline/task_queue.h | 9 +- be/test/pipeline/dummy_task_queue.h | 50 + be/test/pipeline/pipeline_test.cpp | 276 +++- be/test/pipeline/thrift_builder.h | 106 +- gensrc/thrift/PaloInternalService.thrift | 5 +- gensrc/thrift/PlanNodes.thrift | 2 +- 12 files changed, 1174 insertions(+), 790 deletions(-) create mode 100644 be/test/pipeline/dummy_task_queue.h diff --git a/be/src/exprs/runtime_filter.cpp b/be/src/exprs/runtime_filter.cpp index b85f370165a3de..33cc155fc6fba8 100644 --- a/be/src/exprs/runtime_filter.cpp +++ b/be/src/exprs/runtime_filter.cpp @@ -283,711 +283,6 @@ Status create_vbin_predicate(const TypeDescriptor& type, TExprOpcode::type opcod *tnode = node; return vectorized::VExpr::create_expr(node, expr); } -// This class is a wrapper of runtime predicate function -class RuntimePredicateWrapper { -public: - RuntimePredicateWrapper(const RuntimeFilterParams* params) - : RuntimePredicateWrapper(params->column_return_type, params->filter_type, - params->filter_id) {}; - // for a 'tmp' runtime predicate wrapper - // only could called assign method or as a param for merge - RuntimePredicateWrapper(PrimitiveType column_type, RuntimeFilterType type, uint32_t filter_id) - : _column_return_type(column_type), - _filter_type(type), - _context(new RuntimeFilterContext()), - _filter_id(filter_id) {} - - // init runtime filter wrapper - // alloc memory to init runtime filter function - Status init(const RuntimeFilterParams* params) { - _max_in_num = params->max_in_num; - switch (_filter_type) { - case RuntimeFilterType::IN_FILTER: { - _context->hybrid_set.reset(create_set(_column_return_type)); - _context->hybrid_set->set_null_aware(params->null_aware); - break; - } - // Only use in nested loop join not need set null aware - case RuntimeFilterType::MIN_FILTER: - case RuntimeFilterType::MAX_FILTER: { - _context->minmax_func.reset(create_minmax_filter(_column_return_type)); - break; - } - case RuntimeFilterType::MINMAX_FILTER: { - _context->minmax_func.reset(create_minmax_filter(_column_return_type)); - _context->minmax_func->set_null_aware(params->null_aware); - break; - } - case RuntimeFilterType::BLOOM_FILTER: { - _context->bloom_filter_func.reset(create_bloom_filter(_column_return_type)); - _context->bloom_filter_func->init_params(params); - return Status::OK(); - } - case RuntimeFilterType::IN_OR_BLOOM_FILTER: { - _context->hybrid_set.reset(create_set(_column_return_type)); - _context->hybrid_set->set_null_aware(params->null_aware); - _context->bloom_filter_func.reset(create_bloom_filter(_column_return_type)); - _context->bloom_filter_func->init_params(params); - return Status::OK(); - } - case RuntimeFilterType::BITMAP_FILTER: { - _context->bitmap_filter_func.reset(create_bitmap_filter(_column_return_type)); - _context->bitmap_filter_func->set_not_in(params->bitmap_filter_not_in); - return Status::OK(); - } - default: - return Status::InternalError("Unknown Filter type"); - } - return Status::OK(); - } - - Status change_to_bloom_filter() { - if (_filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER) { - return Status::InternalError( - "Can not change to bloom filter because of runtime filter type is {}", - IRuntimeFilter::to_string(_filter_type)); - } - BloomFilterFuncBase* bf = _context->bloom_filter_func.get(); - - if (bf != nullptr) { - insert_to_bloom_filter(bf); - } else if (_context->hybrid_set != nullptr && _context->hybrid_set->size() != 0) { - return Status::InternalError("change to bloom filter need empty set ", - IRuntimeFilter::to_string(_filter_type)); - } - - // release in filter - _context->hybrid_set.reset(); - return Status::OK(); - } - - Status init_bloom_filter(const size_t build_bf_cardinality) { - if (_filter_type != RuntimeFilterType::BLOOM_FILTER && - _filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER) { - throw Exception(ErrorCode::INTERNAL_ERROR, - "init_bloom_filter meet invalid input type {}", int(_filter_type)); - } - return _context->bloom_filter_func->init_with_cardinality(build_bf_cardinality); - } - - bool get_build_bf_cardinality() const { - if (_filter_type == RuntimeFilterType::BLOOM_FILTER || - _filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER) { - return _context->bloom_filter_func->get_build_bf_cardinality(); - } - return false; - } - - void insert_to_bloom_filter(BloomFilterFuncBase* bloom_filter) const { - if (_context->hybrid_set->size() > 0) { - auto* it = _context->hybrid_set->begin(); - while (it->has_next()) { - bloom_filter->insert(it->get_value()); - it->next(); - } - } - if (_context->hybrid_set->contain_null()) { - bloom_filter->set_contain_null_and_null_aware(); - } - } - - BloomFilterFuncBase* get_bloomfilter() const { return _context->bloom_filter_func.get(); } - - void insert_fixed_len(const vectorized::ColumnPtr& column, size_t start) { - if (is_ignored()) { - throw Exception(ErrorCode::INTERNAL_ERROR, "insert_fixed_len meet ignored rf"); - } - switch (_filter_type) { - case RuntimeFilterType::IN_FILTER: { - _context->hybrid_set->insert_fixed_len(column, start); - break; - } - case RuntimeFilterType::MIN_FILTER: - case RuntimeFilterType::MAX_FILTER: - case RuntimeFilterType::MINMAX_FILTER: { - _context->minmax_func->insert_fixed_len(column, start); - break; - } - case RuntimeFilterType::BLOOM_FILTER: { - _context->bloom_filter_func->insert_fixed_len(column, start); - break; - } - case RuntimeFilterType::IN_OR_BLOOM_FILTER: { - if (is_bloomfilter()) { - _context->bloom_filter_func->insert_fixed_len(column, start); - } else { - _context->hybrid_set->insert_fixed_len(column, start); - } - break; - } - default: - DCHECK(false); - break; - } - } - - void insert_batch(const vectorized::ColumnPtr& column, size_t start) { - if (get_real_type() == RuntimeFilterType::BITMAP_FILTER) { - bitmap_filter_insert_batch(column, start); - } else { - insert_fixed_len(column, start); - } - } - - void bitmap_filter_insert_batch(const vectorized::ColumnPtr column, size_t start) { - std::vector bitmaps; - if (column->is_nullable()) { - const auto* nullable = assert_cast(column.get()); - const auto& col = - assert_cast(nullable->get_nested_column()); - const auto& nullmap = - assert_cast(nullable->get_null_map_column()) - .get_data(); - for (size_t i = start; i < column->size(); i++) { - if (!nullmap[i]) { - bitmaps.push_back(&(col.get_data()[i])); - } - } - } else { - const auto* col = assert_cast(column.get()); - for (size_t i = start; i < column->size(); i++) { - bitmaps.push_back(&(col->get_data()[i])); - } - } - _context->bitmap_filter_func->insert_many(bitmaps); - } - - RuntimeFilterType get_real_type() const { - if (_filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER) { - if (_context->hybrid_set) { - return RuntimeFilterType::IN_FILTER; - } - return RuntimeFilterType::BLOOM_FILTER; - } - return _filter_type; - } - - size_t get_bloom_filter_size() const { - return _context->bloom_filter_func ? _context->bloom_filter_func->get_size() : 0; - } - - Status get_push_exprs(std::list& probe_ctxs, - std::vector& push_exprs, - const TExpr& probe_expr); - - Status merge(const RuntimePredicateWrapper* wrapper) { - if (wrapper->is_disabled()) { - set_disabled(); - return Status::OK(); - } - - if (wrapper->is_ignored() || is_disabled()) { - return Status::OK(); - } - - _context->ignored = false; - - bool can_not_merge_in_or_bloom = - _filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER && - (wrapper->_filter_type != RuntimeFilterType::IN_FILTER && - wrapper->_filter_type != RuntimeFilterType::BLOOM_FILTER && - wrapper->_filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER); - - bool can_not_merge_other = _filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER && - _filter_type != wrapper->_filter_type; - - CHECK(!can_not_merge_in_or_bloom && !can_not_merge_other) - << " can not merge runtime filter(id=" << _filter_id - << "), current is filter type is " << IRuntimeFilter::to_string(_filter_type) - << ", other filter type is " << IRuntimeFilter::to_string(wrapper->_filter_type); - - switch (_filter_type) { - case RuntimeFilterType::IN_FILTER: { - _context->hybrid_set->insert(wrapper->_context->hybrid_set.get()); - if (_max_in_num >= 0 && _context->hybrid_set->size() >= _max_in_num) { - set_disabled(); - } - break; - } - case RuntimeFilterType::MIN_FILTER: - case RuntimeFilterType::MAX_FILTER: - case RuntimeFilterType::MINMAX_FILTER: { - RETURN_IF_ERROR(_context->minmax_func->merge(wrapper->_context->minmax_func.get())); - break; - } - case RuntimeFilterType::BLOOM_FILTER: { - RETURN_IF_ERROR( - _context->bloom_filter_func->merge(wrapper->_context->bloom_filter_func.get())); - break; - } - case RuntimeFilterType::IN_OR_BLOOM_FILTER: { - auto real_filter_type = get_real_type(); - - auto other_filter_type = wrapper->_filter_type; - if (other_filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER) { - other_filter_type = wrapper->get_real_type(); - } - - if (real_filter_type == RuntimeFilterType::IN_FILTER) { - // when we meet base rf is in-filter, threre only have two case: - // case1: all input-filter's build_bf_exactly is true, inited by synced global size - // case2: all input-filter's build_bf_exactly is false, inited by default size - if (other_filter_type == RuntimeFilterType::IN_FILTER) { - _context->hybrid_set->insert(wrapper->_context->hybrid_set.get()); - if (_max_in_num >= 0 && _context->hybrid_set->size() >= _max_in_num) { - // case2: use default size to init bf - RETURN_IF_ERROR(_context->bloom_filter_func->init_with_fixed_length()); - RETURN_IF_ERROR(change_to_bloom_filter()); - } - } else { - // case1&case2: use input bf directly and insert hybrid set data into bf - _context->bloom_filter_func = wrapper->_context->bloom_filter_func; - RETURN_IF_ERROR(change_to_bloom_filter()); - } - } else { - if (other_filter_type == RuntimeFilterType::IN_FILTER) { - // case2: insert data to global filter - wrapper->insert_to_bloom_filter(_context->bloom_filter_func.get()); - } else { - // case1&case2: all input bf must has same size - RETURN_IF_ERROR(_context->bloom_filter_func->merge( - wrapper->_context->bloom_filter_func.get())); - } - } - break; - } - case RuntimeFilterType::BITMAP_FILTER: { - // use input bitmap directly because we assume bitmap filter join always have full data - _context->bitmap_filter_func = wrapper->_context->bitmap_filter_func; - break; - } - default: - return Status::InternalError("unknown runtime filter"); - } - return Status::OK(); - } - - Status assign(const PInFilter* in_filter, bool contain_null) { - _context->hybrid_set.reset(create_set(_column_return_type)); - if (contain_null) { - _context->hybrid_set->set_null_aware(true); - _context->hybrid_set->insert((const void*)nullptr); - } - - switch (_column_return_type) { - case TYPE_BOOLEAN: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - bool bool_val = column.boolval(); - set->insert(&bool_val); - }); - break; - } - case TYPE_TINYINT: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto int_val = static_cast(column.intval()); - set->insert(&int_val); - }); - break; - } - case TYPE_SMALLINT: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto int_val = static_cast(column.intval()); - set->insert(&int_val); - }); - break; - } - case TYPE_INT: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - int32_t int_val = column.intval(); - set->insert(&int_val); - }); - break; - } - case TYPE_BIGINT: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - int64_t long_val = column.longval(); - set->insert(&long_val); - }); - break; - } - case TYPE_LARGEINT: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto string_val = column.stringval(); - StringParser::ParseResult result; - auto int128_val = StringParser::string_to_int( - string_val.c_str(), string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - set->insert(&int128_val); - }); - break; - } - case TYPE_FLOAT: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto float_val = static_cast(column.doubleval()); - set->insert(&float_val); - }); - break; - } - case TYPE_DOUBLE: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - double double_val = column.doubleval(); - set->insert(&double_val); - }); - break; - } - case TYPE_DATEV2: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto date_v2_val = column.intval(); - set->insert(&date_v2_val); - }); - break; - } - case TYPE_DATETIMEV2: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto date_v2_val = column.longval(); - set->insert(&date_v2_val); - }); - break; - } - case TYPE_DATETIME: - case TYPE_DATE: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - const auto& string_val_ref = column.stringval(); - VecDateTimeValue datetime_val; - datetime_val.from_date_str(string_val_ref.c_str(), string_val_ref.length()); - set->insert(&datetime_val); - }); - break; - } - case TYPE_DECIMALV2: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - const auto& string_val_ref = column.stringval(); - DecimalV2Value decimal_val(string_val_ref); - set->insert(&decimal_val); - }); - break; - } - case TYPE_DECIMAL32: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - int32_t decimal_32_val = column.intval(); - set->insert(&decimal_32_val); - }); - break; - } - case TYPE_DECIMAL64: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - int64_t decimal_64_val = column.longval(); - set->insert(&decimal_64_val); - }); - break; - } - case TYPE_DECIMAL128I: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto string_val = column.stringval(); - StringParser::ParseResult result; - auto int128_val = StringParser::string_to_int( - string_val.c_str(), string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - set->insert(&int128_val); - }); - break; - } - case TYPE_DECIMAL256: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto string_val = column.stringval(); - StringParser::ParseResult result; - auto int_val = StringParser::string_to_int( - string_val.c_str(), string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - set->insert(&int_val); - }); - break; - } - case TYPE_VARCHAR: - case TYPE_CHAR: - case TYPE_STRING: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - const std::string& string_value = column.stringval(); - // string_value is std::string, call insert(data, size) function in StringSet will not cast as StringRef - // so could avoid some cast error at different class object. - set->insert((void*)string_value.data(), string_value.size()); - }); - break; - } - case TYPE_IPV4: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - int32_t tmp = column.intval(); - set->insert(&tmp); - }); - break; - } - case TYPE_IPV6: { - batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { - auto string_val = column.stringval(); - StringParser::ParseResult result; - auto int128_val = StringParser::string_to_int( - string_val.c_str(), string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - set->insert(&int128_val); - }); - break; - } - default: { - return Status::InternalError("not support assign to in filter, type: " + - type_to_string(_column_return_type)); - } - } - return Status::OK(); - } - - void set_enable_fixed_len_to_uint32_v2() { - if (_context->bloom_filter_func) { - _context->bloom_filter_func->set_enable_fixed_len_to_uint32_v2(); - } - } - - // used by shuffle runtime filter - // assign this filter by protobuf - Status assign(const PBloomFilter* bloom_filter, butil::IOBufAsZeroCopyInputStream* data, - bool contain_null) { - // we won't use this class to insert or find any data - // so any type is ok - _context->bloom_filter_func.reset(create_bloom_filter(_column_return_type == INVALID_TYPE - ? PrimitiveType::TYPE_INT - : _column_return_type)); - RETURN_IF_ERROR(_context->bloom_filter_func->assign(data, bloom_filter->filter_length(), - contain_null)); - return Status::OK(); - } - - // used by shuffle runtime filter - // assign this filter by protobuf - Status assign(const PMinMaxFilter* minmax_filter, bool contain_null) { - _context->minmax_func.reset(create_minmax_filter(_column_return_type)); - - if (contain_null) { - _context->minmax_func->set_null_aware(true); - _context->minmax_func->set_contain_null(); - } - - switch (_column_return_type) { - case TYPE_BOOLEAN: { - bool min_val = minmax_filter->min_val().boolval(); - bool max_val = minmax_filter->max_val().boolval(); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_TINYINT: { - auto min_val = static_cast(minmax_filter->min_val().intval()); - auto max_val = static_cast(minmax_filter->max_val().intval()); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_SMALLINT: { - auto min_val = static_cast(minmax_filter->min_val().intval()); - auto max_val = static_cast(minmax_filter->max_val().intval()); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_INT: { - int32_t min_val = minmax_filter->min_val().intval(); - int32_t max_val = minmax_filter->max_val().intval(); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_BIGINT: { - int64_t min_val = minmax_filter->min_val().longval(); - int64_t max_val = minmax_filter->max_val().longval(); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_LARGEINT: { - auto min_string_val = minmax_filter->min_val().stringval(); - auto max_string_val = minmax_filter->max_val().stringval(); - StringParser::ParseResult result; - auto min_val = StringParser::string_to_int(min_string_val.c_str(), - min_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - auto max_val = StringParser::string_to_int(max_string_val.c_str(), - max_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_FLOAT: { - auto min_val = static_cast(minmax_filter->min_val().doubleval()); - auto max_val = static_cast(minmax_filter->max_val().doubleval()); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DOUBLE: { - auto min_val = static_cast(minmax_filter->min_val().doubleval()); - auto max_val = static_cast(minmax_filter->max_val().doubleval()); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DATEV2: { - int32_t min_val = minmax_filter->min_val().intval(); - int32_t max_val = minmax_filter->max_val().intval(); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DATETIMEV2: { - int64_t min_val = minmax_filter->min_val().longval(); - int64_t max_val = minmax_filter->max_val().longval(); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DATETIME: - case TYPE_DATE: { - const auto& min_val_ref = minmax_filter->min_val().stringval(); - const auto& max_val_ref = minmax_filter->max_val().stringval(); - VecDateTimeValue min_val; - VecDateTimeValue max_val; - min_val.from_date_str(min_val_ref.c_str(), min_val_ref.length()); - max_val.from_date_str(max_val_ref.c_str(), max_val_ref.length()); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DECIMALV2: { - const auto& min_val_ref = minmax_filter->min_val().stringval(); - const auto& max_val_ref = minmax_filter->max_val().stringval(); - DecimalV2Value min_val(min_val_ref); - DecimalV2Value max_val(max_val_ref); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DECIMAL32: { - int32_t min_val = minmax_filter->min_val().intval(); - int32_t max_val = minmax_filter->max_val().intval(); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DECIMAL64: { - int64_t min_val = minmax_filter->min_val().longval(); - int64_t max_val = minmax_filter->max_val().longval(); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DECIMAL128I: { - auto min_string_val = minmax_filter->min_val().stringval(); - auto max_string_val = minmax_filter->max_val().stringval(); - StringParser::ParseResult result; - auto min_val = StringParser::string_to_int(min_string_val.c_str(), - min_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - auto max_val = StringParser::string_to_int(max_string_val.c_str(), - max_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_DECIMAL256: { - auto min_string_val = minmax_filter->min_val().stringval(); - auto max_string_val = minmax_filter->max_val().stringval(); - StringParser::ParseResult result; - auto min_val = StringParser::string_to_int( - min_string_val.c_str(), min_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - auto max_val = StringParser::string_to_int( - max_string_val.c_str(), max_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - return _context->minmax_func->assign(&min_val, &max_val); - } - case TYPE_VARCHAR: - case TYPE_CHAR: - case TYPE_STRING: { - auto min_val_ref = minmax_filter->min_val().stringval(); - auto max_val_ref = minmax_filter->max_val().stringval(); - return _context->minmax_func->assign(&min_val_ref, &max_val_ref); - } - case TYPE_IPV4: { - int tmp_min = minmax_filter->min_val().intval(); - int tmp_max = minmax_filter->max_val().intval(); - return _context->minmax_func->assign(&tmp_min, &tmp_max); - } - case TYPE_IPV6: { - auto min_string_val = minmax_filter->min_val().stringval(); - auto max_string_val = minmax_filter->max_val().stringval(); - StringParser::ParseResult result; - auto min_val = StringParser::string_to_int(min_string_val.c_str(), - min_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - auto max_val = StringParser::string_to_int(max_string_val.c_str(), - max_string_val.length(), &result); - DCHECK(result == StringParser::PARSE_SUCCESS); - return _context->minmax_func->assign(&min_val, &max_val); - } - default: - break; - } - return Status::InternalError("not support!"); - } - - void get_bloom_filter_desc(char** data, int* filter_length) { - _context->bloom_filter_func->get_data(data, filter_length); - } - - PrimitiveType column_type() { return _column_return_type; } - - bool is_bloomfilter() const { return get_real_type() == RuntimeFilterType::BLOOM_FILTER; } - - bool contain_null() const { - if (is_bloomfilter()) { - return _context->bloom_filter_func->contain_null(); - } - if (_context->hybrid_set) { - if (get_real_type() != RuntimeFilterType::IN_FILTER) { - throw Exception(ErrorCode::INTERNAL_ERROR, "rf has hybrid_set but real type is {}", - int(get_real_type())); - } - return _context->hybrid_set->contain_null(); - } - if (_context->minmax_func) { - return _context->minmax_func->contain_null(); - } - return false; - } - - bool is_ignored() const { return _context->ignored; } - - void set_ignored() { _context->ignored = true; } - - bool is_disabled() const { return _context->disabled; } - - void set_disabled() { - _context->disabled = true; - _context->minmax_func.reset(); - _context->hybrid_set.reset(); - _context->bloom_filter_func.reset(); - _context->bitmap_filter_func.reset(); - } - - void batch_assign(const PInFilter* filter, - void (*assign_func)(std::shared_ptr& _hybrid_set, - PColumnValue&)) { - for (int i = 0; i < filter->values_size(); ++i) { - PColumnValue column = filter->values(i); - assign_func(_context->hybrid_set, column); - } - } - - size_t get_in_filter_size() const { - return _context->hybrid_set ? _context->hybrid_set->size() : 0; - } - - std::shared_ptr get_bitmap_filter() const { - return _context->bitmap_filter_func; - } - - friend class IRuntimeFilter; - - void set_filter_id(int id) { - if (_context->bloom_filter_func) { - _context->bloom_filter_func->set_filter_id(id); - } - if (_context->bitmap_filter_func) { - _context->bitmap_filter_func->set_filter_id(id); - } - if (_context->hybrid_set) { - _context->hybrid_set->set_filter_id(id); - } - } - -private: - // When a runtime filter received from remote and it is a bloom filter, _column_return_type will be invalid. - PrimitiveType _column_return_type; // column type - RuntimeFilterType _filter_type; - int32_t _max_in_num = -1; - - RuntimeFilterContextSPtr _context; - uint32_t _filter_id; -}; Status IRuntimeFilter::create(RuntimeFilterParamsContext* state, const TRuntimeFilterDesc* desc, const TQueryOptions* query_options, const RuntimeFilterRole role, @@ -1794,4 +1089,642 @@ Status RuntimePredicateWrapper::get_push_exprs( RuntimeFilterWrapperHolder::RuntimeFilterWrapperHolder() = default; RuntimeFilterWrapperHolder::~RuntimeFilterWrapperHolder() = default; +Status RuntimePredicateWrapper::init(const RuntimeFilterParams* params) { + _max_in_num = params->max_in_num; + switch (_filter_type) { + case RuntimeFilterType::IN_FILTER: { + _context->hybrid_set.reset(create_set(_column_return_type)); + _context->hybrid_set->set_null_aware(params->null_aware); + break; + } + // Only use in nested loop join not need set null aware + case RuntimeFilterType::MIN_FILTER: + case RuntimeFilterType::MAX_FILTER: { + _context->minmax_func.reset(create_minmax_filter(_column_return_type)); + break; + } + case RuntimeFilterType::MINMAX_FILTER: { + _context->minmax_func.reset(create_minmax_filter(_column_return_type)); + _context->minmax_func->set_null_aware(params->null_aware); + break; + } + case RuntimeFilterType::BLOOM_FILTER: { + _context->bloom_filter_func.reset(create_bloom_filter(_column_return_type)); + _context->bloom_filter_func->init_params(params); + return Status::OK(); + } + case RuntimeFilterType::IN_OR_BLOOM_FILTER: { + _context->hybrid_set.reset(create_set(_column_return_type)); + _context->hybrid_set->set_null_aware(params->null_aware); + _context->bloom_filter_func.reset(create_bloom_filter(_column_return_type)); + _context->bloom_filter_func->init_params(params); + return Status::OK(); + } + case RuntimeFilterType::BITMAP_FILTER: { + _context->bitmap_filter_func.reset(create_bitmap_filter(_column_return_type)); + _context->bitmap_filter_func->set_not_in(params->bitmap_filter_not_in); + return Status::OK(); + } + default: + return Status::InternalError("Unknown Filter type"); + } + return Status::OK(); +} + +Status RuntimePredicateWrapper::change_to_bloom_filter() { + if (_filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER) { + return Status::InternalError( + "Can not change to bloom filter because of runtime filter type is {}", + IRuntimeFilter::to_string(_filter_type)); + } + BloomFilterFuncBase* bf = _context->bloom_filter_func.get(); + + if (bf != nullptr) { + insert_to_bloom_filter(bf); + } else if (_context->hybrid_set != nullptr && _context->hybrid_set->size() != 0) { + return Status::InternalError("change to bloom filter need empty set ", + IRuntimeFilter::to_string(_filter_type)); + } + + // release in filter + _context->hybrid_set.reset(); + return Status::OK(); +} + +void RuntimePredicateWrapper::set_filter_id(int id) { + if (_context->bloom_filter_func) { + _context->bloom_filter_func->set_filter_id(id); + } + if (_context->bitmap_filter_func) { + _context->bitmap_filter_func->set_filter_id(id); + } + if (_context->hybrid_set) { + _context->hybrid_set->set_filter_id(id); + } +} + +void RuntimePredicateWrapper::batch_assign( + const PInFilter* filter, + void (*assign_func)(std::shared_ptr& _hybrid_set, PColumnValue&)) { + for (int i = 0; i < filter->values_size(); ++i) { + PColumnValue column = filter->values(i); + assign_func(_context->hybrid_set, column); + } +} + +Status RuntimePredicateWrapper::init_bloom_filter(const size_t build_bf_cardinality) { + if (_filter_type != RuntimeFilterType::BLOOM_FILTER && + _filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER) { + throw Exception(ErrorCode::INTERNAL_ERROR, "init_bloom_filter meet invalid input type {}", + int(_filter_type)); + } + return _context->bloom_filter_func->init_with_cardinality(build_bf_cardinality); +} + +bool RuntimePredicateWrapper::get_build_bf_cardinality() const { + if (_filter_type == RuntimeFilterType::BLOOM_FILTER || + _filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER) { + return _context->bloom_filter_func->get_build_bf_cardinality(); + } + return false; +} + +void RuntimePredicateWrapper::insert_to_bloom_filter(BloomFilterFuncBase* bloom_filter) const { + if (_context->hybrid_set->size() > 0) { + auto* it = _context->hybrid_set->begin(); + while (it->has_next()) { + bloom_filter->insert(it->get_value()); + it->next(); + } + } + if (_context->hybrid_set->contain_null()) { + bloom_filter->set_contain_null_and_null_aware(); + } +} + +void RuntimePredicateWrapper::insert_fixed_len(const vectorized::ColumnPtr& column, size_t start) { + if (is_ignored()) { + throw Exception(ErrorCode::INTERNAL_ERROR, "insert_fixed_len meet ignored rf"); + } + switch (_filter_type) { + case RuntimeFilterType::IN_FILTER: { + _context->hybrid_set->insert_fixed_len(column, start); + break; + } + case RuntimeFilterType::MIN_FILTER: + case RuntimeFilterType::MAX_FILTER: + case RuntimeFilterType::MINMAX_FILTER: { + _context->minmax_func->insert_fixed_len(column, start); + break; + } + case RuntimeFilterType::BLOOM_FILTER: { + _context->bloom_filter_func->insert_fixed_len(column, start); + break; + } + case RuntimeFilterType::IN_OR_BLOOM_FILTER: { + if (is_bloomfilter()) { + _context->bloom_filter_func->insert_fixed_len(column, start); + } else { + _context->hybrid_set->insert_fixed_len(column, start); + } + break; + } + default: + DCHECK(false); + break; + } +} + +void RuntimePredicateWrapper::bitmap_filter_insert_batch(const vectorized::ColumnPtr column, + size_t start) { + std::vector bitmaps; + if (column->is_nullable()) { + const auto* nullable = assert_cast(column.get()); + const auto& col = + assert_cast(nullable->get_nested_column()); + const auto& nullmap = + assert_cast(nullable->get_null_map_column()) + .get_data(); + for (size_t i = start; i < column->size(); i++) { + if (!nullmap[i]) { + bitmaps.push_back(&(col.get_data()[i])); + } + } + } else { + const auto* col = assert_cast(column.get()); + for (size_t i = start; i < column->size(); i++) { + bitmaps.push_back(&(col->get_data()[i])); + } + } + _context->bitmap_filter_func->insert_many(bitmaps); +} + +size_t RuntimePredicateWrapper::get_bloom_filter_size() const { + return _context->bloom_filter_func ? _context->bloom_filter_func->get_size() : 0; +} + +Status RuntimePredicateWrapper::merge(const RuntimePredicateWrapper* wrapper) { + if (wrapper->is_disabled()) { + set_disabled(); + return Status::OK(); + } + + if (wrapper->is_ignored() || is_disabled()) { + return Status::OK(); + } + + _context->ignored = false; + + bool can_not_merge_in_or_bloom = + _filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER && + (wrapper->_filter_type != RuntimeFilterType::IN_FILTER && + wrapper->_filter_type != RuntimeFilterType::BLOOM_FILTER && + wrapper->_filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER); + + bool can_not_merge_other = _filter_type != RuntimeFilterType::IN_OR_BLOOM_FILTER && + _filter_type != wrapper->_filter_type; + + CHECK(!can_not_merge_in_or_bloom && !can_not_merge_other) + << " can not merge runtime filter(id=" << _filter_id << "), current is filter type is " + << IRuntimeFilter::to_string(_filter_type) << ", other filter type is " + << IRuntimeFilter::to_string(wrapper->_filter_type); + + switch (_filter_type) { + case RuntimeFilterType::IN_FILTER: { + _context->hybrid_set->insert(wrapper->_context->hybrid_set.get()); + if (_max_in_num >= 0 && _context->hybrid_set->size() >= _max_in_num) { + set_disabled(); + } + break; + } + case RuntimeFilterType::MIN_FILTER: + case RuntimeFilterType::MAX_FILTER: + case RuntimeFilterType::MINMAX_FILTER: { + RETURN_IF_ERROR(_context->minmax_func->merge(wrapper->_context->minmax_func.get())); + break; + } + case RuntimeFilterType::BLOOM_FILTER: { + RETURN_IF_ERROR( + _context->bloom_filter_func->merge(wrapper->_context->bloom_filter_func.get())); + break; + } + case RuntimeFilterType::IN_OR_BLOOM_FILTER: { + auto real_filter_type = get_real_type(); + + auto other_filter_type = wrapper->_filter_type; + if (other_filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER) { + other_filter_type = wrapper->get_real_type(); + } + + if (real_filter_type == RuntimeFilterType::IN_FILTER) { + // when we meet base rf is in-filter, threre only have two case: + // case1: all input-filter's build_bf_exactly is true, inited by synced global size + // case2: all input-filter's build_bf_exactly is false, inited by default size + if (other_filter_type == RuntimeFilterType::IN_FILTER) { + _context->hybrid_set->insert(wrapper->_context->hybrid_set.get()); + if (_max_in_num >= 0 && _context->hybrid_set->size() >= _max_in_num) { + // case2: use default size to init bf + RETURN_IF_ERROR(_context->bloom_filter_func->init_with_fixed_length()); + RETURN_IF_ERROR(change_to_bloom_filter()); + } + } else { + // case1&case2: use input bf directly and insert hybrid set data into bf + _context->bloom_filter_func = wrapper->_context->bloom_filter_func; + RETURN_IF_ERROR(change_to_bloom_filter()); + } + } else { + if (other_filter_type == RuntimeFilterType::IN_FILTER) { + // case2: insert data to global filter + wrapper->insert_to_bloom_filter(_context->bloom_filter_func.get()); + } else { + // case1&case2: all input bf must has same size + RETURN_IF_ERROR(_context->bloom_filter_func->merge( + wrapper->_context->bloom_filter_func.get())); + } + } + break; + } + case RuntimeFilterType::BITMAP_FILTER: { + // use input bitmap directly because we assume bitmap filter join always have full data + _context->bitmap_filter_func = wrapper->_context->bitmap_filter_func; + break; + } + default: + return Status::InternalError("unknown runtime filter"); + } + return Status::OK(); +} + +Status RuntimePredicateWrapper::assign(const PInFilter* in_filter, bool contain_null) { + _context->hybrid_set.reset(create_set(_column_return_type)); + if (contain_null) { + _context->hybrid_set->set_null_aware(true); + _context->hybrid_set->insert((const void*)nullptr); + } + + switch (_column_return_type) { + case TYPE_BOOLEAN: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + bool bool_val = column.boolval(); + set->insert(&bool_val); + }); + break; + } + case TYPE_TINYINT: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto int_val = static_cast(column.intval()); + set->insert(&int_val); + }); + break; + } + case TYPE_SMALLINT: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto int_val = static_cast(column.intval()); + set->insert(&int_val); + }); + break; + } + case TYPE_INT: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + int32_t int_val = column.intval(); + set->insert(&int_val); + }); + break; + } + case TYPE_BIGINT: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + int64_t long_val = column.longval(); + set->insert(&long_val); + }); + break; + } + case TYPE_LARGEINT: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto string_val = column.stringval(); + StringParser::ParseResult result; + auto int128_val = StringParser::string_to_int(string_val.c_str(), + string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + set->insert(&int128_val); + }); + break; + } + case TYPE_FLOAT: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto float_val = static_cast(column.doubleval()); + set->insert(&float_val); + }); + break; + } + case TYPE_DOUBLE: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + double double_val = column.doubleval(); + set->insert(&double_val); + }); + break; + } + case TYPE_DATEV2: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto date_v2_val = column.intval(); + set->insert(&date_v2_val); + }); + break; + } + case TYPE_DATETIMEV2: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto date_v2_val = column.longval(); + set->insert(&date_v2_val); + }); + break; + } + case TYPE_DATETIME: + case TYPE_DATE: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + const auto& string_val_ref = column.stringval(); + VecDateTimeValue datetime_val; + datetime_val.from_date_str(string_val_ref.c_str(), string_val_ref.length()); + set->insert(&datetime_val); + }); + break; + } + case TYPE_DECIMALV2: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + const auto& string_val_ref = column.stringval(); + DecimalV2Value decimal_val(string_val_ref); + set->insert(&decimal_val); + }); + break; + } + case TYPE_DECIMAL32: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + int32_t decimal_32_val = column.intval(); + set->insert(&decimal_32_val); + }); + break; + } + case TYPE_DECIMAL64: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + int64_t decimal_64_val = column.longval(); + set->insert(&decimal_64_val); + }); + break; + } + case TYPE_DECIMAL128I: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto string_val = column.stringval(); + StringParser::ParseResult result; + auto int128_val = StringParser::string_to_int(string_val.c_str(), + string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + set->insert(&int128_val); + }); + break; + } + case TYPE_DECIMAL256: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto string_val = column.stringval(); + StringParser::ParseResult result; + auto int_val = StringParser::string_to_int(string_val.c_str(), + string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + set->insert(&int_val); + }); + break; + } + case TYPE_VARCHAR: + case TYPE_CHAR: + case TYPE_STRING: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + const std::string& string_value = column.stringval(); + // string_value is std::string, call insert(data, size) function in StringSet will not cast as StringRef + // so could avoid some cast error at different class object. + set->insert((void*)string_value.data(), string_value.size()); + }); + break; + } + case TYPE_IPV4: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + int32_t tmp = column.intval(); + set->insert(&tmp); + }); + break; + } + case TYPE_IPV6: { + batch_assign(in_filter, [](std::shared_ptr& set, PColumnValue& column) { + auto string_val = column.stringval(); + StringParser::ParseResult result; + auto int128_val = StringParser::string_to_int(string_val.c_str(), + string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + set->insert(&int128_val); + }); + break; + } + default: { + return Status::InternalError("not support assign to in filter, type: " + + type_to_string(_column_return_type)); + } + } + return Status::OK(); +} + +void RuntimePredicateWrapper::set_enable_fixed_len_to_uint32_v2() { + if (_context->bloom_filter_func) { + _context->bloom_filter_func->set_enable_fixed_len_to_uint32_v2(); + } +} + +Status RuntimePredicateWrapper::assign(const PBloomFilter* bloom_filter, + butil::IOBufAsZeroCopyInputStream* data, bool contain_null) { + // we won't use this class to insert or find any data + // so any type is ok + _context->bloom_filter_func.reset(create_bloom_filter( + _column_return_type == INVALID_TYPE ? PrimitiveType::TYPE_INT : _column_return_type)); + RETURN_IF_ERROR( + _context->bloom_filter_func->assign(data, bloom_filter->filter_length(), contain_null)); + return Status::OK(); +} + +// used by shuffle runtime filter +// assign this filter by protobuf +Status RuntimePredicateWrapper::assign(const PMinMaxFilter* minmax_filter, bool contain_null) { + _context->minmax_func.reset(create_minmax_filter(_column_return_type)); + + if (contain_null) { + _context->minmax_func->set_null_aware(true); + _context->minmax_func->set_contain_null(); + } + + switch (_column_return_type) { + case TYPE_BOOLEAN: { + bool min_val = minmax_filter->min_val().boolval(); + bool max_val = minmax_filter->max_val().boolval(); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_TINYINT: { + auto min_val = static_cast(minmax_filter->min_val().intval()); + auto max_val = static_cast(minmax_filter->max_val().intval()); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_SMALLINT: { + auto min_val = static_cast(minmax_filter->min_val().intval()); + auto max_val = static_cast(minmax_filter->max_val().intval()); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_INT: { + int32_t min_val = minmax_filter->min_val().intval(); + int32_t max_val = minmax_filter->max_val().intval(); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_BIGINT: { + int64_t min_val = minmax_filter->min_val().longval(); + int64_t max_val = minmax_filter->max_val().longval(); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_LARGEINT: { + auto min_string_val = minmax_filter->min_val().stringval(); + auto max_string_val = minmax_filter->max_val().stringval(); + StringParser::ParseResult result; + auto min_val = StringParser::string_to_int(min_string_val.c_str(), + min_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + auto max_val = StringParser::string_to_int(max_string_val.c_str(), + max_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_FLOAT: { + auto min_val = static_cast(minmax_filter->min_val().doubleval()); + auto max_val = static_cast(minmax_filter->max_val().doubleval()); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DOUBLE: { + auto min_val = static_cast(minmax_filter->min_val().doubleval()); + auto max_val = static_cast(minmax_filter->max_val().doubleval()); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DATEV2: { + int32_t min_val = minmax_filter->min_val().intval(); + int32_t max_val = minmax_filter->max_val().intval(); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DATETIMEV2: { + int64_t min_val = minmax_filter->min_val().longval(); + int64_t max_val = minmax_filter->max_val().longval(); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DATETIME: + case TYPE_DATE: { + const auto& min_val_ref = minmax_filter->min_val().stringval(); + const auto& max_val_ref = minmax_filter->max_val().stringval(); + VecDateTimeValue min_val; + VecDateTimeValue max_val; + min_val.from_date_str(min_val_ref.c_str(), min_val_ref.length()); + max_val.from_date_str(max_val_ref.c_str(), max_val_ref.length()); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DECIMALV2: { + const auto& min_val_ref = minmax_filter->min_val().stringval(); + const auto& max_val_ref = minmax_filter->max_val().stringval(); + DecimalV2Value min_val(min_val_ref); + DecimalV2Value max_val(max_val_ref); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DECIMAL32: { + int32_t min_val = minmax_filter->min_val().intval(); + int32_t max_val = minmax_filter->max_val().intval(); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DECIMAL64: { + int64_t min_val = minmax_filter->min_val().longval(); + int64_t max_val = minmax_filter->max_val().longval(); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DECIMAL128I: { + auto min_string_val = minmax_filter->min_val().stringval(); + auto max_string_val = minmax_filter->max_val().stringval(); + StringParser::ParseResult result; + auto min_val = StringParser::string_to_int(min_string_val.c_str(), + min_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + auto max_val = StringParser::string_to_int(max_string_val.c_str(), + max_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_DECIMAL256: { + auto min_string_val = minmax_filter->min_val().stringval(); + auto max_string_val = minmax_filter->max_val().stringval(); + StringParser::ParseResult result; + auto min_val = StringParser::string_to_int(min_string_val.c_str(), + min_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + auto max_val = StringParser::string_to_int(max_string_val.c_str(), + max_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + return _context->minmax_func->assign(&min_val, &max_val); + } + case TYPE_VARCHAR: + case TYPE_CHAR: + case TYPE_STRING: { + auto min_val_ref = minmax_filter->min_val().stringval(); + auto max_val_ref = minmax_filter->max_val().stringval(); + return _context->minmax_func->assign(&min_val_ref, &max_val_ref); + } + case TYPE_IPV4: { + int tmp_min = minmax_filter->min_val().intval(); + int tmp_max = minmax_filter->max_val().intval(); + return _context->minmax_func->assign(&tmp_min, &tmp_max); + } + case TYPE_IPV6: { + auto min_string_val = minmax_filter->min_val().stringval(); + auto max_string_val = minmax_filter->max_val().stringval(); + StringParser::ParseResult result; + auto min_val = StringParser::string_to_int(min_string_val.c_str(), + min_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + auto max_val = StringParser::string_to_int(max_string_val.c_str(), + max_string_val.length(), &result); + DCHECK(result == StringParser::PARSE_SUCCESS); + return _context->minmax_func->assign(&min_val, &max_val); + } + default: + break; + } + return Status::InternalError("not support!"); +} + +void RuntimePredicateWrapper::get_bloom_filter_desc(char** data, int* filter_length) { + _context->bloom_filter_func->get_data(data, filter_length); +} + +bool RuntimePredicateWrapper::contain_null() const { + if (is_bloomfilter()) { + return _context->bloom_filter_func->contain_null(); + } + if (_context->hybrid_set) { + if (get_real_type() != RuntimeFilterType::IN_FILTER) { + throw Exception(ErrorCode::INTERNAL_ERROR, "rf has hybrid_set but real type is {}", + int(get_real_type())); + } + return _context->hybrid_set->contain_null(); + } + if (_context->minmax_func) { + return _context->minmax_func->contain_null(); + } + return false; +} + +size_t RuntimePredicateWrapper::get_in_filter_size() const { + return _context->hybrid_set ? _context->hybrid_set->size() : 0; +} + +void RuntimePredicateWrapper::set_disabled() { + _context->disabled = true; + _context->minmax_func.reset(); + _context->hybrid_set.reset(); + _context->bloom_filter_func.reset(); + _context->bitmap_filter_func.reset(); +} + } // namespace doris diff --git a/be/src/exprs/runtime_filter.h b/be/src/exprs/runtime_filter.h index 441de7d4da340c..e38f6901ef4618 100644 --- a/be/src/exprs/runtime_filter.h +++ b/be/src/exprs/runtime_filter.h @@ -443,4 +443,115 @@ class RuntimeFilterWrapperHolder { WrapperPtr _wrapper; }; +// This class is a wrapper of runtime predicate function +class RuntimePredicateWrapper { +public: + RuntimePredicateWrapper(const RuntimeFilterParams* params) + : RuntimePredicateWrapper(params->column_return_type, params->filter_type, + params->filter_id) {}; + // for a 'tmp' runtime predicate wrapper + // only could called assign method or as a param for merge + RuntimePredicateWrapper(PrimitiveType column_type, RuntimeFilterType type, uint32_t filter_id) + : _column_return_type(column_type), + _filter_type(type), + _context(new RuntimeFilterContext()), + _filter_id(filter_id) {} + + // init runtime filter wrapper + // alloc memory to init runtime filter function + Status init(const RuntimeFilterParams* params); + + Status change_to_bloom_filter(); + + Status init_bloom_filter(const size_t build_bf_cardinality); + + bool get_build_bf_cardinality() const; + + void insert_to_bloom_filter(BloomFilterFuncBase* bloom_filter) const; + + BloomFilterFuncBase* get_bloomfilter() const { return _context->bloom_filter_func.get(); } + + void insert_fixed_len(const vectorized::ColumnPtr& column, size_t start); + + void insert_batch(const vectorized::ColumnPtr& column, size_t start) { + if (get_real_type() == RuntimeFilterType::BITMAP_FILTER) { + bitmap_filter_insert_batch(column, start); + } else { + insert_fixed_len(column, start); + } + } + + void bitmap_filter_insert_batch(const vectorized::ColumnPtr column, size_t start); + + RuntimeFilterType get_real_type() const { + if (_filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER) { + if (_context->hybrid_set) { + return RuntimeFilterType::IN_FILTER; + } + return RuntimeFilterType::BLOOM_FILTER; + } + return _filter_type; + } + + size_t get_bloom_filter_size() const; + + Status get_push_exprs(std::list& probe_ctxs, + std::vector& push_exprs, + const TExpr& probe_expr); + + Status merge(const RuntimePredicateWrapper* wrapper); + + Status assign(const PInFilter* in_filter, bool contain_null); + + void set_enable_fixed_len_to_uint32_v2(); + + // used by shuffle runtime filter + // assign this filter by protobuf + Status assign(const PBloomFilter* bloom_filter, butil::IOBufAsZeroCopyInputStream* data, + bool contain_null); + + // used by shuffle runtime filter + // assign this filter by protobuf + Status assign(const PMinMaxFilter* minmax_filter, bool contain_null); + + void get_bloom_filter_desc(char** data, int* filter_length); + + PrimitiveType column_type() { return _column_return_type; } + + bool is_bloomfilter() const { return get_real_type() == RuntimeFilterType::BLOOM_FILTER; } + + bool contain_null() const; + + bool is_ignored() const { return _context->ignored; } + + void set_ignored() { _context->ignored = true; } + + bool is_disabled() const { return _context->disabled; } + + void set_disabled(); + + void batch_assign(const PInFilter* filter, + void (*assign_func)(std::shared_ptr& _hybrid_set, + PColumnValue&)); + + size_t get_in_filter_size() const; + + std::shared_ptr get_bitmap_filter() const { + return _context->bitmap_filter_func; + } + + friend class IRuntimeFilter; + + void set_filter_id(int id); + +private: + // When a runtime filter received from remote and it is a bloom filter, _column_return_type will be invalid. + PrimitiveType _column_return_type; // column type + RuntimeFilterType _filter_type; + int32_t _max_in_num = -1; + + RuntimeFilterContextSPtr _context; + uint32_t _filter_id; +}; + } // namespace doris diff --git a/be/src/exprs/runtime_filter_slots.h b/be/src/exprs/runtime_filter_slots.h index a9dd631e3581a2..b732ae155f7531 100644 --- a/be/src/exprs/runtime_filter_slots.h +++ b/be/src/exprs/runtime_filter_slots.h @@ -35,11 +35,7 @@ class VRuntimeFilterSlots { VRuntimeFilterSlots( const std::vector>& build_expr_ctxs, const std::vector>& runtime_filters) - : _build_expr_context(build_expr_ctxs), _runtime_filters(runtime_filters) { - for (auto runtime_filter : _runtime_filters) { - _runtime_filters_map[runtime_filter->expr_order()].push_back(runtime_filter.get()); - } - } + : _build_expr_context(build_expr_ctxs), _runtime_filters(runtime_filters) {} Status send_filter_size(RuntimeState* state, uint64_t hash_table_size, std::shared_ptr dependency) { @@ -139,62 +135,48 @@ class VRuntimeFilterSlots { } void insert(const vectorized::Block* block) { - for (int i = 0; i < _build_expr_context.size(); ++i) { - auto iter = _runtime_filters_map.find(i); - if (iter == _runtime_filters_map.end()) { - continue; - } - - int result_column_id = _build_expr_context[i]->get_last_result_column_id(); + for (auto& filter : _runtime_filters) { + int result_column_id = + _build_expr_context[filter->expr_order()]->get_last_result_column_id(); const auto& column = block->get_by_position(result_column_id).column; - for (auto* filter : iter->second) { - if (filter->get_ignored() || filter->get_disabled()) { - continue; - } - filter->insert_batch(column, 1); + if (filter->get_ignored() || filter->get_disabled()) { + continue; } + filter->insert_batch(column, 1); } } // publish runtime filter Status publish(RuntimeState* state, bool publish_local) { - for (auto& pair : _runtime_filters_map) { - for (auto& filter : pair.second) { - RETURN_IF_ERROR(filter->publish(state, publish_local)); - } + for (auto& filter : _runtime_filters) { + RETURN_IF_ERROR(filter->publish(state, publish_local)); } return Status::OK(); } void copy_to_shared_context(vectorized::SharedHashTableContextPtr& context) { - for (auto& it : _runtime_filters_map) { - for (auto& filter : it.second) { - context->runtime_filters[filter->filter_id()] = filter->get_shared_context_ref(); - } + for (auto& filter : _runtime_filters) { + context->runtime_filters[filter->filter_id()] = filter->get_shared_context_ref(); } } Status copy_from_shared_context(vectorized::SharedHashTableContextPtr& context) { - for (auto& it : _runtime_filters_map) { - for (auto& filter : it.second) { - auto filter_id = filter->filter_id(); - auto ret = context->runtime_filters.find(filter_id); - if (ret == context->runtime_filters.end()) { - return Status::Aborted("invalid runtime filter id: {}", filter_id); - } - filter->get_shared_context_ref() = ret->second; + for (auto& filter : _runtime_filters) { + auto filter_id = filter->filter_id(); + auto ret = context->runtime_filters.find(filter_id); + if (ret == context->runtime_filters.end()) { + return Status::Aborted("invalid runtime filter id: {}", filter_id); } + filter->get_shared_context_ref() = ret->second; } return Status::OK(); } - bool empty() { return _runtime_filters_map.empty(); } + bool empty() { return _runtime_filters.empty(); } private: const std::vector>& _build_expr_context; std::vector> _runtime_filters; - // prob_contition index -> [IRuntimeFilter] - std::map> _runtime_filters_map; }; } // namespace doris diff --git a/be/src/pipeline/exec/operator.cpp b/be/src/pipeline/exec/operator.cpp index bb254aae72b8a7..9ca0f0fcd40dd6 100644 --- a/be/src/pipeline/exec/operator.cpp +++ b/be/src/pipeline/exec/operator.cpp @@ -295,7 +295,7 @@ Status OperatorXBase::do_projections(RuntimeState* state, vectorized::Block* ori *_output_row_descriptor); if (rows != 0) { auto& mutable_columns = mutable_block.mutable_columns(); - DCHECK(mutable_columns.size() == local_state->_projections.size()); + DCHECK_EQ(mutable_columns.size(), local_state->_projections.size()) << debug_string(); for (int i = 0; i < mutable_columns.size(); ++i) { auto result_column_id = -1; RETURN_IF_ERROR(local_state->_projections[i]->execute(&input_block, &result_column_id)); diff --git a/be/src/pipeline/pipeline_fragment_context.cpp b/be/src/pipeline/pipeline_fragment_context.cpp index e8e8ed5d9fea05..c8e3429107c507 100644 --- a/be/src/pipeline/pipeline_fragment_context.cpp +++ b/be/src/pipeline/pipeline_fragment_context.cpp @@ -363,6 +363,7 @@ Status PipelineFragmentContext::_build_pipeline_tasks(const doris::TPipelineFrag const auto target_size = request.local_params.size(); _tasks.resize(target_size); _runtime_filter_states.resize(target_size); + _runtime_filter_mgr_map.resize(target_size); _task_runtime_states.resize(_pipelines.size()); for (size_t pip_idx = 0; pip_idx < _pipelines.size(); pip_idx++) { _task_runtime_states[pip_idx].resize(_pipelines[pip_idx]->num_tasks()); @@ -510,7 +511,7 @@ Status PipelineFragmentContext::_build_pipeline_tasks(const doris::TPipelineFrag } { std::lock_guard l(_state_map_lock); - _runtime_filter_mgr_map[fragment_instance_id] = std::move(runtime_filter_mgr); + _runtime_filter_mgr_map[i] = std::move(runtime_filter_mgr); } return Status::OK(); }; diff --git a/be/src/pipeline/pipeline_fragment_context.h b/be/src/pipeline/pipeline_fragment_context.h index d672ad6e9233c1..bd3a350d0a240a 100644 --- a/be/src/pipeline/pipeline_fragment_context.h +++ b/be/src/pipeline/pipeline_fragment_context.h @@ -275,8 +275,7 @@ class PipelineFragmentContext : public TaskExecutionContext { _op_id_to_le_state; std::map _pip_id_to_pipeline; - // UniqueId -> runtime mgr - std::map> _runtime_filter_mgr_map; + std::vector> _runtime_filter_mgr_map; //Here are two types of runtime states: // - _runtime state is at the Fragment level. diff --git a/be/src/pipeline/task_queue.h b/be/src/pipeline/task_queue.h index 1651eb50cac4ab..2218d70fde61dc 100644 --- a/be/src/pipeline/task_queue.h +++ b/be/src/pipeline/task_queue.h @@ -107,12 +107,15 @@ class MultiCoreTaskQueue { public: explicit MultiCoreTaskQueue(int core_size); +#ifndef BE_TEST ~MultiCoreTaskQueue(); - - void close(); - // Get the task by core id. PipelineTask* take(int core_id); +#else + virtual ~MultiCoreTaskQueue(); + virtual PipelineTask* take(int core_id); +#endif + void close(); // TODO combine these methods to `push_back(task, core_id = -1)` Status push_back(PipelineTask* task); diff --git a/be/test/pipeline/dummy_task_queue.h b/be/test/pipeline/dummy_task_queue.h new file mode 100644 index 00000000000000..bace0ecae96192 --- /dev/null +++ b/be/test/pipeline/dummy_task_queue.h @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#include "pipeline/task_queue.h" + +namespace doris::pipeline { + +class DummyTaskQueue final : public MultiCoreTaskQueue { + explicit DummyTaskQueue(int core_size) : MultiCoreTaskQueue(core_size) {} + ~DummyTaskQueue() override = default; + PipelineTask* take(int core_id) override { + PipelineTask* task = nullptr; + do { + DCHECK(_prio_task_queues.size() > core_id) + << " list size: " << _prio_task_queues.size() << " core_id: " << core_id + << " _core_size: " << _core_size << " _next_core: " << _next_core.load(); + task = _prio_task_queues[core_id].try_take(false); + if (task) { + break; + } + task = _steal_take(core_id); + if (task) { + break; + } + task = _prio_task_queues[core_id].take(1); + if (task) { + break; + } + } while (false); + if (task) { + task->pop_out_runnable_queue(); + } + return task; + } +}; +} // namespace doris::pipeline diff --git a/be/test/pipeline/pipeline_test.cpp b/be/test/pipeline/pipeline_test.cpp index d1c0af85f58d24..6466d6c2927e10 100644 --- a/be/test/pipeline/pipeline_test.cpp +++ b/be/test/pipeline/pipeline_test.cpp @@ -22,12 +22,15 @@ #include "common/exception.h" #include "common/status.h" +#include "dummy_task_queue.h" +#include "exprs/bloom_filter_func.h" +#include "exprs/hybrid_set.h" +#include "exprs/runtime_filter.h" #include "pipeline/dependency.h" #include "pipeline/exec/exchange_source_operator.h" #include "pipeline/exec/hashjoin_build_sink.h" #include "pipeline/exec/hashjoin_probe_operator.h" #include "pipeline/pipeline_fragment_context.h" -#include "pipeline/task_queue.h" #include "runtime/exec_env.h" #include "runtime/fragment_mgr.h" #include "thrift_builder.h" @@ -45,10 +48,13 @@ class PipelineTest : public testing::Test { public: PipelineTest() : _obj_pool(new ObjectPool()), - _mgr(std::make_unique()) { + _mgr(std::make_unique()) {} + ~PipelineTest() override = default; + void SetUp() override { _query_options = TQueryOptionsBuilder() .set_enable_local_exchange(true) .set_enable_local_shuffle(true) + .set_runtime_filter_max_in_num(15) .build(); auto fe_address = TNetworkAddress(); fe_address.hostname = LOCALHOST; @@ -56,10 +62,12 @@ class PipelineTest : public testing::Test { _query_ctx = QueryContext::create_shared(_query_id, ExecEnv::GetInstance(), _query_options, fe_address, true, fe_address, QuerySource::INTERNAL_FRONTEND); + _query_ctx->runtime_filter_mgr()->set_runtime_filter_params( + TRuntimeFilterParamsBuilder().build()); ExecEnv::GetInstance()->set_stream_mgr(_mgr.get()); - _task_queue = std::make_unique(1); + _task_queue = std::make_unique(1); } - ~PipelineTest() override = default; + void TearDown() override {} private: std::shared_ptr _build_pipeline(int num_instances, Pipeline* parent = nullptr) { @@ -111,6 +119,7 @@ class PipelineTest : public testing::Test { _pipeline_profiles.clear(); _pipeline_tasks.clear(); _runtime_states.clear(); + _runtime_filter_mgrs.clear(); } int _next_fragment_id() { return _fragment_id++; } int _next_node_id() { return _next_node_idx++; } @@ -125,7 +134,7 @@ class PipelineTest : public testing::Test { std::shared_ptr _query_ctx; TUniqueId _query_id = TUniqueId(); TQueryOptions _query_options; - std::unique_ptr _task_queue; + std::unique_ptr _task_queue; // Fragment level // Fragment0 -> Fragment1 @@ -149,6 +158,9 @@ class PipelineTest : public testing::Test { std::vector>> _pipeline_tasks; std::vector>> _runtime_states; + // Instance level + std::vector> _runtime_filter_mgrs; + const std::string LOCALHOST = BackendOptions::get_localhost(); const int DUMMY_PORT = config::brpc_port; }; @@ -184,7 +196,7 @@ TEST_F(PipelineTest, HAPPY_PATH) { .set_scalar_type(TPrimitiveType::INT) .build()) .build()) - .set_nullIndicatorBit(0) + .set_nullIndicatorBit(-1) .set_byteOffset(0) .set_slotIdx(0) .set_isMaterialized(true) @@ -489,7 +501,7 @@ TEST_F(PipelineTest, PLAN_LOCAL_EXCHANGE) { .set_scalar_type(TPrimitiveType::INT) .build()) .build()) - .set_nullIndicatorBit(0) + .set_nullIndicatorBit(-1) .set_byteOffset(0) .set_slotIdx(0) .set_isMaterialized(true) @@ -585,7 +597,7 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { .set_scalar_type(TPrimitiveType::INT) .build()) .build()) - .set_nullIndicatorBit(0) + .set_nullIndicatorBit(-1) .set_byteOffset(0) .set_slotIdx(0) .set_isMaterialized(true) @@ -604,7 +616,7 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { .set_scalar_type(TPrimitiveType::INT) .build()) .build()) - .set_nullIndicatorBit(0) + .set_nullIndicatorBit(-1) .set_byteOffset(0) .set_slotIdx(0) .set_isMaterialized(true) @@ -623,7 +635,7 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { .set_scalar_type(TPrimitiveType::INT) .build()) .build()) - .set_nullIndicatorBit(0) + .set_nullIndicatorBit(-1) .set_byteOffset(0) .set_slotIdx(0) .set_isMaterialized(true) @@ -640,7 +652,7 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { .set_scalar_type(TPrimitiveType::INT) .build()) .build()) - .set_nullIndicatorBit(0) + .set_nullIndicatorBit(-1) .set_byteOffset(4) .set_slotIdx(1) .set_isMaterialized(true) @@ -720,6 +732,73 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { .append_vintermediate_tuple_id_list(1) .build()) .append_row_tuples(2, false) + .append_projections( + TExprBuilder() + .append_nodes( + TExprNodeBuilder( + TExprNodeType::SLOT_REF, + TTypeDescBuilder() + .set_types( + TTypeNodeBuilder() + .set_type( + TTypeNodeType:: + SCALAR) + .set_scalar_type( + TPrimitiveType:: + INT) + .build()) + .build(), + 0) + .set_slot_ref(TSlotRefBuilder(0, 0).build()) + .build()) + .build()) + .append_projections( + TExprBuilder() + .append_nodes( + TExprNodeBuilder( + TExprNodeType::SLOT_REF, + TTypeDescBuilder() + .set_types( + TTypeNodeBuilder() + .set_type( + TTypeNodeType:: + SCALAR) + .set_scalar_type( + TPrimitiveType:: + INT) + .build()) + .build(), + 0) + .set_slot_ref(TSlotRefBuilder(1, 1).build()) + .build()) + .build()) + .append_runtime_filters( + TRuntimeFilterDescBuilder( + 0, + TExprBuilder() + .append_nodes( + TExprNodeBuilder( + TExprNodeType::SLOT_REF, + TTypeDescBuilder() + .set_types( + TTypeNodeBuilder() + .set_type( + TTypeNodeType:: + SCALAR) + .set_scalar_type( + TPrimitiveType:: + INT) + .build()) + .build(), + 0) + .set_slot_ref( + TSlotRefBuilder(1, 1).build()) + .build()) + .build(), + 0, std::map {}) + .set_bloom_filter_size_bytes(1048576) + .set_build_bf_exactly(false) + .build()) .build(); { @@ -850,6 +929,12 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { { // Build pipeline task int task_id = 0; + _runtime_filter_mgrs.resize(parallelism); + for (int j = 0; j < parallelism; j++) { + auto runtime_filter_state = RuntimeFilterParamsContext::create(_query_ctx.get()); + _runtime_filter_mgrs[j] = std::make_unique( + _query_id, runtime_filter_state, _query_ctx->query_mem_tracker, false); + } for (size_t i = 0; i < _pipelines.size(); i++) { EXPECT_EQ(_pipelines[i]->id(), i); _pipeline_profiles[_pipelines[i]->id()] = std::make_shared( @@ -871,6 +956,8 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { local_runtime_state->set_task_num(_pipelines[i]->num_tasks()); local_runtime_state->set_task_execution_context( std::static_pointer_cast(_context.back())); + local_runtime_state->set_runtime_filter_mgr(_runtime_filter_mgrs[j].get()); + _runtime_filter_mgrs[j]->_state->set_state(local_runtime_state.get()); std::map, std::shared_ptr>> le_state_map; @@ -891,6 +978,7 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { } std::shared_ptr downstream_recvr; + auto downstream_pipeline_profile = std::make_shared("Downstream Pipeline"); { // Build downstream recvr auto context = _build_fragment_context(); @@ -900,13 +988,12 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { downstream_runtime_state->set_task_execution_context( std::static_pointer_cast(context)); - auto downstream_pipeline_profile = std::make_shared("Downstream Pipeline"); auto* memory_used_counter = downstream_pipeline_profile->AddHighWaterMarkCounter( "MemoryUsage", TUnit::BYTES, "", 1); downstream_recvr = ExecEnv::GetInstance()->_vstream_mgr->create_recvr( downstream_runtime_state.get(), memory_used_counter, _pipelines.front()->operators().back()->row_desc(), dest_ins_id, dest_node_id, - parallelism, downstream_pipeline_profile.get(), false, 20480); + parallelism, downstream_pipeline_profile.get(), false, 2048000); } for (size_t i = 0; i < _pipelines.size(); i++) { for (int j = 0; j < parallelism; j++) { @@ -914,21 +1001,16 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { EXPECT_EQ(_pipeline_tasks[_pipelines[i]->id()][j]->prepare(scan_ranges, j, tsink, _query_ctx.get()), Status::OK()); + if (i == 1) { + auto& local_state = _runtime_states[i][j] + ->get_sink_local_state() + ->cast(); + EXPECT_EQ(local_state._runtime_filters.size(), 1); + EXPECT_EQ(local_state._should_build_hash_table, true); + } } } - // Construct input block - vectorized::Block block; - { - vectorized::DataTypePtr int_type = std::make_shared(); - - auto int_col0 = vectorized::ColumnInt32::create(); - int_col0->insert_many_vals(1, 10); - block.insert({std::move(int_col0), int_type, "test_int_col0"}); - } - auto block_mem_usage = block.allocated_bytes(); - EXPECT_GT(block_mem_usage - 1, 0); - { for (size_t i = 0; i < _pipelines.size(); i++) { for (int j = 0; j < parallelism; j++) { @@ -960,24 +1042,146 @@ TEST_F(PipelineTest, PLAN_HASH_JOIN) { { for (int i = _pipelines.size() - 1; i >= 0; i--) { for (int j = 0; j < parallelism; j++) { + bool eos = false; + EXPECT_EQ(_pipeline_tasks[i][j]->execute(&eos), Status::OK()); + EXPECT_EQ(_pipeline_tasks[i][j]->_opened, true); + EXPECT_EQ(eos, false); + } + } + } + for (int i = _pipelines.size() - 1; i >= 0; i--) { + for (int j = 0; j < parallelism; j++) { + { + vectorized::Block block; + { + vectorized::DataTypePtr int_type = + std::make_shared(); + + auto int_col0 = vectorized::ColumnInt32::create(); + if (j == 0 || i == 0) { + int_col0->insert_many_vals(j, 10); + } else { + size_t ndv = 16; + for (size_t n = 0; n < ndv; n++) { + int_col0->insert_many_vals(n, 1); + } + } + + block.insert({std::move(int_col0), int_type, "test_int_col0"}); + } auto& local_state = _runtime_states[i][j] ->get_local_state(_pipelines[i]->operators().front()->operator_id()) ->cast(); - local_state.stream_recvr->_sender_queues[0]->decrement_senders(0); - - bool eos = false; - EXPECT_EQ(_pipeline_tasks[i][j]->execute(&eos), Status::OK()); - EXPECT_EQ(_pipeline_tasks[i][j]->_is_blocked(), false); - EXPECT_EQ(eos, true); - EXPECT_EQ(_pipeline_tasks[i][j]->is_pending_finish(), false); - EXPECT_EQ(_pipeline_tasks[i][j]->close(Status::OK()), Status::OK()); + EXPECT_EQ(local_state.stream_recvr->_sender_queues[0]->_source_dependency->ready(), + false); + EXPECT_EQ(local_state.stream_recvr->_sender_queues[0] + ->_source_dependency->_blocked_task.size(), + i == 1 ? 1 : 0); + local_state.stream_recvr->_sender_queues[0]->add_block(&block, true); + } + } + } + { + // Pipeline 1 is blocked by exchange dependency so tasks are ready after data reached. + // Pipeline 0 is blocked by hash join dependency and is still waiting for upstream tasks done. + for (int j = 0; j < parallelism; j++) { + // Task is ready and be push into runnable task queue. + EXPECT_EQ(_task_queue->take(0) != nullptr, true); + } + EXPECT_EQ(_task_queue->take(0), nullptr); + for (int j = 0; j < parallelism; j++) { + EXPECT_EQ(_pipeline_tasks[1][j]->_is_blocked(), false); + } + } + { + // Pipeline 1 ran first and build hash table in join build operator. + for (int j = 0; j < parallelism; j++) { + bool eos = false; + EXPECT_EQ(_pipeline_tasks[1][j]->execute(&eos), Status::OK()); + EXPECT_EQ(eos, false); + } + for (int j = 0; j < parallelism; j++) { + auto& local_state = + _runtime_states[1][j] + ->get_local_state(_pipelines[1]->operators().front()->operator_id()) + ->cast(); + local_state.stream_recvr->_sender_queues[0]->decrement_senders(0); + + bool eos = false; + EXPECT_EQ(_pipeline_tasks[1][j]->execute(&eos), Status::OK()); + EXPECT_EQ(_pipeline_tasks[1][j]->_is_blocked(), false); + EXPECT_EQ(eos, true); + auto& sink_local_state = _runtime_states[1][j] + ->get_sink_local_state() + ->cast(); + EXPECT_EQ(sink_local_state._runtime_filters_disabled, false); + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters.size(), 1); + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters[0] + ->need_sync_filter_size(), + false); + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters[0] + ->_runtime_filter_type, + RuntimeFilterType::IN_OR_BLOOM_FILTER); + EXPECT_EQ(_pipeline_tasks[1][j]->is_pending_finish(), false); + EXPECT_EQ(_pipeline_tasks[1][j]->close(Status::OK()), Status::OK()); + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters[0]->get_real_type(), + j == 0 ? RuntimeFilterType::IN_FILTER : RuntimeFilterType::BLOOM_FILTER) + << " " << j << " " + << IRuntimeFilter::to_string( + sink_local_state._runtime_filter_slots->_runtime_filters[0] + ->get_real_type()); + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters[0] + ->_wrapper->is_ignored(), + false); + if (j == 0) { + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters[0] + ->_wrapper->_context->hybrid_set->size(), + 1); + } else { + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters[0] + ->_wrapper->_context->bloom_filter_func->_build_bf_exactly, + false); + + EXPECT_EQ(sink_local_state._runtime_filter_slots->_runtime_filters[0] + ->_wrapper->_context->bloom_filter_func->_bloom_filter_length, + 1048576); } } - { - EXPECT_EQ(downstream_recvr->_sender_queues[0]->_block_queue.size(), 0); - EXPECT_EQ(downstream_recvr->_sender_queues[0]->_num_remaining_senders, 0); + } + { + // Pipeline 0 ran once hash table is built. + for (int j = 0; j < parallelism; j++) { + EXPECT_EQ(_pipeline_tasks[0][j]->_is_blocked(), false); } + for (int j = 0; j < parallelism; j++) { + bool eos = false; + EXPECT_EQ(_pipeline_tasks[0][j]->execute(&eos), Status::OK()); + EXPECT_EQ(eos, false); + } + for (int j = 0; j < parallelism; j++) { + auto& local_state = + _runtime_states[0][j] + ->get_local_state(_pipelines[0]->operators().front()->operator_id()) + ->cast(); + local_state.stream_recvr->_sender_queues[0]->decrement_senders(0); + + bool eos = false; + EXPECT_EQ(_pipeline_tasks[0][j]->execute(&eos), Status::OK()); + EXPECT_EQ(_pipeline_tasks[0][j]->_is_blocked(), false); + EXPECT_EQ(eos, true); + EXPECT_EQ(_pipeline_tasks[0][j]->is_pending_finish(), false); + EXPECT_EQ(_pipeline_tasks[0][j]->close(Status::OK()), Status::OK()); + } + } + { + // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] join [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] produces 100 rows in instance 0. + // [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] join [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] produces 100 rows in instance 1. + EXPECT_EQ(downstream_recvr->_sender_queues[0]->_block_queue.size(), 2); + EXPECT_EQ(downstream_recvr->_sender_queues[0]->_block_queue.front()._block->rows(), + 10 * 10); + EXPECT_EQ(downstream_recvr->_sender_queues[0]->_block_queue.back()._block->rows(), 10); + EXPECT_EQ(downstream_recvr->_sender_queues[0]->_num_remaining_senders, 0); } downstream_recvr->close(); } diff --git a/be/test/pipeline/thrift_builder.h b/be/test/pipeline/thrift_builder.h index bdcead8861690e..1af1ca760bd8c9 100644 --- a/be/test/pipeline/thrift_builder.h +++ b/be/test/pipeline/thrift_builder.h @@ -58,7 +58,7 @@ class TQueryOptionsBuilder { return *this; } TQueryOptionsBuilder& set_enable_new_shuffle_hash_method(bool enable_new_shuffle_hash_method) { - _query_options.enable_new_shuffle_hash_method = enable_new_shuffle_hash_method; + _query_options.__set_enable_new_shuffle_hash_method(enable_new_shuffle_hash_method); return *this; } TQueryOptionsBuilder& set_enable_local_shuffle(bool enable_local_shuffle) { @@ -66,11 +66,23 @@ class TQueryOptionsBuilder { return *this; } TQueryOptionsBuilder& set_runtime_filter_wait_infinitely(bool runtime_filter_wait_infinitely) { - _query_options.runtime_filter_wait_infinitely = runtime_filter_wait_infinitely; + _query_options.__set_runtime_filter_wait_infinitely(runtime_filter_wait_infinitely); return *this; } TQueryOptionsBuilder& set_enable_local_merge_sort(bool enable_local_merge_sort) { - _query_options.enable_local_merge_sort = enable_local_merge_sort; + _query_options.__set_enable_local_merge_sort(enable_local_merge_sort); + return *this; + } + TQueryOptionsBuilder& set_runtime_filter_max_in_num(int64_t runtime_filter_max_in_num) { + _query_options.__set_runtime_filter_max_in_num(runtime_filter_max_in_num); + return *this; + } + TQueryOptionsBuilder& set_runtime_bloom_filter_min_size(int64_t runtime_bloom_filter_min_size) { + _query_options.__set_runtime_bloom_filter_min_size(runtime_bloom_filter_min_size); + return *this; + } + TQueryOptionsBuilder& set_runtime_bloom_filter_max_size(int64_t runtime_bloom_filter_max_size) { + _query_options.__set_runtime_bloom_filter_max_size(runtime_bloom_filter_max_size); return *this; } @@ -117,6 +129,16 @@ class TPlanNodeBuilder { _plan_node.__set_output_tuple_id(output_tuple_id); return *this; } + TPlanNodeBuilder& append_projections(TExpr& projections) { + _plan_node.__isset.projections = true; + _plan_node.projections.push_back(projections); + return *this; + } + TPlanNodeBuilder& append_runtime_filters(TRuntimeFilterDesc& runtime_filter) { + _plan_node.__isset.runtime_filters = true; + _plan_node.runtime_filters.push_back(runtime_filter); + return *this; + } TPlanNode& build() { return _plan_node; } @@ -127,6 +149,55 @@ class TPlanNodeBuilder { TPlanNode _plan_node; }; +class TRuntimeFilterDescBuilder { +public: + explicit TRuntimeFilterDescBuilder( + int filter_id, TExpr& src_expr, int expr_order, + std::map planId_to_target_expr, bool is_broadcast_join = false, + bool has_local_targets = true, bool has_remote_targets = false, + TRuntimeFilterType::type type = TRuntimeFilterType::IN_OR_BLOOM) + : _desc() { + _desc.__set_filter_id(filter_id); + _desc.__set_src_expr(src_expr); + _desc.__set_expr_order(expr_order); + _desc.__set_planId_to_target_expr(planId_to_target_expr); + _desc.__set_is_broadcast_join(is_broadcast_join); + _desc.__set_has_local_targets(has_local_targets); + _desc.__set_has_remote_targets(has_remote_targets); + _desc.__set_type(type); + } + explicit TRuntimeFilterDescBuilder( + int filter_id, TExpr&& src_expr, int expr_order, + std::map planId_to_target_expr, bool is_broadcast_join = false, + bool has_local_targets = true, bool has_remote_targets = false, + TRuntimeFilterType::type type = TRuntimeFilterType::IN_OR_BLOOM) + : _desc() { + _desc.__set_filter_id(filter_id); + _desc.__set_src_expr(src_expr); + _desc.__set_expr_order(expr_order); + _desc.__set_planId_to_target_expr(planId_to_target_expr); + _desc.__set_is_broadcast_join(is_broadcast_join); + _desc.__set_has_local_targets(has_local_targets); + _desc.__set_has_remote_targets(has_remote_targets); + _desc.__set_type(type); + } + + TRuntimeFilterDescBuilder& set_bloom_filter_size_bytes(int64_t bloom_filter_size_bytes) { + _desc.__set_bloom_filter_size_bytes(bloom_filter_size_bytes); + return *this; + } + TRuntimeFilterDescBuilder& set_build_bf_exactly(bool build_bf_exactly) { + _desc.__set_build_bf_exactly(build_bf_exactly); + return *this; + } + TRuntimeFilterDesc& build() { return _desc; } + TRuntimeFilterDescBuilder(const TRuntimeFilterDescBuilder&) = delete; + void operator=(const TRuntimeFilterDescBuilder&) = delete; + +private: + TRuntimeFilterDesc _desc; +}; + class TExchangeNodeBuilder { public: explicit TExchangeNodeBuilder() : _plan_node() {} @@ -269,7 +340,10 @@ class TSlotDescriptorBuilder { class TTypeDescBuilder { public: - explicit TTypeDescBuilder() : _desc() {} + explicit TTypeDescBuilder() : _desc() { + _desc.__set_result_is_nullable(false); + _desc.__set_is_nullable(false); + } TTypeDescBuilder& set_types(TTypeNode type_node) { _desc.types.push_back(type_node); @@ -423,6 +497,7 @@ class TExprNodeBuilder { _expr_node.__set_type(type); _expr_node.__set_num_children(num_children); _expr_node.__set_opcode(opcode); + _expr_node.__set_is_nullable(false); } explicit TExprNodeBuilder(TExprNodeType::type node_type, TTypeDesc&& type, int num_children, TExprOpcode::type opcode = TExprOpcode::INVALID_OPCODE) @@ -477,4 +552,27 @@ class TEqJoinConditionBuilder { TEqJoinCondition _eq_conjuncts; }; +class TRuntimeFilterParamsBuilder { +public: + explicit TRuntimeFilterParamsBuilder( + TNetworkAddress runtime_filter_merge_addr = TNetworkAddress(), + std::map> rid_to_target_param = {}, + std::map rid_to_runtime_filter = {}, + std::map runtime_filter_builder_num = {}, + std::map> rid_to_target_paramv2 = {}) + : _params() { + _params.__set_runtime_filter_merge_addr(runtime_filter_merge_addr); + _params.__set_rid_to_target_param(rid_to_target_param); + _params.__set_rid_to_runtime_filter(rid_to_runtime_filter); + _params.__set_runtime_filter_builder_num(runtime_filter_builder_num); + _params.__set_rid_to_target_paramv2(rid_to_target_paramv2); + } + TRuntimeFilterParams& build() { return _params; } + TRuntimeFilterParamsBuilder(const TRuntimeFilterParamsBuilder&) = delete; + void operator=(const TRuntimeFilterParamsBuilder&) = delete; + +private: + TRuntimeFilterParams _params; +}; + } // namespace doris::pipeline diff --git a/gensrc/thrift/PaloInternalService.thrift b/gensrc/thrift/PaloInternalService.thrift index 70db19bfa33581..9ab20842c25e8f 100644 --- a/gensrc/thrift/PaloInternalService.thrift +++ b/gensrc/thrift/PaloInternalService.thrift @@ -392,18 +392,21 @@ struct TRuntimeFilterTargetParamsV2 { } struct TRuntimeFilterParams { - // Runtime filter merge instance address + // Runtime filter merge instance address. Used if this filter has a remote target 1: optional Types.TNetworkAddress runtime_filter_merge_addr // deprecated 2: optional map> rid_to_target_param // Runtime filter ID to the runtime filter desc + // Used if this filter has a remote target 3: optional map rid_to_runtime_filter // Number of Runtime filter producers + // Used if this filter has a remote target 4: optional map runtime_filter_builder_num + // Used if this filter has a remote target 5: optional map> rid_to_target_paramv2 } diff --git a/gensrc/thrift/PlanNodes.thrift b/gensrc/thrift/PlanNodes.thrift index be920c2baf50f6..c7f8d247d7c1b1 100644 --- a/gensrc/thrift/PlanNodes.thrift +++ b/gensrc/thrift/PlanNodes.thrift @@ -1252,7 +1252,7 @@ struct TRuntimeFilterDesc { // The order of Expr in join predicate 3: required i32 expr_order - // Map of target node id to the target expr + // Map of target node id to the target expr. Used by consumer 4: required map planId_to_target_expr // Indicates if the source join node of this filter is a broadcast or From ad2ef5e68db79b269ee934f9818004356a137486 Mon Sep 17 00:00:00 2001 From: airborne12 Date: Thu, 16 Jan 2025 10:17:30 +0800 Subject: [PATCH 06/26] [fix](test) add bloomfilter to nonConcurrent (#47038) --- regression-test/suites/bloom_filter_p0/test_bloom_filter.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regression-test/suites/bloom_filter_p0/test_bloom_filter.groovy b/regression-test/suites/bloom_filter_p0/test_bloom_filter.groovy index ff8710c5998fac..9fdd88b283752e 100644 --- a/regression-test/suites/bloom_filter_p0/test_bloom_filter.groovy +++ b/regression-test/suites/bloom_filter_p0/test_bloom_filter.groovy @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -suite("test_bloom_filter") { +suite("test_bloom_filter","nonConcurrent") { // todo: test bloom filter, such alter table bloom filter, create table with bloom filter sql "SHOW ALTER TABLE COLUMN" From 04fc0d41a98d4d294a499e004f0d9801cdc81902 Mon Sep 17 00:00:00 2001 From: Xin Liao Date: Thu, 16 Jan 2025 10:35:55 +0800 Subject: [PATCH 07/26] [opt](regression-test) Adjust the stream load timeout check of the regression framework (#47037) stream load may cost more time than expected in regression test, because of case run in parallel. So we allow stream load cost more time, use 3 * time as threshold. --- .../doris/regression/action/StreamLoadAction.groovy | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/StreamLoadAction.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/StreamLoadAction.groovy index 97b6d038fa701c..b0a811a2e2fdaa 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/StreamLoadAction.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/StreamLoadAction.groovy @@ -367,10 +367,12 @@ class StreamLoadAction implements SuiteAction { if (time > 0) { long elapsed = endTime - startTime - if (elapsed > time) { - log.info("Stream load consums more time than expected, elapsed ${elapsed} ms, expect ${time} ms") - } else { - log.info("Stream load consums time elapsed ${elapsed} ms, expect ${time} ms") + try { + // stream load may cost more time than expected in regression test, because of case run in parallel. + // So we allow stream load cost more time, use 3 * time as threshold. + Assert.assertTrue("Stream load Expect elapsed <= 3 * ${time}, but meet ${elapsed}", elapsed <= 3 * time) + } catch (Throwable t) { + throw new IllegalStateException("Stream load Expect elapsed <= 3 * ${time}, but meet ${elapsed}") } } } From 8880c0081de95ef0dc5ffe66dbaea7b0058dabc4 Mon Sep 17 00:00:00 2001 From: shuke Date: Thu, 16 Jan 2025 10:51:16 +0800 Subject: [PATCH 08/26] [regression-test](fix) fix test_select_column_auth.groovy bug (#46689) --- .../auth_p0/test_select_column_auth.groovy | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/regression-test/suites/auth_p0/test_select_column_auth.groovy b/regression-test/suites/auth_p0/test_select_column_auth.groovy index 296b47975fb707..ba1511c77c0a47 100644 --- a/regression-test/suites/auth_p0/test_select_column_auth.groovy +++ b/regression-test/suites/auth_p0/test_select_column_auth.groovy @@ -54,7 +54,22 @@ suite("test_select_column_auth","p0,auth") { sql """create view ${dbName}.${mv_name} as select * from ${dbName}.${tableName};""" sql """alter table ${dbName}.${tableName} add rollup ${rollup_name}(username)""" - sleep(5 * 1000) + + for (int i = 0; i < 20; ++i) { + def r = sql_return_maparray """show alter table rollup where TableName='${tableName}' order by createtime desc limit 1""" + if (r.size() > 0) { + if ( r[0]["State"] == "FINISHED") { + break + } else if (r[0]["State"] == "CANCELLED") { + assertTrue(1==0) + } else { + sleep(1000) + } + } + sleep(1000) + } + sleep(1000) + createMV("""create materialized view ${mtmv_name} as select username from ${dbName}.${tableName}""") sleep(5 * 1000) sql """CREATE MATERIALIZED VIEW ${dbName}.${mtmv_name} From fc1f68370868e6dc5a31dd182b90c5d5b697b7e9 Mon Sep 17 00:00:00 2001 From: huanghaibin Date: Thu, 16 Jan 2025 11:13:05 +0800 Subject: [PATCH 09/26] [fix](cloud-mow)Fix case test_schema_change_with_mow_txn_conflict (#47044) fix regression case test_schema_change_with_mow_txn_conflict --- be/src/cloud/cloud_schema_change_job.cpp | 2 +- .../test_schema_change_with_mow_txn_conflict.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/be/src/cloud/cloud_schema_change_job.cpp b/be/src/cloud/cloud_schema_change_job.cpp index 81b07ff7e4c843..d12bcdaa01e21c 100644 --- a/be/src/cloud/cloud_schema_change_job.cpp +++ b/be/src/cloud/cloud_schema_change_job.cpp @@ -395,7 +395,7 @@ Status CloudSchemaChangeJob::_convert_historical_rowsets(const SchemaChangeParam DBUG_EXECUTE_IF("CloudSchemaChangeJob::_convert_historical_rowsets.test_conflict", { std::srand(static_cast(std::time(nullptr))); int random_value = std::rand() % 100; - if (random_value < 50) { + if (random_value < 20) { return Status::Error("test txn conflict"); } }); diff --git a/regression-test/suites/schema_change_p0/test_schema_change_with_mow_txn_conflict.groovy b/regression-test/suites/schema_change_p0/test_schema_change_with_mow_txn_conflict.groovy index 448ae3bc0fbea1..f2130956ad6b4e 100644 --- a/regression-test/suites/schema_change_p0/test_schema_change_with_mow_txn_conflict.groovy +++ b/regression-test/suites/schema_change_p0/test_schema_change_with_mow_txn_conflict.groovy @@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit import org.awaitility.Awaitility -suite("test_schema_change_with_mow_txn_conflict", "p0") { +suite("test_schema_change_with_mow_txn_conflict", "nonConcurrent") { def customFeConfig = [ schema_change_max_retry_time: 10 ] From 8916860fa967b278d1eecf14805e9c2f27d78aff Mon Sep 17 00:00:00 2001 From: shee <13843187+qzsee@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:40:28 +0800 Subject: [PATCH 10/26] [Bug](mtmv) update mapping relation when mtmv occur alter (#46983) ### What problem does this PR solve? CREATE MATERIALIZED VIEW mv_a REFRESH COMPLETE ON commit DISTRIBUTED BY HASH(`sid`) BUCKETS 1 PROPERTIES ( "replication_allocation" = "tag.location.default: 1" ) AS select * from stu limit 1; CREATE MATERIALIZED VIEW mv_aa REFRESH COMPLETE ON commit DISTRIBUTED BY HASH(`sid`) BUCKETS 1 PROPERTIES ( "replication_allocation" = "tag.location.default: 1" ) AS select * from mv_a limit 1; alter MATERIALIZED VIEW mv_a RENAME mv_b CREATE TABLE `mv_a` ( `sid` int(32) NULL, `sname` varchar(32) NULL, ) ENGINE=OLAP DUPLICATE KEY(`sid`) DISTRIBUTED BY HASH(`sid`) BUCKETS 1 PROPERTIES ( "replication_allocation" = "tag.location.default: 1" ); CREATE MATERIALIZED VIEW mv_a REFRESH COMPLETE ON commit DISTRIBUTED BY HASH(`sid`) BUCKETS 1 PROPERTIES ( "replication_allocation" = "tag.location.default: 1" ) AS select * from stu limit 1; select * from stu limit 1; java.lang.ClassCastException: class org.apache.doris.catalog.OlapTable cannot be cast to class org.apache.doris.catalog.MTMV (org.apache.doris.catalog.OlapTable and org.apache.doris.catalog.MTMV are in unnamed module of loader 'app') --------- Co-authored-by: garenshi --- .../java/org/apache/doris/alter/Alter.java | 4 + .../doris/mtmv/MTMVRelationManager.java | 4 + .../org/apache/doris/mtmv/AlterMTMVTest.java | 79 +++++++++++++++++++ .../doris/utframe/TestWithFeService.java | 19 +++++ 4 files changed, 106 insertions(+) create mode 100644 fe/fe-core/src/test/java/org/apache/doris/mtmv/AlterMTMVTest.java diff --git a/fe/fe-core/src/main/java/org/apache/doris/alter/Alter.java b/fe/fe-core/src/main/java/org/apache/doris/alter/Alter.java index c8cd28c8617506..3b59c3bac9b056 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/alter/Alter.java +++ b/fe/fe-core/src/main/java/org/apache/doris/alter/Alter.java @@ -699,6 +699,10 @@ private void processReplaceTable(Database db, OlapTable origTable, List allMTMVs = relationManager.getAllMTMVs(Lists.newArrayList(new BaseTableInfo(table))); + boolean hasMvA = false; + boolean hasMvB = false; + for (MTMV mtmv : allMTMVs) { + if ("mv_a".equals(mtmv.getName())) { + hasMvA = true; + } + if ("mv_b".equals(mtmv.getName())) { + hasMvB = true; + } + } + Assertions.assertFalse(hasMvA); + Assertions.assertTrue(hasMvB); + + + createTable("CREATE TABLE `stu1` (`sid` int(32) NULL, `sname` varchar(32) NULL)\n" + + "ENGINE=OLAP\n" + + "DUPLICATE KEY(`sid`)\n" + + "DISTRIBUTED BY HASH(`sid`) BUCKETS 1\n" + + "PROPERTIES ('replication_allocation' = 'tag.location.default: 1')"); + + DdlException exception = Assertions.assertThrows(DdlException.class, () -> + alterTableSync("ALTER TABLE stu1 REPLACE WITH TABLE mv_b PROPERTIES('swap' = 'true')")); + Assertions.assertEquals("errCode = 2, detailMessage = replace table[mv_b] cannot be a materialized view", exception.getMessage()); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java index c24703bf6f6fa3..35ac71314d414e 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java +++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java @@ -66,6 +66,7 @@ import org.apache.doris.nereids.rules.RuleType; import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator; import org.apache.doris.nereids.trees.plans.commands.AddConstraintCommand; +import org.apache.doris.nereids.trees.plans.commands.AlterMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.CreateMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.CreateTableCommand; import org.apache.doris.nereids.trees.plans.commands.DropConstraintCommand; @@ -621,6 +622,11 @@ public void createDatabase(String db) throws Exception { Env.getCurrentEnv().createDb(createDbStmt); } + public void createDatabaseAndUse(String db) throws Exception { + createDatabase(db); + useDatabase(db); + } + public void createDatabaseWithSql(String createDbSql) throws Exception { CreateDbStmt createDbStmt = (CreateDbStmt) parseAndAnalyzeStmt(createDbSql); Env.getCurrentEnv().createDb(createDbStmt); @@ -845,6 +851,19 @@ protected void createMv(String sql) throws Exception { Thread.sleep(100); } + protected void alterMv(String sql) throws Exception { + NereidsParser nereidsParser = new NereidsParser(); + LogicalPlan parsed = nereidsParser.parseSingle(sql); + StmtExecutor stmtExecutor = new StmtExecutor(connectContext, sql); + if (parsed instanceof AlterMTMVCommand) { + ((AlterMTMVCommand) parsed).run(connectContext, stmtExecutor); + } + checkAlterJob(); + // waiting table state to normal + Thread.sleep(1000); + } + + protected void createMvByNereids(String sql) throws Exception { new MockUp() { @Mock From f3d8bce5e3c74b94743649f433f25d7bc6a07431 Mon Sep 17 00:00:00 2001 From: morrySnow Date: Thu, 16 Jan 2025 11:58:17 +0800 Subject: [PATCH 11/26] [fix](Nereids) insert lock all target tables (#47033) ### What problem does this PR solve? Related PR: #45045 Problem Summary: we get target table twice, lock them all --- .../plans/commands/insert/InsertIntoTableCommand.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java index c65ce4a282ddf8..76c72f82f90552 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java @@ -28,6 +28,7 @@ import org.apache.doris.common.UserException; import org.apache.doris.common.profile.ProfileManager.ProfileType; import org.apache.doris.common.util.DebugUtil; +import org.apache.doris.common.util.MetaLockUtils; import org.apache.doris.datasource.hive.HMSExternalTable; import org.apache.doris.datasource.iceberg.IcebergExternalTable; import org.apache.doris.datasource.jdbc.JdbcExternalTable; @@ -72,6 +73,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -184,7 +186,9 @@ public AbstractInsertExecutor initPlan(ConnectContext ctx, StmtExecutor stmtExec // lock after plan and check does table's schema changed to ensure we lock table order by id. TableIf newestTargetTableIf = RelationUtil.getTable(qualifiedTargetTableName, ctx.getEnv()); - newestTargetTableIf.readLock(); + List targetTables = Lists.newArrayList(targetTableIf, newestTargetTableIf); + targetTables.sort(Comparator.comparing(TableIf::getId)); + MetaLockUtils.readLockTables(targetTables); try { if (targetTableIf.getId() != newestTargetTableIf.getId()) { LOG.warn("insert plan failed {} times. query id is {}. table id changed from {} to {}", @@ -205,9 +209,9 @@ public AbstractInsertExecutor initPlan(ConnectContext ctx, StmtExecutor stmtExec buildResult.physicalSink ); } - newestTargetTableIf.readUnlock(); + MetaLockUtils.readUnlockTables(targetTables); } catch (Throwable e) { - newestTargetTableIf.readUnlock(); + MetaLockUtils.readUnlockTables(targetTables); // the abortTxn in onFail need to acquire table write lock if (insertExecutor != null) { insertExecutor.onFail(e); From 01981e1077db61a540dfe1f5311171c92ba16fe4 Mon Sep 17 00:00:00 2001 From: Sun Chenyang Date: Thu, 16 Jan 2025 12:08:57 +0800 Subject: [PATCH 12/26] [fix](json) fix parsing double in jsonb (#46977) Problem Summary: 1. When a double exceeds the precision that can be represented by a double type in simdjson, it gets converted to 0. 2. The correct approach, should be to truncate the double value instead. --- be/src/util/jsonb_parser_simd.h | 22 ++++- .../data/json_p0/test_json_double.csv | 2 + .../data/json_p0/test_json_load_double.out | 11 +++ .../json_p0/test_json_load_double.groovy | 87 +++++++++++++++++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 regression-test/data/json_p0/test_json_double.csv create mode 100644 regression-test/data/json_p0/test_json_load_double.out create mode 100644 regression-test/suites/json_p0/test_json_load_double.groovy diff --git a/be/src/util/jsonb_parser_simd.h b/be/src/util/jsonb_parser_simd.h index 96ce866f74e256..8684fc192188e6 100644 --- a/be/src/util/jsonb_parser_simd.h +++ b/be/src/util/jsonb_parser_simd.h @@ -136,7 +136,7 @@ class JsonbParserTSIMD { break; } case simdjson::ondemand::json_type::number: { - write_number(doc.get_number()); + write_number(doc.get_number(), doc.raw_json_token()); break; } } @@ -172,7 +172,7 @@ class JsonbParserTSIMD { break; } case simdjson::ondemand::json_type::number: { - write_number(value.get_number()); + write_number(value.get_number(), value.raw_json_token()); break; } case simdjson::ondemand::json_type::object: { @@ -290,9 +290,23 @@ class JsonbParserTSIMD { } } - void write_number(simdjson::ondemand::number num) { + void write_number(simdjson::ondemand::number num, std::string_view raw_string) { if (num.is_double()) { - if (writer_.writeDouble(num.get_double()) == 0) { + double number = num.get_double(); + // When a double exceeds the precision that can be represented by a double type in simdjson, it gets converted to 0. + // The correct approach, should be to truncate the double value instead. + if (number == 0) { + StringParser::ParseResult result; + number = StringParser::string_to_float(raw_string.data(), raw_string.size(), + &result); + if (result != StringParser::PARSE_SUCCESS) { + err_ = JsonbErrType::E_INVALID_NUMBER; + LOG(WARNING) << "invalid number, raw string is: " << raw_string; + return; + } + } + + if (writer_.writeDouble(number) == 0) { err_ = JsonbErrType::E_OUTPUT_FAIL; LOG(WARNING) << "writeDouble failed"; return; diff --git a/regression-test/data/json_p0/test_json_double.csv b/regression-test/data/json_p0/test_json_double.csv new file mode 100644 index 00000000000000..e928633659bc10 --- /dev/null +++ b/regression-test/data/json_p0/test_json_double.csv @@ -0,0 +1,2 @@ +2 {"rebookProfit":3.729672759600005773616970827788463793694972991943359375} +3 3.729672759600005773616970827788463793694972991943359375 \ No newline at end of file diff --git a/regression-test/data/json_p0/test_json_load_double.out b/regression-test/data/json_p0/test_json_load_double.out new file mode 100644 index 00000000000000..621c3a0910e26e --- /dev/null +++ b/regression-test/data/json_p0/test_json_load_double.out @@ -0,0 +1,11 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !sql_select_src -- +3.72967275960001 +\N + +-- !sql_select_dst -- +1 3.72967275960001 +1 {"rebookProfit":3.72967275960001} +2 {"rebookProfit":3.72967275960001} +3 3.72967275960001 + diff --git a/regression-test/suites/json_p0/test_json_load_double.groovy b/regression-test/suites/json_p0/test_json_load_double.groovy new file mode 100644 index 00000000000000..8c692e3e71da3d --- /dev/null +++ b/regression-test/suites/json_p0/test_json_load_double.groovy @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +import org.codehaus.groovy.runtime.IOGroovyMethods + +suite("test_json_load_double", "p0") { + + def srcTable = "stringTable" + def dstTable = "jsonTable" + def dataFile = "test_json_double.csv" + + sql """ DROP TABLE IF EXISTS ${srcTable} """ + sql """ DROP TABLE IF EXISTS ${dstTable} """ + + sql """ + CREATE TABLE IF NOT EXISTS ${srcTable} ( + id INT not null, + v STRING not null + ) + DUPLICATE KEY(id) + DISTRIBUTED BY HASH(id) BUCKETS 1 + PROPERTIES("replication_num" = "1"); + """ + + sql """ + CREATE TABLE IF NOT EXISTS ${dstTable} ( + id INT not null, + j JSON not null + ) + DUPLICATE KEY(id) + DISTRIBUTED BY HASH(id) BUCKETS 1 + PROPERTIES("replication_num" = "1"); + """ + + sql """ + insert into ${srcTable} values(1,'{"rebookProfit":3.729672759600005773616970827788463793694972991943359375}'); + """ + + sql """ + insert into ${srcTable} values(1,'3.729672759600005773616970827788463793694972991943359375'); + """ + + sql """ insert into ${dstTable} select * from ${srcTable} """ + + // load the json data from csv file + streamLoad { + table dstTable + + file dataFile // import csv file + time 10000 // limit inflight 10s + set 'strict_mode', 'true' + + // if declared a check callback, the default check condition will ignore. + // So you must check all condition + check { result, exception, startTime, endTime -> + if (exception != null) { + throw exception + } + log.info("Stream load result: ${result}".toString()) + def json = parseJson(result) + + assertEquals("success", json.Status.toLowerCase()) + assertEquals(2, json.NumberTotalRows) + assertEquals(2, json.NumberLoadedRows) + assertTrue(json.LoadBytes > 0) + log.info("url: " + json.ErrorURL) + } + } + + qt_sql_select_src """ select jsonb_extract(v, '\$.rebookProfit') from ${srcTable} """ + qt_sql_select_dst """ select * from ${dstTable} """ + +} From 3749b7b8e0a43978910a078aaf59b10447cc2672 Mon Sep 17 00:00:00 2001 From: Pxl Date: Thu, 16 Jan 2025 12:27:27 +0800 Subject: [PATCH 13/26] [Bug](runtime-filter) fix bloom_filter_func use after free on get_build_bf_cardinality (#47034) ### What problem does this PR solve? fix bloom_filter_func use after free on get_build_bf_cardinality In https://github.com/apache/doris/pull/46789 we release memory after rf disabled, but fuzzy_disable_runtime_filter_in_be will disable all rf at join_operator::open() and lead use after free ```cpp /root/doris/be/src/exprs/runtime_filter.cpp:376:49: runtime error: member call on null pointer of type 'doris::BloomFilterFuncBase' 19:06:00 *** Query id: a1c642ea781748a7-964e332695678128 *** 19:06:00 *** is nereids: 1 *** 19:06:00 *** tablet id: 0 *** 19:06:00 *** Aborted at 1736938943 (unix time) try "date -d @1736938943" if you are using GNU date *** 19:06:00 *** Current BE git commitID: e3f29ba692 *** 19:06:00 *** SIGSEGV address not mapped to object (@0x0) received by PID 1874761 (TID 1877271 OR 0x7fb56c5c7700) from PID 0; stack trace: *** 19:06:00 0# doris::signal::(anonymous namespace)::FailureSignalHandler(int, siginfo_t*, void*) at /root/doris/be/src/common/signal_handler.h:421 19:06:00 1# PosixSignals::chained_handler(int, siginfo_t*, void*) [clone .part.0] in /usr/lib/jvm/java-17-openjdk-amd64/lib/server/libjvm.so 19:06:00 2# JVM_handle_linux_signal in /usr/lib/jvm/java-17-openjdk-amd64/lib/server/libjvm.so 19:06:00 3# 0x00007FB7232D9090 in /lib/x86_64-linux-gnu/libc.so.6 19:06:00 4# doris::RuntimePredicateWrapper::get_build_bf_cardinality() const at /root/doris/be/src/exprs/runtime_filter.cpp:376 19:06:00 5# doris::IRuntimeFilter::need_sync_filter_size() at /root/doris/be/src/exprs/runtime_filter.cpp:1634 19:06:00 6# doris::VRuntimeFilterSlots::send_filter_size(doris::RuntimeState*, unsigned long, std::shared_ptr) at /root/doris/be/src/exprs/runtime_filter_slots.h:50 19:06:00 7# doris::pipeline::HashJoinBuildSinkLocalState::close(doris::RuntimeState*, doris::Status) at /root/doris/be/src/pipeline/exec/hashjoin_build_sink.cpp:156 19:06:00 8# doris::pipeline::DataSinkOperatorXBase::close(doris::RuntimeState*, doris::Status) at /root/doris/be/src/pipeline/exec/operator.h:497 19:06:00 9# doris::pipeline::PipelineTask::close(doris::Status, bool) at /root/doris/be/src/pipeline/pipeline_task.cpp:468 19:06:00 10# doris::pipeline::_close_task(doris::pipeline::PipelineTask*, doris::Status) at /root/doris/be/src/pipeline/task_scheduler.cpp:90 19:06:00 11# doris::pipeline::TaskScheduler::_do_work(int) at /root/doris/be/src/pipeline/task_scheduler.cpp:175 ``` ### Check List (For Author) - Test - [ ] Regression test - [ ] Unit Test - [ ] Manual test (add detailed scripts or steps below) - [x] No need to test or manual test. Explain why: - [ ] This is a refactor/code format and no logic has been changed. - [x] Previous test can cover this change. - [ ] No code files have been changed. - [ ] Other reason - Behavior changed: - [x] No. - [ ] Yes. - Does this need documentation? - [x] No. - [ ] Yes. ### Check List (For Reviewer who merge this PR) - [ ] Confirm the release note - [ ] Confirm test cases - [ ] Confirm document - [ ] Add branch pick label --- be/src/exprs/runtime_filter.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/be/src/exprs/runtime_filter.cpp b/be/src/exprs/runtime_filter.cpp index 33cc155fc6fba8..58ec8cce5c4993 100644 --- a/be/src/exprs/runtime_filter.cpp +++ b/be/src/exprs/runtime_filter.cpp @@ -867,11 +867,12 @@ void IRuntimeFilter::update_runtime_filter_type_to_profile(uint64_t local_merge_ std::string IRuntimeFilter::debug_string() const { return fmt::format( - "RuntimeFilter: (id = {}, type = {}, is_broadcast: {}, ignored: {}, " + "RuntimeFilter: (id = {}, type = {}, is_broadcast: {}, ignored: {}, disabled: {}, " "build_bf_cardinality: {}, dependency: {}, synced_size: {}, has_local_target: {}, " "has_remote_target: {}, error_msg: [{}]", _filter_id, to_string(_runtime_filter_type), _is_broadcast_join, - _wrapper->_context->ignored, _wrapper->get_build_bf_cardinality(), + _wrapper->_context->ignored, _wrapper->_context->disabled, + _wrapper->get_build_bf_cardinality(), _dependency ? _dependency->debug_string() : "none", _synced_size, _has_local_target, _has_remote_target, _wrapper->_context->err_msg); } @@ -1182,11 +1183,7 @@ Status RuntimePredicateWrapper::init_bloom_filter(const size_t build_bf_cardinal } bool RuntimePredicateWrapper::get_build_bf_cardinality() const { - if (_filter_type == RuntimeFilterType::BLOOM_FILTER || - _filter_type == RuntimeFilterType::IN_OR_BLOOM_FILTER) { - return _context->bloom_filter_func->get_build_bf_cardinality(); - } - return false; + return _context->bloom_filter_func && _context->bloom_filter_func->get_build_bf_cardinality(); } void RuntimePredicateWrapper::insert_to_bloom_filter(BloomFilterFuncBase* bloom_filter) const { From a956a52809afa47f1fc2f77e757a4e8a33041fd2 Mon Sep 17 00:00:00 2001 From: abmdocrt Date: Thu, 16 Jan 2025 12:51:13 +0800 Subject: [PATCH 14/26] [fix](recycler) Fix premature exit recycling when there is an invalid storage vault (#46798) --- cloud/src/recycler/recycler.cpp | 221 +++++++++++++++++++++++++------- cloud/test/recycler_test.cpp | 131 +++++++++++++++++++ 2 files changed, 308 insertions(+), 44 deletions(-) diff --git a/cloud/src/recycler/recycler.cpp b/cloud/src/recycler/recycler.cpp index 307528011ea88c..62a161e3edbe52 100644 --- a/cloud/src/recycler/recycler.cpp +++ b/cloud/src/recycler/recycler.cpp @@ -24,12 +24,14 @@ #include #include +#include #include #include #include #include #include "common/stopwatch.h" +#include "meta-service/meta_service.h" #include "meta-service/meta_service_schema.h" #include "meta-service/txn_kv.h" #include "meta-service/txn_kv_error.h" @@ -249,8 +251,9 @@ void Recycler::recycle_callback() { auto instance_recycler = std::make_shared( txn_kv_, instance, _thread_pool_group, txn_lazy_committer_); - if (instance_recycler->init() != 0) { - LOG(WARNING) << "failed to init instance recycler, instance_id=" << instance_id; + if (int r = instance_recycler->init(); r != 0) { + LOG(WARNING) << "failed to init instance recycler, instance_id=" << instance_id + << " ret=" << r; continue; } std::string recycle_job_key; @@ -258,6 +261,8 @@ void Recycler::recycle_callback() { int ret = prepare_instance_recycle_job(txn_kv_.get(), recycle_job_key, instance_id, ip_port_, config::recycle_interval_seconds * 1000); if (ret != 0) { // Prepare failed + LOG(WARNING) << "failed to prepare recycle_job, instance_id=" << instance_id + << " ret=" << ret; continue; } else { std::lock_guard lock(mtx_); @@ -276,7 +281,12 @@ void Recycler::recycle_callback() { std::lock_guard lock(mtx_); recycling_instance_map_.erase(instance_id); } - LOG_INFO("finish recycle instance").tag("instance_id", instance_id); + auto elpased_ms = + ctime_ms - + duration_cast(system_clock::now().time_since_epoch()).count(); + LOG_INFO("finish recycle instance") + .tag("instance_id", instance_id) + .tag("cost_ms", elpased_ms); } } @@ -523,35 +533,37 @@ int InstanceRecycler::init_storage_vault_accessors() { LOG(WARNING) << "malformed storage vault, unable to deserialize key=" << hex(k); return -1; } + TEST_SYNC_POINT_CALLBACK("InstanceRecycler::init_storage_vault_accessors.mock_vault", + &accessor_map_, &vault); if (vault.has_hdfs_info()) { auto accessor = std::make_shared(vault.hdfs_info()); int ret = accessor->init(); if (ret != 0) { LOG(WARNING) << "failed to init hdfs accessor. instance_id=" << instance_id_ - << " resource_id=" << vault.id() << " name=" << vault.name(); - return ret; + << " resource_id=" << vault.id() << " name=" << vault.name() + << " hdfs_vault=" << vault.hdfs_info().ShortDebugString(); + continue; } accessor_map_.emplace(vault.id(), std::move(accessor)); } else if (vault.has_obj_info()) { -#ifdef UNIT_TEST - auto accessor = std::make_shared(); -#else auto s3_conf = S3Conf::from_obj_store_info(vault.obj_info()); if (!s3_conf) { - LOG(WARNING) << "failed to init object accessor, instance_id=" << instance_id_; - return -1; + LOG(WARNING) << "failed to init object accessor, invalid conf, instance_id=" + << instance_id_ << " s3_vault=" << vault.obj_info().ShortDebugString(); + continue; } std::shared_ptr accessor; int ret = S3Accessor::create(std::move(*s3_conf), &accessor); if (ret != 0) { LOG(WARNING) << "failed to init s3 accessor. instance_id=" << instance_id_ - << " resource_id=" << vault.id() << " name=" << vault.name(); - return ret; + << " resource_id=" << vault.id() << " name=" << vault.name() + << " ret=" << ret + << " s3_vault=" << vault.obj_info().ShortDebugString(); + continue; } -#endif accessor_map_.emplace(vault.id(), std::move(accessor)); } @@ -562,6 +574,13 @@ int InstanceRecycler::init_storage_vault_accessors() { return -1; } + if (accessor_map_.empty()) { + LOG(WARNING) << "no accessors for instance=" << instance_id_; + return -2; + } + LOG_INFO("finish init instance recycler number_accessors={} instance=", accessor_map_.size(), + instance_id_); + return 0; } @@ -1461,7 +1480,8 @@ int InstanceRecycler::delete_rowset_data(const std::vector int { + DCHECK(accessor_map_.count(*rid)) + << "uninitilized accessor, instance_id=" << instance_id_ + << " resource_id=" << resource_id << " path[0]=" << (*paths)[0]; + if (!accessor_map_.contains(*rid)) { + LOG_WARNING("delete rowset data accessor_map_ does not contains resouce id") + .tag("resource_id", resource_id) + .tag("instance_id", instance_id_); + return -1; + } auto& accessor = accessor_map_[*rid]; - DCHECK(accessor); return accessor->delete_files(*paths); }); } @@ -1576,7 +1604,9 @@ int InstanceRecycler::delete_rowset_data(const std::string& resource_id, int64_t if (it == accessor_map_.end()) { LOG_WARNING("instance has no such resource id") .tag("instance_id", instance_id_) - .tag("resource_id", resource_id); + .tag("resource_id", resource_id) + .tag("tablet_id", tablet_id) + .tag("rowset_id", rowset_id); return -1; } auto& accessor = it->second; @@ -1588,42 +1618,107 @@ int InstanceRecycler::recycle_tablet(int64_t tablet_id) { .tag("instance_id", instance_id_) .tag("tablet_id", tablet_id); + int ret = 0; auto start_time = steady_clock::now(); + // collect resource ids + std::string rs_key0 = meta_rowset_key({instance_id_, tablet_id, 0}); + std::string rs_key1 = meta_rowset_key({instance_id_, tablet_id + 1, 0}); + std::string recyc_rs_key0 = recycle_rowset_key({instance_id_, tablet_id, ""}); + std::string recyc_rs_key1 = recycle_rowset_key({instance_id_, tablet_id + 1, ""}); + + std::set resource_ids; + int64_t recycle_rowsets_number = 0; + int64_t recycle_segments_number = 0; + int64_t recycle_rowsets_data_size = 0; + int64_t recycle_rowsets_index_size = 0; + int64_t max_rowset_version = 0; + int64_t min_rowset_creation_time = INT64_MAX; + int64_t max_rowset_creation_time = 0; + int64_t min_rowset_expiration_time = INT64_MAX; + int64_t max_rowset_expiration_time = 0; + std::unique_ptr> defer_log_statistics((int*)0x01, [&](int*) { auto cost = duration(steady_clock::now() - start_time).count(); LOG_INFO("recycle the rowsets of dropped tablet finished, cost={}s", cost) .tag("instance_id", instance_id_) - .tag("tablet_id", tablet_id); + .tag("tablet_id", tablet_id) + .tag("recycle rowsets number", recycle_rowsets_number) + .tag("recycle segments number", recycle_segments_number) + .tag("all rowsets recycle data size", recycle_rowsets_data_size) + .tag("all rowsets recycle index size", recycle_rowsets_index_size) + .tag("max rowset version", max_rowset_version) + .tag("min rowset creation time", min_rowset_creation_time) + .tag("max rowset creation time", max_rowset_creation_time) + .tag("min rowset expiration time", min_rowset_expiration_time) + .tag("max rowset expiration time", max_rowset_expiration_time) + .tag("ret", ret); }); - // delete all rowset kv in this tablet - std::string rs_key0 = meta_rowset_key({instance_id_, tablet_id, 0}); - std::string rs_key1 = meta_rowset_key({instance_id_, tablet_id + 1, 0}); - std::string recyc_rs_key0 = recycle_rowset_key({instance_id_, tablet_id, ""}); - std::string recyc_rs_key1 = recycle_rowset_key({instance_id_, tablet_id + 1, ""}); - - int ret = 0; std::unique_ptr txn; if (txn_kv_->create_txn(&txn) != TxnErrorCode::TXN_OK) { - LOG(WARNING) << "failed to delete rowset kv of tablet " << tablet_id; + LOG_WARNING("failed to recycle tablet ") + .tag("tablet id", tablet_id) + .tag("instance_id", instance_id_) + .tag("reason", "failed to create txn"); ret = -1; } - txn->remove(rs_key0, rs_key1); - txn->remove(recyc_rs_key0, recyc_rs_key1); - - // remove delete bitmap for MoW table - std::string pending_key = meta_pending_delete_bitmap_key({instance_id_, tablet_id}); - txn->remove(pending_key); - std::string delete_bitmap_start = meta_delete_bitmap_key({instance_id_, tablet_id, "", 0, 0}); - std::string delete_bitmap_end = meta_delete_bitmap_key({instance_id_, tablet_id + 1, "", 0, 0}); - txn->remove(delete_bitmap_start, delete_bitmap_end); - - TxnErrorCode err = txn->commit(); - if (err != TxnErrorCode::TXN_OK) { - LOG(WARNING) << "failed to delete rowset kv of tablet " << tablet_id << ", err=" << err; + GetRowsetResponse resp; + std::string msg; + MetaServiceCode code = MetaServiceCode::OK; + // get rowsets in tablet + internal_get_rowset(txn.get(), 0, std::numeric_limits::max() - 1, instance_id_, + tablet_id, code, msg, &resp); + if (code != MetaServiceCode::OK) { + LOG_WARNING("failed to get rowsets of tablet when recycle tablet") + .tag("tablet id", tablet_id) + .tag("msg", msg) + .tag("code", code) + .tag("instance id", instance_id_); ret = -1; } + TEST_SYNC_POINT_CALLBACK("InstanceRecycler::recycle_tablet.create_rowset_meta", &resp); + + for (const auto& rs_meta : resp.rowset_meta()) { + if (!rs_meta.has_resource_id()) { + LOG_WARNING("rowset meta does not have a resource id, impossible!") + .tag("rs_meta", rs_meta.ShortDebugString()) + .tag("instance_id", instance_id_) + .tag("tablet_id", tablet_id); + return -1; + } + auto it = accessor_map_.find(rs_meta.resource_id()); + // possible if the accessor is not initilized correctly + if (it == accessor_map_.end()) [[unlikely]] { + LOG_WARNING( + "failed to find resource id when recycle tablet, skip this vault accessor " + "recycle process") + .tag("tablet id", tablet_id) + .tag("instance_id", instance_id_) + .tag("resource_id", rs_meta.resource_id()) + .tag("rowset meta pb", rs_meta.ShortDebugString()); + return -1; + } + recycle_rowsets_number += 1; + recycle_segments_number += rs_meta.num_segments(); + recycle_rowsets_data_size += rs_meta.data_disk_size(); + recycle_rowsets_index_size += rs_meta.index_disk_size(); + max_rowset_version = std::max(max_rowset_version, rs_meta.end_version()); + min_rowset_creation_time = std::min(min_rowset_creation_time, rs_meta.creation_time()); + max_rowset_creation_time = std::max(max_rowset_creation_time, rs_meta.creation_time()); + min_rowset_expiration_time = std::min(min_rowset_expiration_time, rs_meta.txn_expiration()); + max_rowset_expiration_time = std::max(max_rowset_expiration_time, rs_meta.txn_expiration()); + resource_ids.emplace(rs_meta.resource_id()); + } + + LOG_INFO("recycle tablet start to delete object") + .tag("instance id", instance_id_) + .tag("tablet id", tablet_id) + .tag("recycle tablet resource ids are", + std::accumulate(resource_ids.begin(), resource_ids.end(), std::string(), + [](const std::string& a, const std::string& b) { + return a.empty() ? b : a + "," + b; + })); SyncExecutor concurrent_delete_executor( _thread_pool_group.s3_producer_pool, @@ -1631,16 +1726,20 @@ int InstanceRecycler::recycle_tablet(int64_t tablet_id) { [](const int& ret) { return ret != 0; }); // delete all rowset data in this tablet - for (auto& [_, accessor] : accessor_map_) { - concurrent_delete_executor.add([&, accessor_ptr = &accessor]() { - if ((*accessor_ptr)->delete_directory(tablet_path_prefix(tablet_id)) != 0) { + // ATTN: there may be data leak if not all accessor initilized successfully + // partial data deleted if the tablet is stored cross-storage vault + // vault id is not attached to TabletMeta... + for (const auto& resource_id : resource_ids) { + concurrent_delete_executor.add([&, accessor_ptr = accessor_map_[resource_id]]() { + if (accessor_ptr->delete_directory(tablet_path_prefix(tablet_id)) != 0) { LOG(WARNING) << "failed to delete rowset data of tablet " << tablet_id - << " s3_path=" << accessor->uri(); + << " path=" << accessor_ptr->uri(); return -1; } return 0; }); } + bool finished = true; std::vector rets = concurrent_delete_executor.when_all(&finished); for (int r : rets) { @@ -1651,6 +1750,40 @@ int InstanceRecycler::recycle_tablet(int64_t tablet_id) { ret = finished ? ret : -1; + if (ret != 0) { // failed recycle tablet data + LOG_WARNING("ret!=0") + .tag("finished", finished) + .tag("ret", ret) + .tag("instance_id", instance_id_) + .tag("tablet_id", tablet_id); + return ret; + } + + txn.reset(); + if (txn_kv_->create_txn(&txn) != TxnErrorCode::TXN_OK) { + LOG_WARNING("failed to recycle tablet ") + .tag("tablet id", tablet_id) + .tag("instance_id", instance_id_) + .tag("reason", "failed to create txn"); + ret = -1; + } + // delete all rowset kv in this tablet + txn->remove(rs_key0, rs_key1); + txn->remove(recyc_rs_key0, recyc_rs_key1); + + // remove delete bitmap for MoW table + std::string pending_key = meta_pending_delete_bitmap_key({instance_id_, tablet_id}); + txn->remove(pending_key); + std::string delete_bitmap_start = meta_delete_bitmap_key({instance_id_, tablet_id, "", 0, 0}); + std::string delete_bitmap_end = meta_delete_bitmap_key({instance_id_, tablet_id + 1, "", 0, 0}); + txn->remove(delete_bitmap_start, delete_bitmap_end); + + TxnErrorCode err = txn->commit(); + if (err != TxnErrorCode::TXN_OK) { + LOG(WARNING) << "failed to delete rowset kv of tablet " << tablet_id << ", err=" << err; + ret = -1; + } + if (ret == 0) { // All object files under tablet have been deleted std::lock_guard lock(recycled_tablets_mtx_); @@ -2233,7 +2366,7 @@ int InstanceRecycler::abort_timeout_txn() { txn_info.set_status(TxnStatusPB::TXN_STATUS_ABORTED); txn_info.set_finish_time(current_time); txn_info.set_reason("timeout"); - VLOG_DEBUG << "txn_info=" << txn_info.DebugString(); + VLOG_DEBUG << "txn_info=" << txn_info.ShortDebugString(); txn_inf_val.clear(); if (!txn_info.SerializeToString(&txn_inf_val)) { LOG_WARNING("failed to serialize txn info").tag("key", hex(k)); @@ -2911,7 +3044,7 @@ int InstanceRecycler::recycle_expired_stage_objects() { const auto& old_obj = instance_info_.obj_info()[idx - 1]; auto s3_conf = S3Conf::from_obj_store_info(old_obj); if (!s3_conf) { - LOG(WARNING) << "failed to init s3_conf with obj_info=" << old_obj.DebugString(); + LOG(WARNING) << "failed to init s3_conf with obj_info=" << old_obj.ShortDebugString(); continue; } diff --git a/cloud/test/recycler_test.cpp b/cloud/test/recycler_test.cpp index e38d25aaa8420a..567d27f5d6f3c4 100644 --- a/cloud/test/recycler_test.cpp +++ b/cloud/test/recycler_test.cpp @@ -36,6 +36,7 @@ #include "meta-service/mem_txn_kv.h" #include "meta-service/meta_service.h" #include "meta-service/txn_kv_error.h" +#include "mock_accessor.h" #include "mock_resource_manager.h" #include "rate-limiter/rate_limiter.h" #include "recycler/checker.h" @@ -3263,4 +3264,134 @@ TEST(RecyclerTest, delete_rowset_data_without_inverted_index_storage_format) { } } +TEST(RecyclerTest, init_vault_accessor_failed_test) { + auto* sp = SyncPoint::get_instance(); + std::unique_ptr> defer((int*)0x01, [&sp](int*) { + sp->clear_all_call_backs(); + sp->clear_trace(); + sp->disable_processing(); + }); + + auto txn_kv = std::make_shared(); + EXPECT_EQ(txn_kv->init(), 0); + std::unique_ptr txn; + ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK); + std::string key; + std::string val; + + InstanceKeyInfo key_info {"test_instance"}; + instance_key(key_info, &key); + InstanceInfoPB instance; + instance.set_instance_id("GetObjStoreInfoTestInstance"); + // failed to init because S3Conf::from_obj_store_info() fails + { + ObjectStoreInfoPB obj_info; + StorageVaultPB vault; + obj_info.set_id("id"); + obj_info.set_ak("ak"); + obj_info.set_sk("sk"); + vault.mutable_obj_info()->MergeFrom(obj_info); + vault.set_name("test_failed_s3_vault_1"); + vault.set_id("failed_s3_1"); + instance.add_storage_vault_names(vault.name()); + instance.add_resource_ids(vault.id()); + txn->put(storage_vault_key({instance.instance_id(), "1"}), vault.SerializeAsString()); + } + + // succeed to init but unuseful + { + ObjectStoreInfoPB obj_info; + StorageVaultPB vault; + obj_info.set_id("id"); + obj_info.set_ak("ak"); + obj_info.set_sk("sk"); + obj_info.set_provider(ObjectStoreInfoPB_Provider_COS); + vault.mutable_obj_info()->MergeFrom(obj_info); + vault.set_name("test_failed_s3_vault_2"); + vault.set_id("failed_s3_2"); + instance.add_storage_vault_names(vault.name()); + instance.add_resource_ids(vault.id()); + instance.set_instance_id("GetObjStoreInfoTestInstance"); + txn->put(storage_vault_key({instance.instance_id(), "2"}), vault.SerializeAsString()); + } + + // failed to init because accessor->init() fails + { + HdfsBuildConf hdfs_build_conf; + StorageVaultPB vault; + hdfs_build_conf.set_fs_name("fs_name"); + hdfs_build_conf.set_user("root"); + HdfsVaultInfo hdfs_info; + hdfs_info.set_prefix("root_path"); + hdfs_info.mutable_build_conf()->MergeFrom(hdfs_build_conf); + vault.mutable_hdfs_info()->MergeFrom(hdfs_info); + vault.set_name("test_failed_hdfs_vault_1"); + vault.set_id("failed_hdfs_1"); + instance.add_storage_vault_names(vault.name()); + instance.add_resource_ids(vault.id()); + instance.set_instance_id("GetObjStoreInfoTestInstance"); + txn->put(storage_vault_key({instance.instance_id(), "3"}), vault.SerializeAsString()); + } + + auto accessor = std::make_shared(); + EXPECT_EQ(accessor->put_file("data/0/test.csv", ""), 0); + sp->set_call_back( + "InstanceRecycler::init_storage_vault_accessors.mock_vault", [&accessor](auto&& args) { + auto* map = try_any_cast< + std::unordered_map>*>( + args[0]); + auto* vault = try_any_cast(args[1]); + if (vault->name() == "test_success_hdfs_vault") { + map->emplace(vault->id(), accessor); + } + }); + sp->set_call_back("InstanceRecycler::recycle_tablet.create_rowset_meta", [](auto&& args) { + auto* resp = try_any_cast(args[0]); + auto* rs = resp->add_rowset_meta(); + rs->set_resource_id("failed_s3_2"); + rs = resp->add_rowset_meta(); + rs->set_resource_id("success_vault"); + }); + sp->enable_processing(); + + // succeed to init MockAccessor + { + HdfsBuildConf hdfs_build_conf; + StorageVaultPB vault; + hdfs_build_conf.set_fs_name("fs_name"); + hdfs_build_conf.set_user("root"); + HdfsVaultInfo hdfs_info; + hdfs_info.set_prefix("root_path"); + hdfs_info.mutable_build_conf()->MergeFrom(hdfs_build_conf); + vault.mutable_hdfs_info()->MergeFrom(hdfs_info); + vault.set_name("test_success_hdfs_vault"); + vault.set_id("success_vault"); + instance.add_storage_vault_names(vault.name()); + instance.add_resource_ids(vault.id()); + instance.set_instance_id("GetObjStoreInfoTestInstance"); + txn->put(storage_vault_key({instance.instance_id(), "4"}), vault.SerializeAsString()); + } + + val = instance.SerializeAsString(); + txn->put(key, val); + EXPECT_EQ(txn->commit(), TxnErrorCode::TXN_OK); + + EXPECT_EQ(accessor->exists("data/0/test.csv"), 0); + + InstanceRecycler recycler(txn_kv, instance, thread_group, + std::make_shared(txn_kv)); + EXPECT_EQ(recycler.init(), 0); + EXPECT_EQ(recycler.accessor_map_.size(), 2); + + // unuseful obj accessor + EXPECT_EQ(recycler.accessor_map_.at("failed_s3_2")->exists("data/0/test.csv"), -1); + // useful mock accessor + EXPECT_EQ(recycler.accessor_map_.at("success_vault")->exists("data/0/test.csv"), 0); + + // recycle tablet will fail because unuseful obj accessor can not connectted + EXPECT_EQ(recycler.recycle_tablet(0), -1); + // however, useful mock accessor can recycle tablet + EXPECT_EQ(recycler.accessor_map_.at("success_vault")->exists("data/0/test.csv"), 1); +} + } // namespace doris::cloud From c16567eb3cb9bb6140e0e5024c2bb39e822c6b29 Mon Sep 17 00:00:00 2001 From: "Mingyu Chen (Rayner)" Date: Thu, 16 Jan 2025 13:58:49 +0800 Subject: [PATCH 15/26] [opt](parquet) change parquet init footer read size to 48KB (#46904) ### What problem does this PR solve? Change the initial footer read size from 128KB to 48KB, to slightly reduce the read size. This is same as presto/trino, because typically, a 1GB parquet file usually has footer with size 30~40KB. And usercase shows when there are 30 thousands parquet file, the parse footer time can reduce from: ``` ParseFooterTime: avg 2s28ms, max 3s707ms, min 905.866ms ``` to ``` ParseFooterTime: avg 886.364ms, max 1s734ms, min 391.846ms ``` --- be/src/vec/exec/format/parquet/parquet_thrift_util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be/src/vec/exec/format/parquet/parquet_thrift_util.h b/be/src/vec/exec/format/parquet/parquet_thrift_util.h index b767f177f4a326..1c04d748ca54f7 100644 --- a/be/src/vec/exec/format/parquet/parquet_thrift_util.h +++ b/be/src/vec/exec/format/parquet/parquet_thrift_util.h @@ -34,7 +34,7 @@ namespace doris::vectorized { constexpr uint8_t PARQUET_VERSION_NUMBER[4] = {'P', 'A', 'R', '1'}; constexpr uint32_t PARQUET_FOOTER_SIZE = 8; -constexpr size_t INIT_META_SIZE = 128 * 1024; // 128k +constexpr size_t INIT_META_SIZE = 48 * 1024; // 48k static Status parse_thrift_footer(io::FileReaderSPtr file, FileMetaData** file_metadata, size_t* meta_size, io::IOContext* io_ctx) { From ccd169b05f175f4b9b8f72464db7d1d60b39fcf9 Mon Sep 17 00:00:00 2001 From: Petrichor Date: Thu, 16 Jan 2025 14:01:16 +0800 Subject: [PATCH 16/26] [feat](nereids)implement alterDatabaseSetQuotaCommand in nereids (#46459) Issue Number: close https://github.com/apache/doris/issues/42785 --- .../org/apache/doris/nereids/DorisParser.g4 | 4 +- fe/fe-core/src/main/cup/sql_parser.cup | 2 +- .../org/apache/doris/alter/QuotaType.java | 28 ++++++ .../analysis/AlterDatabaseQuotaStmt.java | 8 +- .../java/org/apache/doris/catalog/Env.java | 4 +- .../doris/datasource/InternalCatalog.java | 19 ++-- .../nereids/parser/LogicalPlanBuilder.java | 27 ++++++ .../doris/nereids/trees/plans/PlanType.java | 1 + .../alter/AlterDatabaseSetQuotaCommand.java | 91 +++++++++++++++++++ .../trees/plans/visitor/CommandVisitor.java | 5 + .../apache/doris/persist/DatabaseInfo.java | 2 +- .../analysis/AlterDatabaseQuotaStmtTest.java | 2 +- .../doris/persist/DatabaseInfoTest.java | 2 +- .../test_nereids_alter_database_set_quota.out | 31 +++++++ ...st_nereids_alter_database_set_quota.groovy | 37 ++++++++ 15 files changed, 237 insertions(+), 26 deletions(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/alter/QuotaType.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/alter/AlterDatabaseSetQuotaCommand.java create mode 100644 regression-test/data/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.out create mode 100644 regression-test/suites/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.groovy diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index 8c21461f1e68b7..d24a1f74f2c7ce 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -224,6 +224,8 @@ supportedAlterStatement dropRollupClause (COMMA dropRollupClause)* #alterTableDropRollup | ALTER TABLE name=multipartIdentifier SET LEFT_PAREN propertyItemList RIGHT_PAREN #alterTableProperties + | ALTER DATABASE name=identifier SET (DATA | REPLICA | TRANSACTION) + QUOTA (quota=identifier | INTEGER_VALUE) #alterDatabaseSetQuota | ALTER SYSTEM RENAME COMPUTE GROUP name=identifier newName=identifier #alterSystemRenameComputeGroup ; @@ -593,8 +595,6 @@ privilegeList unsupportedAlterStatement : ALTER SYSTEM alterSystemClause #alterSystem - | ALTER DATABASE name=identifier SET (DATA |REPLICA | TRANSACTION) - QUOTA INTEGER_VALUE identifier? #alterDatabaseSetQuota | ALTER DATABASE name=identifier SET PROPERTIES LEFT_PAREN propertyItemList RIGHT_PAREN #alterDatabaseProperties | ALTER CATALOG name=identifier SET PROPERTIES diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup index 7046a71c63bdee..1c9cf2452ce321 100644 --- a/fe/fe-core/src/main/cup/sql_parser.cup +++ b/fe/fe-core/src/main/cup/sql_parser.cup @@ -29,7 +29,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import org.apache.doris.analysis.AlterDatabaseQuotaStmt.QuotaType; +import org.apache.doris.alter.QuotaType; import org.apache.doris.analysis.ColumnNullableType; import org.apache.doris.analysis.SetOperationStmt.Qualifier; import org.apache.doris.analysis.SetOperationStmt.Operation; diff --git a/fe/fe-core/src/main/java/org/apache/doris/alter/QuotaType.java b/fe/fe-core/src/main/java/org/apache/doris/alter/QuotaType.java new file mode 100644 index 00000000000000..33d1a4d7262836 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/alter/QuotaType.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.alter; + +/** + * Represents the type of quota. + */ +public enum QuotaType { + NONE, + DATA, + REPLICA, + TRANSACTION +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterDatabaseQuotaStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterDatabaseQuotaStmt.java index fd41e3800f2804..ce607b1d2db532 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterDatabaseQuotaStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/AlterDatabaseQuotaStmt.java @@ -17,6 +17,7 @@ package org.apache.doris.analysis; +import org.apache.doris.alter.QuotaType; import org.apache.doris.catalog.Env; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; @@ -42,13 +43,6 @@ public class AlterDatabaseQuotaStmt extends DdlStmt implements NotFallbackInPars @SerializedName("q") private long quota; - public enum QuotaType { - NONE, - DATA, - REPLICA, - TRANSACTION - } - public AlterDatabaseQuotaStmt(String dbName, QuotaType quotaType, String quotaValue) { this.dbName = dbName; this.quotaType = quotaType; diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java index 9f1fc330551267..a4e2778cb5abfc 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java @@ -21,6 +21,7 @@ import org.apache.doris.alter.AlterJobV2; import org.apache.doris.alter.AlterJobV2.JobType; import org.apache.doris.alter.MaterializedViewHandler; +import org.apache.doris.alter.QuotaType; import org.apache.doris.alter.SchemaChangeHandler; import org.apache.doris.alter.SystemHandler; import org.apache.doris.analysis.AddPartitionClause; @@ -36,7 +37,6 @@ import org.apache.doris.analysis.AdminSetTableStatusStmt; import org.apache.doris.analysis.AlterDatabasePropertyStmt; import org.apache.doris.analysis.AlterDatabaseQuotaStmt; -import org.apache.doris.analysis.AlterDatabaseQuotaStmt.QuotaType; import org.apache.doris.analysis.AlterDatabaseRename; import org.apache.doris.analysis.AlterMultiPartitionClause; import org.apache.doris.analysis.AlterSystemStmt; @@ -3319,7 +3319,7 @@ public void replayRecoverDatabase(RecoverInfo info) { } public void alterDatabaseQuota(AlterDatabaseQuotaStmt stmt) throws DdlException { - getInternalCatalog().alterDatabaseQuota(stmt); + getInternalCatalog().alterDatabaseQuota(stmt.getDbName(), stmt.getQuotaType(), stmt.getQuota()); } public void replayAlterDatabaseQuota(String dbName, long quota, QuotaType quotaType) throws MetaNotFoundException { diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java index 5b1ee38c64da08..1508675b2e28b3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java @@ -17,13 +17,12 @@ package org.apache.doris.datasource; +import org.apache.doris.alter.QuotaType; import org.apache.doris.analysis.AddPartitionClause; import org.apache.doris.analysis.AddPartitionLikeClause; import org.apache.doris.analysis.AddRollupClause; import org.apache.doris.analysis.AlterClause; import org.apache.doris.analysis.AlterDatabasePropertyStmt; -import org.apache.doris.analysis.AlterDatabaseQuotaStmt; -import org.apache.doris.analysis.AlterDatabaseQuotaStmt.QuotaType; import org.apache.doris.analysis.AlterMultiPartitionClause; import org.apache.doris.analysis.Analyzer; import org.apache.doris.analysis.ColumnDef; @@ -762,21 +761,19 @@ public void replayRecoverDatabase(RecoverInfo info) { LOG.info("replay recover db[{}]", dbId); } - public void alterDatabaseQuota(AlterDatabaseQuotaStmt stmt) throws DdlException { - String dbName = stmt.getDbName(); - Database db = (Database) getDbOrDdlException(dbName); - QuotaType quotaType = stmt.getQuotaType(); + public void alterDatabaseQuota(String dbName, QuotaType quotaType, long quotaValue) throws DdlException { + Database db = getDbOrDdlException(dbName); db.writeLockOrDdlException(); try { if (quotaType == QuotaType.DATA) { - db.setDataQuota(stmt.getQuota()); + db.setDataQuota(quotaValue); } else if (quotaType == QuotaType.REPLICA) { - db.setReplicaQuota(stmt.getQuota()); + db.setReplicaQuota(quotaValue); } else if (quotaType == QuotaType.TRANSACTION) { - db.setTransactionQuotaSize(stmt.getQuota()); + db.setTransactionQuotaSize(quotaValue); } - long quota = stmt.getQuota(); - DatabaseInfo dbInfo = new DatabaseInfo(dbName, "", quota, quotaType); + + DatabaseInfo dbInfo = new DatabaseInfo(dbName, "", quotaValue, quotaType); Env.getCurrentEnv().getEditLog().logAlterDb(dbInfo); } finally { db.writeUnlock(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index 476ad72285c1e9..2232f01bb3020b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -17,6 +17,7 @@ package org.apache.doris.nereids.parser; +import org.apache.doris.alter.QuotaType; import org.apache.doris.analysis.ArithmeticExpr.Operator; import org.apache.doris.analysis.BrokerDesc; import org.apache.doris.analysis.ColumnNullableType; @@ -72,6 +73,7 @@ import org.apache.doris.nereids.DorisParser.AlterCatalogCommentContext; import org.apache.doris.nereids.DorisParser.AlterCatalogRenameContext; import org.apache.doris.nereids.DorisParser.AlterDatabaseRenameContext; +import org.apache.doris.nereids.DorisParser.AlterDatabaseSetQuotaContext; import org.apache.doris.nereids.DorisParser.AlterMTMVContext; import org.apache.doris.nereids.DorisParser.AlterMultiPartitionClauseContext; import org.apache.doris.nereids.DorisParser.AlterRoleContext; @@ -624,6 +626,7 @@ import org.apache.doris.nereids.trees.plans.commands.UnsupportedCommand; import org.apache.doris.nereids.trees.plans.commands.UpdateCommand; import org.apache.doris.nereids.trees.plans.commands.alter.AlterDatabaseRenameCommand; +import org.apache.doris.nereids.trees.plans.commands.alter.AlterDatabaseSetQuotaCommand; import org.apache.doris.nereids.trees.plans.commands.clean.CleanLabelCommand; import org.apache.doris.nereids.trees.plans.commands.info.AddColumnOp; import org.apache.doris.nereids.trees.plans.commands.info.AddColumnsOp; @@ -5464,6 +5467,30 @@ public LogicalPlan visitShowConvertLsc(ShowConvertLscContext ctx) { return new ShowConvertLSCCommand(databaseName); } + @Override + public Object visitAlterDatabaseSetQuota(AlterDatabaseSetQuotaContext ctx) { + String databaseName = Optional.ofNullable(ctx.name) + .map(ParseTree::getText).filter(s -> !s.isEmpty()) + .orElseThrow(() -> new ParseException("database name can not be null")); + String quota = Optional.ofNullable(ctx.quota) + .map(ParseTree::getText) + .orElseGet(() -> Optional.ofNullable(ctx.INTEGER_VALUE()) + .map(TerminalNode::getText) + .orElse(null)); + // Determine the quota type + QuotaType quotaType; + if (ctx.DATA() != null) { + quotaType = QuotaType.DATA; + } else if (ctx.REPLICA() != null) { + quotaType = QuotaType.REPLICA; + } else if (ctx.TRANSACTION() != null) { + quotaType = QuotaType.TRANSACTION; + } else { + quotaType = QuotaType.NONE; + } + return new AlterDatabaseSetQuotaCommand(databaseName, quotaType, quota); + } + @Override public LogicalPlan visitDropDatabase(DropDatabaseContext ctx) { boolean ifExists = ctx.EXISTS() != null; diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java index cd1789b687efd0..2e54d7895dd921 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java @@ -187,6 +187,7 @@ public enum PlanType { ALTER_WORKLOAD_GROUP_COMMAND, ALTER_WORKLOAD_POLICY_COMMAND, ALTER_DATABASE_RENAME_COMMAND, + ALTER_DATABASE_SET_DATA_QUOTA_COMMAND, DROP_CATALOG_RECYCLE_BIN_COMMAND, DROP_ENCRYPTKEY_COMMAND, DROP_FILE_COMMAND, diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/alter/AlterDatabaseSetQuotaCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/alter/AlterDatabaseSetQuotaCommand.java new file mode 100644 index 00000000000000..db1379d72a5789 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/alter/AlterDatabaseSetQuotaCommand.java @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.nereids.trees.plans.commands.alter; + +import org.apache.doris.alter.QuotaType; +import org.apache.doris.catalog.Env; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.util.InternalDatabaseUtil; +import org.apache.doris.common.util.ParseUtil; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.commands.AlterCommand; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.StmtExecutor; + +import com.google.common.base.Strings; +import com.google.gson.annotations.SerializedName; + +/** + * Represents the command for ALTER DATABASE db_name SET (DATA |REPLICA | TRANSACTION) QUOTA quota_value. + */ +public class AlterDatabaseSetQuotaCommand extends AlterCommand { + + @SerializedName("db") + private String dbName; + + @SerializedName("qt") + private QuotaType quotaType; + + @SerializedName("qv") + private String quotaValue; + + @SerializedName("q") + private long quota; + + public AlterDatabaseSetQuotaCommand(String dbName, QuotaType quotaType, String quotaValue) { + super(PlanType.ALTER_DATABASE_SET_DATA_QUOTA_COMMAND); + this.dbName = dbName; + this.quotaType = quotaType; + this.quotaValue = quotaValue; + } + + @Override + public void doRun(ConnectContext ctx, StmtExecutor executor) throws Exception { + InternalDatabaseUtil.checkDatabase(dbName, ConnectContext.get()); + if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR, + ctx.getQualifiedUser(), dbName); + } + + if (Strings.isNullOrEmpty(dbName)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); + } + if (quotaType == QuotaType.DATA) { + quota = ParseUtil.analyzeDataVolume(quotaValue); + } else if (quotaType == QuotaType.REPLICA) { + quota = ParseUtil.analyzeReplicaNumber(quotaValue); + } else if (quotaType == QuotaType.TRANSACTION) { + quota = ParseUtil.analyzeTransactionNumber(quotaValue); + } + Env.getCurrentInternalCatalog().alterDatabaseQuota(dbName, quotaType, quota); + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitAlterDatabaseSetQuotaCommand(this, context); + } + + public String toSql() { + return "ALTER DATABASE " + dbName + " SET " + + quotaType.name() + + " QUOTA " + quotaValue; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java index c849f9ce1f8fe2..5c33ad1d7a4101 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java @@ -154,6 +154,7 @@ import org.apache.doris.nereids.trees.plans.commands.UnsupportedCommand; import org.apache.doris.nereids.trees.plans.commands.UpdateCommand; import org.apache.doris.nereids.trees.plans.commands.alter.AlterDatabaseRenameCommand; +import org.apache.doris.nereids.trees.plans.commands.alter.AlterDatabaseSetQuotaCommand; import org.apache.doris.nereids.trees.plans.commands.clean.CleanLabelCommand; import org.apache.doris.nereids.trees.plans.commands.insert.BatchInsertIntoTableCommand; import org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTableCommand; @@ -759,6 +760,10 @@ default R visitAlterDatabaseRenameCommand(AlterDatabaseRenameCommand alterDataba return visitCommand(alterDatabaseRenameCommand, context); } + default R visitAlterDatabaseSetQuotaCommand(AlterDatabaseSetQuotaCommand alterDatabaseSetQuotaCommand, C context) { + return visitCommand(alterDatabaseSetQuotaCommand, context); + } + default R visitDropDatabaseCommand(DropDatabaseCommand dropDatabaseCommand, C context) { return visitCommand(dropDatabaseCommand, context); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/DatabaseInfo.java b/fe/fe-core/src/main/java/org/apache/doris/persist/DatabaseInfo.java index 966444c11c4fba..a48ce474b370e4 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/persist/DatabaseInfo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/DatabaseInfo.java @@ -17,7 +17,7 @@ package org.apache.doris.persist; -import org.apache.doris.analysis.AlterDatabaseQuotaStmt.QuotaType; +import org.apache.doris.alter.QuotaType; import org.apache.doris.catalog.BinlogConfig; import org.apache.doris.catalog.Database.DbState; import org.apache.doris.catalog.Env; diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterDatabaseQuotaStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterDatabaseQuotaStmtTest.java index eb04e7615a9100..745cc95bcec2cc 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterDatabaseQuotaStmtTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/AlterDatabaseQuotaStmtTest.java @@ -17,7 +17,7 @@ package org.apache.doris.analysis; -import org.apache.doris.analysis.AlterDatabaseQuotaStmt.QuotaType; +import org.apache.doris.alter.QuotaType; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.UserException; import org.apache.doris.mysql.privilege.AccessControllerManager; diff --git a/fe/fe-core/src/test/java/org/apache/doris/persist/DatabaseInfoTest.java b/fe/fe-core/src/test/java/org/apache/doris/persist/DatabaseInfoTest.java index 3edd0b21ffb4c3..c965cbd995055e 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/persist/DatabaseInfoTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/persist/DatabaseInfoTest.java @@ -17,7 +17,7 @@ package org.apache.doris.persist; -import org.apache.doris.analysis.AlterDatabaseQuotaStmt.QuotaType; +import org.apache.doris.alter.QuotaType; import org.apache.doris.common.AnalysisException; import org.junit.Assert; diff --git a/regression-test/data/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.out b/regression-test/data/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.out new file mode 100644 index 00000000000000..d353b2ca3a3c41 --- /dev/null +++ b/regression-test/data/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.out @@ -0,0 +1,31 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !show_data_sql -- +Total 0.000 0 0.000 +Quota 1024.000 TB 1073741824 +Left 1024.000 TB 1073741824 + +-- !show_data_sql_100m -- +Total 0.000 0 0.000 +Quota 100.000 MB 1073741824 +Left 100.000 MB 1073741824 + +-- !show_data_sql_1024g -- +Total 0.000 0 0.000 +Quota 1024.000 GB 1073741824 +Left 1024.000 GB 1073741824 + +-- !show_data_sql_100t -- +Total 0.000 0 0.000 +Quota 100.000 TB 1073741824 +Left 100.000 TB 1073741824 + +-- !show_data_sql_10t -- +Total 0.000 0 0.000 +Quota 10.000 TB 1073741824 +Left 10.000 TB 1073741824 + +-- !show_data_sql_replica_num -- +Total 0.000 0 0.000 +Quota 10.000 TB 102400 +Left 10.000 TB 102400 + diff --git a/regression-test/suites/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.groovy b/regression-test/suites/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.groovy new file mode 100644 index 00000000000000..6ff8256227f6a3 --- /dev/null +++ b/regression-test/suites/nereids_p0/ddl/alter/test_nereids_alter_database_set_quota.groovy @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +suite("test_nereids_alter_database_set_quota") { + String quotaDb1= "quotaDb2" + + sql """DROP database IF EXISTS ${quotaDb1}""" + sql """create database ${quotaDb1}""" + sql """use ${quotaDb1}""" + qt_show_data_sql """show data;""" + checkNereidsExecute("ALTER DATABASE ${quotaDb1} SET DATA QUOTA 100M;"); + qt_show_data_sql_100m """show data""" + checkNereidsExecute("ALTER DATABASE ${quotaDb1} SET DATA QUOTA 1024G;"); + qt_show_data_sql_1024g """show data""" + checkNereidsExecute("ALTER DATABASE ${quotaDb1} SET DATA QUOTA 100T;"); + qt_show_data_sql_100t """show data""" + checkNereidsExecute("ALTER DATABASE ${quotaDb1} SET DATA QUOTA 10995116277760;"); + qt_show_data_sql_10t """show data""" + checkNereidsExecute("ALTER DATABASE ${quotaDb1} SET REPLICA QUOTA 102400;"); + qt_show_data_sql_replica_num """show data""" + checkNereidsExecute("ALTER DATABASE ${quotaDb1} SET TRANSACTION QUOTA 100000;"); + sql """drop database ${quotaDb1}""" +} From 941717cc406f9520e08aa2215622e3c1285c5466 Mon Sep 17 00:00:00 2001 From: Gavin Chou Date: Thu, 16 Jan 2025 15:06:36 +0800 Subject: [PATCH 17/26] [chore](codeowner) Update storage related code owners (#47067) --- .github/CODEOWNERS | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b00919e4c8728d..bed8224817dd5e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,8 +14,24 @@ # See the License for the specific language governing permissions and # limitations under the License. # -be/src/io/* @platoneko @gavinchou @dataroaring + +# for more syntax help and usage example +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax + +be/src/io/ @gavinchou @dataroaring be/src/agent/be_exec_version_manager.cpp @BiteTheDDDDt fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java @dataroaring @CalvinKirs @morningman **/pom.xml @CalvinKirs @morningman fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java @dataroaring @morningman @yiguolei @xiaokang +fe/fe-core/src/main/java/org/apache/doris/cloud/ @luwei16 @gavinchou @dearding @dataroaring +cloud/src/meta-service/*job* @luwei16 @gavinchou @dataroaring +cloud/src/meta-service/*meta_service_txn* @swjtu-zhanglei @gavinchou @dataroaring +cloud/src/meta-service/*resource* @deardeng @gavinchou @dataroaring +cloud/src/resource-manager/* @deardeng @gavinchou @dataroaring +cloud/src/recycler/* @swjtu-zhanglei @gavinchou @dataroaring +be/src/cloud/*compact* @luwei16 @gavinchou @dataroaring +be/src/cloud/*schema* @luwei16 @gavinchou @dataroaring +cloud/ @gavinchou @dataroaring @w41ter +be/src/cloud/ @gavinchou @dataroaring +gensrc/proto/olap_file.proto @gavinchou @dataroaring @yiguolei +gensrc/proto/cloud.proto @gavinchou @dataroaring @w41ter From 90d307cb412ab4c6977e6b6d7b78aabe4ac05c4f Mon Sep 17 00:00:00 2001 From: Calvin Kirs Date: Thu, 16 Jan 2025 15:12:46 +0800 Subject: [PATCH 18/26] Add codeowner review for 3.0, and all content being merged into 3.0 requires RM confirmation. (#47075) --- .asf.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index 7a7d845e4c9bb0..4e8afd8c525439 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -88,9 +88,8 @@ github: - P0 Regression (Doris Regression) - External Regression (Doris External Regression) - cloud_p0 (Doris Cloud Regression) - #required_pull_request_reviews: - # dismiss_stale_reviews: true - # required_approving_review_count: 1 + required_pull_request_reviews: + require_code_owner_reviews: true branch-2.1: required_status_checks: From cc61c5f5cfa92e44cd0b2ea5939d5a04831f8638 Mon Sep 17 00:00:00 2001 From: yujun Date: Thu, 16 Jan 2025 15:54:16 +0800 Subject: [PATCH 19/26] [fix](nereids) simplify self compare exclude non-foldable expression (#47054) ### What problem does this PR solve? #46905 add an rewrite expression rule to simplify self comparison, for example: `a = a` will evaluate to `TRUE`. But if `a` is non-foldable, it shouldn't simplify. for example: function `random` is non-foldable, then `random(1, 10) = random(1, 10)` cannot evaluate to `TRUE`. What's more, if an expression is non-deterministic, but if it's foldable, then self comparison also can fold it too. for example: function `user` is foldable and non-deterministic, then `user() = user()` can still simplify to `TRUE`. --- .../rules/expression/rules/SimplifySelfComparison.java | 7 ++++++- .../trees/expressions/functions/ExpressionTrait.java | 9 ++++++++- .../expression/rules/SimplifySelfComparisonTest.java | 8 ++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparison.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparison.java index dbd89964710de5..76e93c442034d4 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparison.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparison.java @@ -54,7 +54,12 @@ public List> buildRules() { private Expression rewrite(ComparisonPredicate comparison) { Expression left = comparison.left(); - if (left.equals(comparison.right())) { + // expression maybe non-deterministic, but if it's foldable, then it still can simplify self comparison. + // for example, function `user()`, `current_timestamp()` are foldable and non-deterministic, + // then `user() = user()` and `current_timestamp() = current_timestamp()` can simplify to `TRUE`. + // function `random` is not foldable and non-deterministic, + // then `random(1, 10) = random(1, 10)` cann't simplify to `TRUE` + if (!left.containsNonfoldable() && left.equals(comparison.right())) { if (comparison instanceof EqualTo || comparison instanceof GreaterThanEqual || comparison instanceof LessThanEqual) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java index daaab359e8433a..6e33f2a5e113fc 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ExpressionTrait.java @@ -77,6 +77,13 @@ default boolean foldable() { return true; } + /** + * Identify the expression is containing non-foldable expr or not + */ + default boolean containsNonfoldable() { + return anyMatch(expr -> !((ExpressionTrait) expr).foldable()); + } + /** * Identify the expression itself is deterministic or not, default true */ @@ -85,7 +92,7 @@ default boolean isDeterministic() { } /** - * Identify the expression is containing deterministic expr or not + * Identify the expression is containing non-deterministic expr or not */ default boolean containsNondeterministic() { return anyMatch(expr -> !((ExpressionTrait) expr).isDeterministic()); diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparisonTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparisonTest.java index be59404b46786b..28a5ed3b7c3329 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparisonTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/rules/SimplifySelfComparisonTest.java @@ -32,12 +32,20 @@ void testRewrite() { ExpressionRewrite.bottomUp(SimplifySelfComparison.INSTANCE) )); + // foldable, cast assertRewriteAfterTypeCoercion("TA + TB = TA + TB", "NOT ((TA + TB) IS NULL) OR NULL"); assertRewriteAfterTypeCoercion("TA + TB >= TA + TB", "NOT ((TA + TB) IS NULL) OR NULL"); assertRewriteAfterTypeCoercion("TA + TB <= TA + TB", "NOT ((TA + TB) IS NULL) OR NULL"); assertRewriteAfterTypeCoercion("TA + TB <=> TA + TB", "TRUE"); assertRewriteAfterTypeCoercion("TA + TB > TA + TB", "(TA + TB) IS NULL AND NULL"); assertRewriteAfterTypeCoercion("TA + TB < TA + TB", "(TA + TB) IS NULL AND NULL"); + assertRewriteAfterTypeCoercion("DAYS_ADD(CA, 7) <=> DAYS_ADD(CA, 7)", "TRUE"); + assertRewriteAfterTypeCoercion("USER() = USER()", "TRUE"); + assertRewriteAfterTypeCoercion("CURRENT_TIMESTAMP() = CURRENT_TIMESTAMP()", "TRUE"); + + // not foldable, not cast + assertRewriteAfterTypeCoercion("random(5, 10) = random(5, 10)", "random(5, 10) = random(5, 10)"); + assertRewriteAfterTypeCoercion("random(5, 10) + 100 = random(5, 10) + 100", "random(5, 10) + 100 = random(5, 10) + 100"); } } From 93f8da1daee2d0ec27ad96bb6eba938720c1d58d Mon Sep 17 00:00:00 2001 From: Calvin Kirs Date: Thu, 16 Jan 2025 15:57:54 +0800 Subject: [PATCH 20/26] Update .asf.yaml (#47079) --- .asf.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.asf.yaml b/.asf.yaml index 4e8afd8c525439..e75df33d1919a6 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -90,6 +90,8 @@ github: - cloud_p0 (Doris Cloud Regression) required_pull_request_reviews: require_code_owner_reviews: true + required_approving_review_count: 1 + dismiss_stale_reviews: true branch-2.1: required_status_checks: From eb40d0af75efc8c1d7808d5dd5dd9850ef359e40 Mon Sep 17 00:00:00 2001 From: zgxme Date: Thu, 16 Jan 2025 17:59:37 +0800 Subject: [PATCH 21/26] [Opt](external-docker) Modify kerberos network mode to host (#47043) ### What problem does this PR solve? In order to be able to perform kerberos testing on a multi-node doris cluster. --- .../event-hook.sh} | 14 ++-- .../docker-compose/common/hive-configure.sh | 50 ++++++++++++++ .../kerberos/common/conf/doris-krb5.conf | 19 +++-- .../kerberos/common/hadoop/hadoop-run.sh | 2 + .../kerberos/conf/kerberos1/kdc.conf.tpl | 50 ++++++++++++++ .../kerberos/conf/kerberos1/krb5.conf.tpl | 39 +++++++++++ .../kerberos/conf/kerberos2/kdc.conf.tpl | 38 ++++++++++ .../kerberos/conf/kerberos2/krb5.conf.tpl | 35 ++++++++++ .../my.cnf.tpl} | 29 ++++---- .../kerberos/entrypoint-hive-master-2.sh | 50 -------------- .../kerberos/entrypoint-hive-master.sh | 46 ++++++++++--- .../kerberos/hadoop-hive.env.tpl | 65 +++++++++++++++++ .../kerberos/health-checks/health.sh | 1 - .../health-checks/hive-health-check-2.sh | 2 +- .../health-checks/hive-health-check.sh | 2 +- ...health-check.sh => supervisorctl-check.sh} | 0 .../docker-compose/kerberos/kerberos.yaml.tpl | 69 ++++++++----------- .../kerberos/kerberos1_settings.env | 47 +++++++++++++ .../kerberos/kerberos2_settings.env | 43 ++++++++++++ .../thirdparties/run-thirdparties-docker.sh | 24 +++++-- .../kerberos/test_single_hive_kerberos.groovy | 19 ++--- .../kerberos/test_two_hive_kerberos.groovy | 13 ++-- 22 files changed, 501 insertions(+), 156 deletions(-) rename docker/thirdparties/docker-compose/{kerberos/two-kerberos-hives/update-location.sh => common/event-hook.sh} (74%) mode change 100755 => 100644 create mode 100755 docker/thirdparties/docker-compose/common/hive-configure.sh create mode 100644 docker/thirdparties/docker-compose/kerberos/conf/kerberos1/kdc.conf.tpl create mode 100644 docker/thirdparties/docker-compose/kerberos/conf/kerberos1/krb5.conf.tpl create mode 100644 docker/thirdparties/docker-compose/kerberos/conf/kerberos2/kdc.conf.tpl create mode 100644 docker/thirdparties/docker-compose/kerberos/conf/kerberos2/krb5.conf.tpl rename docker/thirdparties/docker-compose/kerberos/{ccache/create_kerberos_credential_cache_files.sh => conf/my.cnf.tpl} (55%) delete mode 100755 docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master-2.sh create mode 100644 docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl rename docker/thirdparties/docker-compose/kerberos/health-checks/{hadoop-health-check.sh => supervisorctl-check.sh} (100%) create mode 100644 docker/thirdparties/docker-compose/kerberos/kerberos1_settings.env create mode 100644 docker/thirdparties/docker-compose/kerberos/kerberos2_settings.env diff --git a/docker/thirdparties/docker-compose/kerberos/two-kerberos-hives/update-location.sh b/docker/thirdparties/docker-compose/common/event-hook.sh old mode 100755 new mode 100644 similarity index 74% rename from docker/thirdparties/docker-compose/kerberos/two-kerberos-hives/update-location.sh rename to docker/thirdparties/docker-compose/common/event-hook.sh index 8d727b2308dd05..144550e8bb0aeb --- a/docker/thirdparties/docker-compose/kerberos/two-kerberos-hives/update-location.sh +++ b/docker/thirdparties/docker-compose/common/event-hook.sh @@ -16,10 +16,10 @@ # specific language governing permissions and limitations # under the License. -/usr/bin/mysqld_safe & -while ! mysqladmin ping -proot --silent; do sleep 1; done - -hive --service metatool -updateLocation hdfs://hadoop-master-2:9000/user/hive/warehouse hdfs://hadoop-master:9000/user/hive/warehouse - -killall mysqld -while pgrep mysqld; do sleep 1; done +function exec_success_hook() { + echo "Executing success hook" + echo "Creating /tmp/success and /tmp/SUCCESS" + touch /tmp/success /tmp/SUCCESS + echo "Do not exit, just tailing /dev/null" + tail -f /dev/null +} \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/common/hive-configure.sh b/docker/thirdparties/docker-compose/common/hive-configure.sh new file mode 100755 index 00000000000000..cb17d4d2275554 --- /dev/null +++ b/docker/thirdparties/docker-compose/common/hive-configure.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Referenced from [docker-hive](https://github.com/big-data-europe/docker-hive) + +# Set some sensible defaults +export CORE_CONF_fs_defaultFS=${CORE_CONF_fs_defaultFS:-hdfs://`hostname -f`:8020} + +function addProperty() { + local path=$1 + local name=$2 + local value=$3 + + local entry="$name${value}" + local escapedEntry=$(echo $entry | sed 's/\//\\\//g') + sed -i "/<\/configuration>/ s/.*/${escapedEntry}\n&/" $path +} + +function configure() { + local path=$1 + local module=$2 + local envPrefix=$3 + + local var + local value + + echo "Configuring $module" + for c in `printenv | perl -sne 'print "$1 " if m/^${envPrefix}_(.+?)=.*/' -- -envPrefix=$envPrefix`; do + name=`echo ${c} | perl -pe 's/___/-/g; s/__/_/g; s/_/./g'` + var="${envPrefix}_${c}" + value=${!var} + echo " - Setting $name=$ " + addProperty $path $name "$value" + done +} \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/common/conf/doris-krb5.conf b/docker/thirdparties/docker-compose/kerberos/common/conf/doris-krb5.conf index 36547b8f89d163..83fe29c2cb2fa0 100644 --- a/docker/thirdparties/docker-compose/kerberos/common/conf/doris-krb5.conf +++ b/docker/thirdparties/docker-compose/kerberos/common/conf/doris-krb5.conf @@ -32,21 +32,18 @@ [realms] LABS.TERADATA.COM = { - kdc = hadoop-master:88 - admin_server = hadoop-master + kdc = hadoop-master:5588 + admin_server = hadoop-master:5749 } OTHERLABS.TERADATA.COM = { - kdc = hadoop-master:89 - admin_server = hadoop-master + kdc = hadoop-master:5589 + admin_server = hadoop-master:5750 } - OTHERLABS.TERADATA.COM = { - kdc = hadoop-master:89 - admin_server = hadoop-master - } -OTHERREALM.COM = { - kdc = hadoop-master-2:88 - admin_server = hadoop-master + OTHERREALM.COM = { + kdc = hadoop-master-2:6688 + admin_server = hadoop-master-2:6749 } [domain_realm] hadoop-master-2 = OTHERREALM.COM + hadoop-master = LABS.TERADATA.COM diff --git a/docker/thirdparties/docker-compose/kerberos/common/hadoop/hadoop-run.sh b/docker/thirdparties/docker-compose/kerberos/common/hadoop/hadoop-run.sh index b8bfd8715e9565..93c6e385effe1f 100755 --- a/docker/thirdparties/docker-compose/kerberos/common/hadoop/hadoop-run.sh +++ b/docker/thirdparties/docker-compose/kerberos/common/hadoop/hadoop-run.sh @@ -38,5 +38,7 @@ fi trap exit INT echo "Running services with supervisord" +rm -rf /etc/supervisord.d/socks-proxy.conf +rm -rf /etc/supervisord.d/sshd.conf supervisord -c /etc/supervisord.conf diff --git a/docker/thirdparties/docker-compose/kerberos/conf/kerberos1/kdc.conf.tpl b/docker/thirdparties/docker-compose/kerberos/conf/kerberos1/kdc.conf.tpl new file mode 100644 index 00000000000000..e16c70e16db817 --- /dev/null +++ b/docker/thirdparties/docker-compose/kerberos/conf/kerberos1/kdc.conf.tpl @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +[kdcdefaults] + kdc_ports = ${KDC_PORT1} + kdc_tcp_ports = ${KDC_PORT1} + kadmind_port = ${KADMIND_PORT1} + kpasswd_port = ${KPASSWD_PORT1} + +[realms] + LABS.TERADATA.COM = { + acl_file = /var/kerberos/krb5kdc/kadm5.acl + dict_file = /usr/share/dict/words + admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab + supported_enctypes = aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal + kdc_listen = ${KDC_PORT1} + kdc_tcp_listen = ${KDC_PORT1} + kdc_ports = ${KDC_PORT1} + kdc_tcp_ports = ${KDC_PORT1} + kadmind_port = ${KADMIND_PORT1} + kpasswd_port = ${KPASSWD_PORT1} + } + + OTHERLABS.TERADATA.COM = { + acl_file = /var/kerberos/krb5kdc/kadm5-other.acl + dict_file = /usr/share/dict/words + admin_keytab = /var/kerberos/krb5kdc/kadm5-other.keytab + supported_enctypes = aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal + kdc_listen = ${KDC_PORT2} + kdc_tcp_listen = ${KDC_PORT2} + kdc_ports = ${KDC_PORT2} + kdc_tcp_ports = ${KDC_PORT2} + kadmind_port = ${KADMIND_PORT2} + kpasswd_port = ${KPASSWD_PORT2} + } \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/conf/kerberos1/krb5.conf.tpl b/docker/thirdparties/docker-compose/kerberos/conf/kerberos1/krb5.conf.tpl new file mode 100644 index 00000000000000..1edf2bb8fd05fe --- /dev/null +++ b/docker/thirdparties/docker-compose/kerberos/conf/kerberos1/krb5.conf.tpl @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +[logging] + default = FILE:/var/log/krb5libs.log + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmind.log + +[libdefaults] + default_realm = LABS.TERADATA.COM + dns_lookup_realm = false + dns_lookup_kdc = false + forwardable = true + allow_weak_crypto = true + +[realms] + LABS.TERADATA.COM = { + kdc = ${HOST}:${KDC_PORT1} + admin_server = ${HOST}:${KADMIND_PORT1} + } + OTHERLABS.TERADATA.COM = { + kdc = ${HOST}:${KDC_PORT2} + admin_server = ${HOST}:${KADMIND_PORT2} + } \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/conf/kerberos2/kdc.conf.tpl b/docker/thirdparties/docker-compose/kerberos/conf/kerberos2/kdc.conf.tpl new file mode 100644 index 00000000000000..61b4994ad5c1ac --- /dev/null +++ b/docker/thirdparties/docker-compose/kerberos/conf/kerberos2/kdc.conf.tpl @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +[kdcdefaults] + kdc_ports = ${KDC_PORT1} + kdc_tcp_ports = ${KDC_PORT1} + kadmind_port = ${KADMIND_PORT1} + kpasswd_port = ${KPASSWD_PORT1} + + +[realms] + OTHERREALM.COM = { + acl_file = /var/kerberos/krb5kdc/kadm5.acl + dict_file = /usr/share/dict/words + admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab + supported_enctypes = aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal + kdc_listen = ${KDC_PORT1} + kdc_tcp_listen = ${KDC_PORT1} + kdc_ports = ${KDC_PORT1} + kdc_tcp_ports = ${KDC_PORT1} + kadmind_port = ${KADMIND_PORT1} + kpasswd_port = ${KPASSWD_PORT1} + } \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/conf/kerberos2/krb5.conf.tpl b/docker/thirdparties/docker-compose/kerberos/conf/kerberos2/krb5.conf.tpl new file mode 100644 index 00000000000000..c817dbdd79753d --- /dev/null +++ b/docker/thirdparties/docker-compose/kerberos/conf/kerberos2/krb5.conf.tpl @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +[logging] + default = FILE:/var/log/krb5libs.log + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmind.log + +[libdefaults] + default_realm = OTHERREALM.COM + dns_lookup_realm = false + dns_lookup_kdc = false + forwardable = true + allow_weak_crypto = true + +[realms] + OTHERREALM.COM = { + kdc = ${HOST}:${KDC_PORT1} + admin_server = ${HOST}:${KADMIND_PORT1} + } \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/ccache/create_kerberos_credential_cache_files.sh b/docker/thirdparties/docker-compose/kerberos/conf/my.cnf.tpl similarity index 55% rename from docker/thirdparties/docker-compose/kerberos/ccache/create_kerberos_credential_cache_files.sh rename to docker/thirdparties/docker-compose/kerberos/conf/my.cnf.tpl index 2bba3f928b198c..e91c65c100463f 100644 --- a/docker/thirdparties/docker-compose/kerberos/ccache/create_kerberos_credential_cache_files.sh +++ b/docker/thirdparties/docker-compose/kerberos/conf/my.cnf.tpl @@ -16,18 +16,23 @@ # specific language governing permissions and limitations # under the License. -set -exuo pipefail +[mysqld] +port=${MYSQL_PORT} +datadir=/var/lib/mysql +socket=/var/lib/mysql/mysql.sock +# Disabling symbolic-links is recommended to prevent assorted security risks +symbolic-links=0 +# Settings user and group are ignored when systemd is used. +# If you need to run mysqld under a different user or group, +# customize your systemd unit file for mariadb according to the +# instructions in http://fedoraproject.org/wiki/Systemd -TICKET_LIFETIME='30m' +[mysqld_safe] +log-error=/var/log/mariadb/mariadb.log +pid-file=/var/run/mariadb/mariadb.pid -kinit -l "$TICKET_LIFETIME" -f -c /etc/trino/conf/presto-server-krbcc \ - -kt /etc/trino/conf/presto-server.keytab presto-server/$(hostname -f)@LABS.TERADATA.COM - -kinit -l "$TICKET_LIFETIME" -f -c /etc/trino/conf/hive-presto-master-krbcc \ - -kt /etc/trino/conf/hive-presto-master.keytab hive/$(hostname -f)@LABS.TERADATA.COM - -kinit -l "$TICKET_LIFETIME" -f -c /etc/trino/conf/hdfs-krbcc \ - -kt /etc/hadoop/conf/hdfs.keytab hdfs/hadoop-master@LABS.TERADATA.COM +# +# include all files from the config directory +# +!includedir /etc/my.cnf.d -kinit -l "$TICKET_LIFETIME" -f -c /etc/trino/conf/hive-krbcc \ - -kt /etc/hive/conf/hive.keytab hive/hadoop-master@LABS.TERADATA.COM diff --git a/docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master-2.sh b/docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master-2.sh deleted file mode 100755 index eb95c5cb697619..00000000000000 --- a/docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master-2.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -set -euo pipefail - -echo "Copying kerberos keytabs to /keytabs/" -mkdir -p /etc/hadoop-init.d/ -cp /etc/trino/conf/hive-presto-master.keytab /keytabs/other-hive-presto-master.keytab -cp /etc/trino/conf/presto-server.keytab /keytabs/other-presto-server.keytab -cp /keytabs/update-location.sh /etc/hadoop-init.d/update-location.sh -/usr/local/hadoop-run.sh & - -# check healthy hear -echo "Waiting for hadoop to be healthy" - -for i in {1..10}; do - if /usr/local/health.sh; then - echo "Hadoop is healthy" - break - fi - echo "Hadoop is not healthy yet. Retrying in 20 seconds..." - sleep 20 -done - -if [ $i -eq 10 ]; then - echo "Hadoop did not become healthy after 120 attempts. Exiting." - exit 1 -fi - -echo "Init kerberos test data" -kinit -kt /etc/hive/conf/hive.keytab hive/hadoop-master-2@OTHERREALM.COM -hive -f /usr/local/sql/create_kerberos_hive_table.sql -touch /mnt/SUCCESS - -tail -f /dev/null diff --git a/docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master.sh b/docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master.sh index 76f49724297a61..f12b76734f74dc 100755 --- a/docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master.sh +++ b/docker/thirdparties/docker-compose/kerberos/entrypoint-hive-master.sh @@ -15,34 +15,60 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - set -euo pipefail +source /usr/local/common/hive-configure.sh +source /usr/local/common/event-hook.sh + +echo "Configuring hive" +configure /etc/hive/conf/hive-site.xml hive HIVE_SITE_CONF +configure /etc/hive/conf/hiveserver2-site.xml hive HIVE_SITE_CONF +configure /etc/hadoop/conf/core-site.xml core CORE_CONF +configure /etc/hadoop/conf/hdfs-site.xml hdfs HDFS_CONF +configure /etc/hadoop/conf/yarn-site.xml yarn YARN_CONF +configure /etc/hadoop/conf/mapred-site.xml mapred MAPRED_CONF +configure /etc/hive/conf/beeline-site.xml beeline BEELINE_SITE_CONF echo "Copying kerberos keytabs to keytabs/" mkdir -p /etc/hadoop-init.d/ -cp /etc/trino/conf/* /keytabs/ + +if [ "$1" == "1" ]; then + cp /etc/trino/conf/* /keytabs/ +elif [ "$1" == "2" ]; then + cp /etc/trino/conf/hive-presto-master.keytab /keytabs/other-hive-presto-master.keytab + cp /etc/trino/conf/presto-server.keytab /keytabs/other-presto-server.keytab +else + echo "Invalid index parameter. Exiting." + exit 1 +fi /usr/local/hadoop-run.sh & # check healthy hear echo "Waiting for hadoop to be healthy" -for i in {1..10}; do +for i in {1..60}; do if /usr/local/health.sh; then echo "Hadoop is healthy" break fi - echo "Hadoop is not healthy yet. Retrying in 20 seconds..." - sleep 20 + echo "Hadoop is not healthy yet. Retrying in 60 seconds..." + sleep 5 done -if [ $i -eq 10 ]; then - echo "Hadoop did not become healthy after 120 attempts. Exiting." +if [ $i -eq 60 ]; then + echo "Hadoop did not become healthy after 60 attempts. Exiting." exit 1 fi echo "Init kerberos test data" -kinit -kt /etc/hive/conf/hive.keytab hive/hadoop-master@LABS.TERADATA.COM + +if [ "$1" == "1" ]; then + kinit -kt /etc/hive/conf/hive.keytab hive/hadoop-master@LABS.TERADATA.COM +elif [ "$1" == "2" ]; then + kinit -kt /etc/hive/conf/hive.keytab hive/hadoop-master-2@OTHERREALM.COM +else + echo "Invalid index parameter. Exiting." + exit 1 +fi hive -f /usr/local/sql/create_kerberos_hive_table.sql -touch /mnt/SUCCESS -tail -f /dev/null +exec_success_hook \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl b/docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl new file mode 100644 index 00000000000000..41c95057a92207 --- /dev/null +++ b/docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl @@ -0,0 +1,65 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +HIVE_SITE_CONF_javax_jdo_option_ConnectionURL=jdbc:mysql://127.0.0.1:${MYSQL_PORT}/metastore +HIVE_SITE_CONF_javax_jdo_option_ConnectionDriverName=com.mysql.jdbc.Driver +HIVE_SITE_CONF_javax_jdo_option_ConnectionUserName=root +HIVE_SITE_CONF_javax_jdo_option_ConnectionPassword=root +HIVE_SITE_CONF_datanucleus_autoCreateSchema=false +HIVE_SITE_CONF_hive_metastore_port=${HMS_PORT} +HIVE_SITE_CONF_hive_metastore_uris=thrift://${IP_HOST}:${HMS_PORT} +HIVE_SITE_CONF_hive_server2_thrift_bind_host=0.0.0.0 +HIVE_SITE_CONF_hive_server2_thrift_port=${HS_PORT} +HIVE_SITE_CONF_hive_server2_webui_port=0 +HIVE_SITE_CONF_hive_compactor_initiator_on=true +HIVE_SITE_CONF_hive_compactor_worker_threads=2 +HIVE_SITE_CONF_metastore_storage_schema_reader_impl=org.apache.hadoop.hive.metastore.SerDeStorageSchemaReader +BEELINE_SITE_CONF_beeline_hs2_jdbc_url_tcpUrl=jdbc:hive2://${HOST}:${HS_PORT}/default;user=hdfs;password=hive +BEELINE_SITE_CONF_beeline_hs2_jdbc_url_httpUrl=jdbc:hive2://${HOST}:${HS_PORT}/default;user=hdfs;password=hive + + +CORE_CONF_fs_defaultFS=hdfs://${HOST}:${FS_PORT} +CORE_CONF_hadoop_http_staticuser_user=root +CORE_CONF_hadoop_proxyuser_hue_hosts=* +CORE_CONF_hadoop_proxyuser_hue_groups=* + +HDFS_CONF_dfs_webhdfs_enabled=true +HDFS_CONF_dfs_permissions_enabled=false +HDFS_CONF_dfs_namenode_datanode_registration_ip___hostname___check=false +HDFS_CONF_dfs_datanode_address=${HOST}:${DFS_DN_PORT} +HDFS_CONF_dfs_datanode_http_address=${HOST}:${DFS_DN_HTTP_PORT} +HDFS_CONF_dfs_datanode_ipc_address=${HOST}:${DFS_DN_IPC_PORT} +HDFS_CONF_dfs_namenode_http___address=${HOST}:${DFS_NN_HTTP_PORT} +YARN_CONF_yarn_log___aggregation___enable=true +YARN_CONF_yarn_resourcemanager_recovery_enabled=true +YARN_CONF_yarn_resourcemanager_store_class=org.apache.hadoop.yarn.server.resourcemanager.recovery.FileSystemRMStateStore +YARN_CONF_yarn_resourcemanager_fs_state___store_uri=/rmstate +YARN_CONF_yarn_nodemanager_remote___app___log___dir=/var/log/hadoop-yarn/apps +YARN_CONF_yarn_log_server_url=http://${HOST}:${YARM_LOG_SERVER_PORT}/jobhistory/logs +YARN_CONF_yarn_timeline___service_enabled=false +YARN_CONF_yarn_timeline___service_generic___application___history_enabled=true +YARN_CONF_yarn_resourcemanager_system___metrics___publisher_enabled=true +YARN_CONF_yarn_resourcemanager_hostname=${HOST} +MAPRED_CONF_mapreduce_shuffle_port=${MAPREDUCE_SHUFFLE_PORT} +YARN_CONF_yarn_timeline___service_hostname=${HOST} +YARN_CONF_yarn_resourcemanager_address=${HOST}:${YARN_RM_PORT} +YARN_CONF_yarn_resourcemanager_scheduler_address=${HOST}:${YARN_RM_SCHEDULER_PORT} +YARN_CONF_yarn_resourcemanager_resource___tracker_address=${HOST}:${YARN_RM_TRACKER_PORT} +YARN_CONF_yarn_resourcemanager_admin_address=${HOST}:${YARN_RM_ADMIN_PORT} +YARN_CONF_yarn_resourcemanager_webapp_address=${HOST}:${YARN_RM_WEBAPP_PORT} +YARN_CONF_yarn_nodemanager_localizer_address=${HOST}:${YARN_NM_LOCAL_PORT} +YARN_CONF_yarn_nodemanager_webapp_address=${HOST}:${YARN_NM_WEBAPP_PORT} \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/health-checks/health.sh b/docker/thirdparties/docker-compose/kerberos/health-checks/health.sh index 473d7ceaeb6166..515f37e36ac9e3 100755 --- a/docker/thirdparties/docker-compose/kerberos/health-checks/health.sh +++ b/docker/thirdparties/docker-compose/kerberos/health-checks/health.sh @@ -32,4 +32,3 @@ if test -d "${HEALTH_D}"; then "${health_script}" &>> /var/log/container-health.log || exit 1 done fi -exit 0 diff --git a/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check-2.sh b/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check-2.sh index 854524dac1fcff..7545969bc47d65 100755 --- a/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check-2.sh +++ b/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check-2.sh @@ -17,4 +17,4 @@ # under the License. kinit -kt /etc/hive/conf/hive.keytab hive/hadoop-master-2@OTHERREALM.COM -beeline -u "jdbc:hive2://localhost:10000/default;principal=hive/hadoop-master-2@OTHERREALM.COM" -e "show databases;" \ No newline at end of file +beeline -u "jdbc:hive2://localhost:16000/default;principal=hive/hadoop-master-2@OTHERREALM.COM" -e "show databases;" \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check.sh b/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check.sh index 4d3d86f69a25c4..ab464b5233b31a 100755 --- a/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check.sh +++ b/docker/thirdparties/docker-compose/kerberos/health-checks/hive-health-check.sh @@ -17,4 +17,4 @@ # under the License. kinit -kt /etc/hive/conf/hive.keytab hive/hadoop-master@LABS.TERADATA.COM -beeline -u "jdbc:hive2://localhost:10000/default;principal=hive/hadoop-master@LABS.TERADATA.COM" -e "show databases;" \ No newline at end of file +beeline -u "jdbc:hive2://localhost:15000/default;principal=hive/hadoop-master@LABS.TERADATA.COM" -e "show databases;" \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/health-checks/hadoop-health-check.sh b/docker/thirdparties/docker-compose/kerberos/health-checks/supervisorctl-check.sh similarity index 100% rename from docker/thirdparties/docker-compose/kerberos/health-checks/hadoop-health-check.sh rename to docker/thirdparties/docker-compose/kerberos/health-checks/supervisorctl-check.sh diff --git a/docker/thirdparties/docker-compose/kerberos/kerberos.yaml.tpl b/docker/thirdparties/docker-compose/kerberos/kerberos.yaml.tpl index e635ed6bb27f34..9a1520b74db98e 100644 --- a/docker/thirdparties/docker-compose/kerberos/kerberos.yaml.tpl +++ b/docker/thirdparties/docker-compose/kerberos/kerberos.yaml.tpl @@ -16,68 +16,55 @@ # under the License. version: "3" services: - hive-krb: + hive-krb1: image: doristhirdpartydocker/trinodb:hdp3.1-hive-kerberized_96 - container_name: doris--kerberos1 + container_name: doris-${CONTAINER_UID}-kerberos1 volumes: + - ../common:/usr/local/common - ./two-kerberos-hives:/keytabs - ./sql:/usr/local/sql - ./common/hadoop/apply-config-overrides.sh:/etc/hadoop-init.d/00-apply-config-overrides.sh - ./common/hadoop/hadoop-run.sh:/usr/local/hadoop-run.sh - ./health-checks/health.sh:/usr/local/health.sh - - ./health-checks/hadoop-health-check.sh:/etc/health.d/hadoop-health-check.sh + - ./health-checks/supervisorctl-check.sh:/etc/health.d/supervisorctl-check.sh - ./health-checks/hive-health-check.sh:/etc/health.d/hive-health-check.sh - ./entrypoint-hive-master.sh:/usr/local/entrypoint-hive-master.sh + - ./conf/kerberos1/my.cnf:/etc/my.cnf + - ./conf/kerberos1/kdc.conf:/var/kerberos/krb5kdc/kdc.conf + - ./conf/kerberos1/krb5.conf:/etc/krb5.conf hostname: hadoop-master - entrypoint: /usr/local/entrypoint-hive-master.sh + entrypoint: /usr/local/entrypoint-hive-master.sh 1 healthcheck: - test: ["CMD", "ls", "/mnt/SUCCESS"] - interval: 20s - timeout: 60s + test: ["CMD", "ls", "/tmp/SUCCESS"] + interval: 5s + timeout: 10s retries: 120 - ports: - - "5806:5006" - - "8820:8020" - - "8842:8042" - - "9800:9000" - - "9883:9083" - - "18000:10000" - networks: - doris--krb_net: - ipv4_address: 172.31.71.25 - + network_mode: "host" + env_file: + - ./hadoop-hive-1.env hive-krb2: image: doristhirdpartydocker/trinodb:hdp3.1-hive-kerberized-2_96 - container_name: doris--kerberos2 + container_name: doris-${CONTAINER_UID}-kerberos2 hostname: hadoop-master-2 volumes: + - ../common:/usr/local/common - ./two-kerberos-hives:/keytabs - ./sql:/usr/local/sql - ./common/hadoop/apply-config-overrides.sh:/etc/hadoop-init.d/00-apply-config-overrides.sh - ./common/hadoop/hadoop-run.sh:/usr/local/hadoop-run.sh - ./health-checks/health.sh:/usr/local/health.sh - - ./health-checks/hadoop-health-check.sh:/etc/health.d/hadoop-health-check.sh + - ./health-checks/supervisorctl-check.sh:/etc/health.d/supervisorctl-check.sh - ./health-checks/hive-health-check-2.sh:/etc/health.d/hive-health-check-2.sh - - ./entrypoint-hive-master-2.sh:/usr/local/entrypoint-hive-master-2.sh - entrypoint: /usr/local/entrypoint-hive-master-2.sh + - ./entrypoint-hive-master.sh:/usr/local/entrypoint-hive-master.sh + - ./conf/kerberos2/my.cnf:/etc/my.cnf + - ./conf/kerberos2/kdc.conf:/var/kerberos/krb5kdc/kdc.conf + - ./conf/kerberos2/krb5.conf:/etc/krb5.conf + entrypoint: /usr/local/entrypoint-hive-master.sh 2 healthcheck: - test: ["CMD", "ls", "/mnt/SUCCESS"] - interval: 20s - timeout: 60s + test: ["CMD", "ls", "/tmp/SUCCESS"] + interval: 5s + timeout: 10s retries: 120 - ports: - - "15806:5006" - - "18820:8020" - - "18842:8042" - - "19800:9000" - - "19883:9083" - - "18800:10000" - networks: - doris--krb_net: - ipv4_address: 172.31.71.26 - -networks: - doris--krb_net: - ipam: - config: - - subnet: 172.31.71.0/24 + network_mode: "host" + env_file: + - ./hadoop-hive-2.env \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/kerberos1_settings.env b/docker/thirdparties/docker-compose/kerberos/kerberos1_settings.env new file mode 100644 index 00000000000000..aaf4c639fa8509 --- /dev/null +++ b/docker/thirdparties/docker-compose/kerberos/kerberos1_settings.env @@ -0,0 +1,47 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Change this to a specific string. +# Do not use "_" or other sepcial characters, only number and alphabeta. +# NOTICE: change this uid will modify hive-*.yaml + +export HOST="hadoop-master" +export FS_PORT=8520 +export HMS_PORT=9583 +export HS_PORT=15000 +export MYSQL_PORT=3356 +export DFS_DN_PORT=9566 +export DFS_DN_HTTP_PORT=9564 +export DFS_DN_IPC_PORT=9567 +export DFS_NN_HTTP_PORT=9570 +export YARM_LOG_SERVER_PORT=8588 +export YARN_RM_PORT=8532 +export YARN_RM_SCHEDULER_PORT=8530 +export YARN_RM_TRACKER_PORT=8531 +export YARN_RM_ADMIN_PORT=8533 +export YARN_RM_WEBAPP_PORT=8589 +export YARN_NM_LOCAL_PORT=8540 +export YARN_NM_WEBAPP_PORT=8542 +export MAPREDUCE_SHUFFLE_PORT=13562 +export KADMIND_PORT=5464 +export KDC_PORT1=5588 +export KDC_PORT2=5589 +export KADMIND_PORT1=5749 +export KADMIND_PORT2=5750 +export KPASSWD_PORT1=5464 +export KPASSWD_PORT2=5465 \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/kerberos/kerberos2_settings.env b/docker/thirdparties/docker-compose/kerberos/kerberos2_settings.env new file mode 100644 index 00000000000000..fca68d60162091 --- /dev/null +++ b/docker/thirdparties/docker-compose/kerberos/kerberos2_settings.env @@ -0,0 +1,43 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Change this to a specific string. +# Do not use "_" or other sepcial characters, only number and alphabeta. +# NOTICE: change this uid will modify hive-*.yaml + +export HOST="hadoop-master-2" +export FS_PORT=8620 +export HMS_PORT=9683 +export HS_PORT=16000 +export MYSQL_PORT=3366 +export DFS_DN_PORT=9666 +export DFS_DN_HTTP_PORT=9664 +export DFS_DN_IPC_PORT=9667 +export DFS_NN_HTTP_PORT=9670 +export YARM_LOG_SERVER_PORT=8688 +export YARN_RM_PORT=8632 +export YARN_RM_SCHEDULER_PORT=8630 +export YARN_RM_TRACKER_PORT=8631 +export YARN_RM_ADMIN_PORT=8633 +export YARN_RM_WEBAPP_PORT=8689 +export YARN_NM_LOCAL_PORT=8640 +export YARN_NM_WEBAPP_PORT=8642 +export MAPREDUCE_SHUFFLE_PORT=13662 +export KDC_PORT1=6688 +export KADMIND_PORT1=6749 +export KPASSWD_PORT1=6464 \ No newline at end of file diff --git a/docker/thirdparties/run-thirdparties-docker.sh b/docker/thirdparties/run-thirdparties-docker.sh index e3769025fec62d..a581baa6cfa1e7 100755 --- a/docker/thirdparties/run-thirdparties-docker.sh +++ b/docker/thirdparties/run-thirdparties-docker.sh @@ -582,8 +582,22 @@ start_lakesoul() { start_kerberos() { echo "RUN_KERBEROS" - cp "${ROOT}"/docker-compose/kerberos/kerberos.yaml.tpl "${ROOT}"/docker-compose/kerberos/kerberos.yaml - sed -i "s/doris--/${CONTAINER_UID}/g" "${ROOT}"/docker-compose/kerberos/kerberos.yaml + eth_name=$(ifconfig -a | grep -E "^eth[0-9]" | sort -k1.4n | awk -F ':' '{print $1}' | head -n 1) + IP_HOST=$(ifconfig "${eth_name}" | grep inet | grep -v 127.0.0.1 | grep -v inet6 | awk '{print $2}' | tr -d "addr:" | head -n 1) + export IP_HOST=${IP_HOST} + export CONTAINER_UID=${CONTAINER_UID} + envsubst <"${ROOT}"/docker-compose/kerberos/kerberos.yaml.tpl >"${ROOT}"/docker-compose/kerberos/kerberos.yaml + for i in {1..2}; do + . "${ROOT}"/docker-compose/kerberos/kerberos${i}_settings.env + envsubst <"${ROOT}"/docker-compose/kerberos/hadoop-hive.env.tpl >"${ROOT}"/docker-compose/kerberos/hadoop-hive-${i}.env + envsubst <"${ROOT}"/docker-compose/kerberos/conf/my.cnf.tpl > "${ROOT}"/docker-compose/kerberos/conf/kerberos${i}/my.cnf + envsubst <"${ROOT}"/docker-compose/kerberos/conf/kerberos${i}/kdc.conf.tpl > "${ROOT}"/docker-compose/kerberos/conf/kerberos${i}/kdc.conf + envsubst <"${ROOT}"/docker-compose/kerberos/conf/kerberos${i}/krb5.conf.tpl > "${ROOT}"/docker-compose/kerberos/conf/kerberos${i}/krb5.conf + done + sudo chmod a+w /etc/hosts + sudo sed -i "1i${IP_HOST} hadoop-master" /etc/hosts + sudo sed -i "1i${IP_HOST} hadoop-master-2" /etc/hosts + sudo cp "${ROOT}"/docker-compose/kerberos/kerberos.yaml.tpl "${ROOT}"/docker-compose/kerberos/kerberos.yaml sudo docker compose -f "${ROOT}"/docker-compose/kerberos/kerberos.yaml down sudo rm -rf "${ROOT}"/docker-compose/kerberos/data if [[ "${STOP}" -ne 1 ]]; then @@ -591,15 +605,11 @@ start_kerberos() { rm -rf "${ROOT}"/docker-compose/kerberos/two-kerberos-hives/*.keytab rm -rf "${ROOT}"/docker-compose/kerberos/two-kerberos-hives/*.jks rm -rf "${ROOT}"/docker-compose/kerberos/two-kerberos-hives/*.conf - sudo docker compose -f "${ROOT}"/docker-compose/kerberos/kerberos.yaml up -d + sudo docker compose -f "${ROOT}"/docker-compose/kerberos/kerberos.yaml up -d --wait sudo rm -f /keytabs sudo ln -s "${ROOT}"/docker-compose/kerberos/two-kerberos-hives /keytabs sudo cp "${ROOT}"/docker-compose/kerberos/common/conf/doris-krb5.conf /keytabs/krb5.conf sudo cp "${ROOT}"/docker-compose/kerberos/common/conf/doris-krb5.conf /etc/krb5.conf - - sudo chmod a+w /etc/hosts - echo '172.31.71.25 hadoop-master' >> /etc/hosts - echo '172.31.71.26 hadoop-master-2' >> /etc/hosts sleep 2 fi } diff --git a/regression-test/suites/external_table_p0/kerberos/test_single_hive_kerberos.groovy b/regression-test/suites/external_table_p0/kerberos/test_single_hive_kerberos.groovy index 505c5208c9988c..e1612bcf884779 100644 --- a/regression-test/suites/external_table_p0/kerberos/test_single_hive_kerberos.groovy +++ b/regression-test/suites/external_table_p0/kerberos/test_single_hive_kerberos.groovy @@ -25,6 +25,7 @@ suite("test_single_hive_kerberos", "p0,external,kerberos,external_docker,externa println "Docker containers:" println output String enabled = context.config.otherConfigs.get("enableKerberosTest") + String externalEnvIp = context.config.otherConfigs.get("externalEnvIp") if (enabled != null && enabled.equalsIgnoreCase("true")) { String hms_catalog_name = "test_single_hive_kerberos" sql """drop catalog if exists hms_kerberos;""" @@ -32,8 +33,8 @@ suite("test_single_hive_kerberos", "p0,external,kerberos,external_docker,externa CREATE CATALOG IF NOT EXISTS hms_kerberos PROPERTIES ( "type" = "hms", - "hive.metastore.uris" = "thrift://172.31.71.25:9083", - "fs.defaultFS" = "hdfs://172.31.71.25:8020", + "hive.metastore.uris" = "thrift://${externalEnvIp}:9583", + "fs.defaultFS" = "hdfs://${externalEnvIp}:8520", "hadoop.security.authentication" = "kerberos", "hadoop.kerberos.principal"="presto-server/presto-master.docker.cluster@LABS.TERADATA.COM", "hadoop.kerberos.keytab" = "/keytabs/presto-server.keytab", @@ -42,7 +43,7 @@ suite("test_single_hive_kerberos", "p0,external,kerberos,external_docker,externa RULE:[2:\$1@\$0](.*@OTHERREALM.COM)s/@.*// DEFAULT", "hive.metastore.sasl.enabled " = "true", - "hive.metastore.kerberos.principal" = "hive/_HOST@LABS.TERADATA.COM" + "hive.metastore.kerberos.principal" = "hive/hadoop-master@LABS.TERADATA.COM" ); """ sql """ switch hms_kerberos """ @@ -56,8 +57,8 @@ suite("test_single_hive_kerberos", "p0,external,kerberos,external_docker,externa CREATE CATALOG IF NOT EXISTS hms_kerberos_hadoop_err1 PROPERTIES ( "type" = "hms", - "hive.metastore.uris" = "thrift://172.31.71.25:9083", - "fs.defaultFS" = "hdfs://172.31.71.25:8020", + "hive.metastore.uris" = "thrift://${externalEnvIp}:9583", + "fs.defaultFS" = "hdfs://${externalEnvIp}:8520", "hadoop.security.authentication" = "kerberos", "hadoop.kerberos.principal"="presto-server/presto-master.docker.cluster@LABS.TERADATA.COM", "hadoop.kerberos.keytab" = "/keytabs/presto-server.keytab" @@ -79,8 +80,8 @@ suite("test_single_hive_kerberos", "p0,external,kerberos,external_docker,externa PROPERTIES ( "type" = "hms", "hive.metastore.sasl.enabled " = "true", - "hive.metastore.uris" = "thrift://172.31.71.25:9083", - "fs.defaultFS" = "hdfs://172.31.71.25:8020" + "hive.metastore.uris" = "thrift://${externalEnvIp}:9583", + "fs.defaultFS" = "hdfs://${externalEnvIp}:8520" ); """ sql """ switch hms_kerberos_hadoop_err2 """ @@ -95,8 +96,8 @@ suite("test_single_hive_kerberos", "p0,external,kerberos,external_docker,externa // CREATE CATALOG IF NOT EXISTS hms_keberos_ccache // PROPERTIES ( // "type" = "hms", - // "hive.metastore.uris" = "thrift://172.31.71.25:9083", - // "fs.defaultFS" = "hdfs://172.31.71.25:8020", + // "hive.metastore.uris" = "thrift://${externalEnvIp}:9583", + // "fs.defaultFS" = "hdfs://${externalEnvIp}:8520", // "hadoop.security.authentication" = "kerberos", // "hadoop.kerberos.principal"="presto-server/presto-master.docker.cluster@LABS.TERADATA.COM", // "hadoop.kerberos.keytab" = "/keytabs/presto-server.keytab", diff --git a/regression-test/suites/external_table_p0/kerberos/test_two_hive_kerberos.groovy b/regression-test/suites/external_table_p0/kerberos/test_two_hive_kerberos.groovy index 29b0cb2cd7e9c6..764078c62a6c82 100644 --- a/regression-test/suites/external_table_p0/kerberos/test_two_hive_kerberos.groovy +++ b/regression-test/suites/external_table_p0/kerberos/test_two_hive_kerberos.groovy @@ -29,6 +29,7 @@ suite("test_two_hive_kerberos", "p0,external,kerberos,external_docker,external_d println "Docker containers:" println output String enabled = context.config.otherConfigs.get("enableKerberosTest") + String externalEnvIp = context.config.otherConfigs.get("externalEnvIp") if (enabled != null && enabled.equalsIgnoreCase("true")) { String hms_catalog_name = "test_two_hive_kerberos" sql """drop catalog if exists ${hms_catalog_name};""" @@ -36,14 +37,14 @@ suite("test_two_hive_kerberos", "p0,external,kerberos,external_docker,external_d CREATE CATALOG IF NOT EXISTS ${hms_catalog_name} PROPERTIES ( "type" = "hms", - "hive.metastore.uris" = "thrift://172.31.71.25:9083", - "fs.defaultFS" = "hdfs://172.31.71.25:8020", + "hive.metastore.uris" = "thrift://${externalEnvIp}:9583", + "fs.defaultFS" = "hdfs://${externalEnvIp}:8520", "hadoop.kerberos.min.seconds.before.relogin" = "5", "hadoop.security.authentication" = "kerberos", "hadoop.kerberos.principal"="hive/presto-master.docker.cluster@LABS.TERADATA.COM", "hadoop.kerberos.keytab" = "/keytabs/hive-presto-master.keytab", "hive.metastore.sasl.enabled " = "true", - "hive.metastore.kerberos.principal" = "hive/_HOST@LABS.TERADATA.COM" + "hive.metastore.kerberos.principal" = "hive/hadoop-master@LABS.TERADATA.COM" ); """ @@ -52,14 +53,14 @@ suite("test_two_hive_kerberos", "p0,external,kerberos,external_docker,external_d CREATE CATALOG IF NOT EXISTS other_${hms_catalog_name} PROPERTIES ( "type" = "hms", - "hive.metastore.uris" = "thrift://172.31.71.26:9083", - "fs.defaultFS" = "hdfs://172.31.71.26:8020", + "hive.metastore.uris" = "thrift://${externalEnvIp}:9683", + "fs.defaultFS" = "hdfs://${externalEnvIp}:8620", "hadoop.kerberos.min.seconds.before.relogin" = "5", "hadoop.security.authentication" = "kerberos", "hadoop.kerberos.principal"="hive/presto-master.docker.cluster@OTHERREALM.COM", "hadoop.kerberos.keytab" = "/keytabs/other-hive-presto-master.keytab", "hive.metastore.sasl.enabled " = "true", - "hive.metastore.kerberos.principal" = "hive/_HOST@OTHERREALM.COM", + "hive.metastore.kerberos.principal" = "hive/hadoop-master-2@OTHERREALM.COM", "hadoop.security.auth_to_local" ="RULE:[2:\$1@\$0](.*@OTHERREALM.COM)s/@.*// RULE:[2:\$1@\$0](.*@OTHERLABS.TERADATA.COM)s/@.*// DEFAULT" From a539bb70ca27bc68abbe1867d9f3ea520f6226d8 Mon Sep 17 00:00:00 2001 From: Tiewei Fang Date: Thu, 16 Jan 2025 20:27:29 +0800 Subject: [PATCH 22/26] [fix](regression-test) fix regression case `test_local_tvf_compression` (#46982) related pr: https://github.com/apache/doris/pull/40862 Doris `branch-2.1` modified this regression case without modifying the `master` branch. So this pr fixes the regression case `test_local_tvf_compression` --- .../tvf/compress/test_tvf.csv.snappy | Bin 107203 -> 100481 bytes .../tvf/test_local_tvf_compression.out | 44 +++++++++--------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/regression-test/data/external_table_p0/tvf/compress/test_tvf.csv.snappy b/regression-test/data/external_table_p0/tvf/compress/test_tvf.csv.snappy index 9ac2b7ae29977e030216d1863e01414bed885b61..054613c514691b98458669525ee30dc1b52ff841 100644 GIT binary patch literal 100481 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 B!-fC= literal 107203 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%0DZTd636P*Z+A1r3C69`J(nB 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°i!mO4SJej2VSNQeX4HyUfP7m`YLp{B$ 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 EKdlI5oB#j- diff --git a/regression-test/data/external_table_p0/tvf/test_local_tvf_compression.out b/regression-test/data/external_table_p0/tvf/test_local_tvf_compression.out index 8120427ea6cec5..5f1a4f5d463b93 100644 --- a/regression-test/data/external_table_p0/tvf/test_local_tvf_compression.out +++ b/regression-test/data/external_table_p0/tvf/test_local_tvf_compression.out @@ -123,28 +123,28 @@ 2023-09-18 7 -- !snappy_1 -- -1 694832 buHDwfGeNHfpRFdNaogneddi 2024-02-09 4.899588807225554 -10 218729 goZsLvvWFOIjlzSAitC 2024-06-10 4.137732740231178 -100 813423 zICskqgcdPc 2024-03-23 8.486529018746493 -1000 612650 RzOXeYpKOmuJOogUyeIEDNDmvq 2023-12-05 7.8741752707933435 -1001 29486 WoUAFJFuJNnwyqMnoDhX 2024-03-11 9.758244908785949 -1002 445363 OdTEeeWtxfcRwx 2024-08-01 0.3934945460194128 -1003 707035 JAYnKxusVpGzYueACf 2023-11-14 5.377110182643222 -1004 227858 JIFyjKzmbjkt 2024-03-24 5.748037621519263 -1005 539305 PlruLkSUSXZgaHafFriklrhCi 2023-11-08 4.122635188836725 -1006 145518 KCwqEcSCGuXrHerwn 2024-06-22 8.482290064407216 -1007 939028 KzXhEMelsKVLbDMsEKh 2024-01-01 8.144449761594585 -1008 913569 CHlqPKqkIdqwBCBUHreXbFAkCt 2024-05-25 1.5683842369495904 -1009 757881 AjcSyYMIMzS 2024-05-04 7.5674012939461255 -101 326164 QWLnalYNmYDt 2024-01-07 3.8159876011523854 -1010 427079 AlRUfmxfAuoLnPqUTvQVMtrS 2024-06-04 3.8087069699523313 -1011 252076 gHmFDhtytYzWETIxdpkpMUpnLd 2023-09-17 6.773606843056635 -1012 819615 rFfRHquexplDJvSeUK 2023-11-02 3.220639250504097 -1013 413456 uvNPelHXYjJKiOkwdNbmUkGzxiiqLo 2024-03-15 8.305048700108081 -1014 308042 vnzcsvHxnWFhvLwJkAtUqe 2024-06-15 1.5668867233009998 -1015 603837 VBEsRVGyhRNWQeKzDaBnJHmFDnXAOU 2024-08-17 3.8287482122289007 -1016 912679 eEjldPhxojSjTnE 2024-01-09 1.3717891874157961 -1017 630392 TcczYHXbwaCYzFSfXJlhsFjN 2023-10-07 4.733337480058437 +1 694832 buHDwfGeNHfpRFdNaogneddi 2024-02-09 4.8995886 +10 218729 goZsLvvWFOIjlzSAitC 2024-06-10 4.1377325 +100 813423 zICskqgcdPc 2024-03-23 8.4865294 +1000 612650 RzOXeYpKOmuJOogUyeIEDNDmvq 2023-12-05 7.8741751 +1001 29486 WoUAFJFuJNnwyqMnoDhX 2024-03-11 9.7582445 +1002 445363 OdTEeeWtxfcRwx 2024-08-01 0.39349455 +1003 707035 JAYnKxusVpGzYueACf 2023-11-14 5.37711 +1004 227858 JIFyjKzmbjkt 2024-03-24 5.7480378 +1005 539305 PlruLkSUSXZgaHafFriklrhCi 2023-11-08 4.1226354 +1006 145518 KCwqEcSCGuXrHerwn 2024-06-22 8.48229 +1007 939028 KzXhEMelsKVLbDMsEKh 2024-01-01 8.14445 +1008 913569 CHlqPKqkIdqwBCBUHreXbFAkCt 2024-05-25 1.5683843 +1009 757881 AjcSyYMIMzS 2024-05-04 7.5674014 +101 326164 QWLnalYNmYDt 2024-01-07 3.8159876 +1010 427079 AlRUfmxfAuoLnPqUTvQVMtrS 2024-06-04 3.808707 +1011 252076 gHmFDhtytYzWETIxdpkpMUpnLd 2023-09-17 6.7736068 +1012 819615 rFfRHquexplDJvSeUK 2023-11-02 3.2206392 +1013 413456 uvNPelHXYjJKiOkwdNbmUkGzxiiqLo 2024-03-15 8.3050489 +1014 308042 vnzcsvHxnWFhvLwJkAtUqe 2024-06-15 1.5668868 +1015 603837 VBEsRVGyhRNWQeKzDaBnJHmFDnXAOU 2024-08-17 3.8287482 +1016 912679 eEjldPhxojSjTnE 2024-01-09 1.3717892 +1017 630392 TcczYHXbwaCYzFSfXJlhsFjN 2023-10-07 4.7333374 -- !snappy_2 -- From 2295f33b7f0412ba19ed3558e3e869006388ce3e Mon Sep 17 00:00:00 2001 From: yagagagaga Date: Thu, 16 Jan 2025 20:46:45 +0800 Subject: [PATCH 23/26] [regression-test](fix) fix backup and restore case (#47058) --- .../admin-manual/data-admin/backup.md.groovy | 36 +++++++++------ .../admin-manual/data-admin/restore.md.groovy | 44 ++++++++++--------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/regression-test/suites/doc/admin-manual/data-admin/backup.md.groovy b/regression-test/suites/doc/admin-manual/data-admin/backup.md.groovy index 4c7f6406a2abdd..131c64cbb48fcd 100644 --- a/regression-test/suites/doc/admin-manual/data-admin/backup.md.groovy +++ b/regression-test/suites/doc/admin-manual/data-admin/backup.md.groovy @@ -17,13 +17,22 @@ import org.junit.jupiter.api.Assertions; -suite("docs/admin-manual/data-admin/backup.md", "p0,nonConcurrent") { +suite("docs/admin-manual/data-admin/backup.md", "backup_restore") { + if (isCloudMode()) { + logger.info("skip this case, because not supported in cloud mode") + return + } + if (!enableHdfs()) { + logger.info("skip this case, because hdfs is not enable") + return + } try { + def uuid = UUID.randomUUID().hashCode().abs() multi_sql """ - CREATE DATABASE IF NOT EXISTS example_db; - USE example_db; + CREATE DATABASE IF NOT EXISTS example_db${uuid}; + USE example_db${uuid}; DROP TABLE IF EXISTS example_tbl; - CREATE TABLE IF NOT EXISTS example_db.example_tbl( + CREATE TABLE IF NOT EXISTS example_db${uuid}.example_tbl( a INT ) PARTITION BY RANGE(a) ( PARTITION p1 VALUES LESS THAN (1), @@ -38,7 +47,6 @@ suite("docs/admin-manual/data-admin/backup.md", "p0,nonConcurrent") { INSERT INTO example_tbl2 SELECT * FROM example_tbl; """ - def uuid = UUID.randomUUID().hashCode().abs() def syncer = getSyncer() /* CREATE REPOSITORY `example_repo` @@ -50,7 +58,7 @@ suite("docs/admin-manual/data-admin/backup.md", "p0,nonConcurrent") { "hadoop.username" = "hadoop" ); */ - syncer.createHdfsRepository("example_repo") + syncer.createHdfsRepository("example_repo${uuid}") /* CREATE REPOSITORY `s3_repo` @@ -64,28 +72,28 @@ suite("docs/admin-manual/data-admin/backup.md", "p0,nonConcurrent") { "AWS_REGION" = "xxx" ); */ - syncer.createS3Repository("s3_repo") + syncer.createS3Repository("s3_repo${uuid}") sql """ - BACKUP SNAPSHOT example_db.snapshot_label1${uuid} - TO example_repo + BACKUP SNAPSHOT example_db${uuid}.snapshot_label1${uuid} + TO example_repo${uuid} ON (example_tbl) PROPERTIES ("type" = "full"); """ - syncer.waitSnapshotFinish("example_db") + syncer.waitSnapshotFinish("example_db${uuid}") sql """ - BACKUP SNAPSHOT example_db.snapshot_label2${uuid} - TO example_repo + BACKUP SNAPSHOT example_db${uuid}.snapshot_label2${uuid} + TO example_repo${uuid} ON ( example_tbl PARTITION (p1,p2), example_tbl2 ); """ - syncer.waitSnapshotFinish("example_db") + syncer.waitSnapshotFinish("example_db${uuid}") sql """show BACKUP""" - sql """SHOW SNAPSHOT ON example_repo WHERE SNAPSHOT = "snapshot_label1${uuid}";""" + sql """SHOW SNAPSHOT ON example_repo${uuid} WHERE SNAPSHOT = "snapshot_label1${uuid}";""" } catch (Throwable t) { Assertions.fail("examples in docs/admin-manual/data-admin/backup.md failed to exec, please fix it", t) diff --git a/regression-test/suites/doc/admin-manual/data-admin/restore.md.groovy b/regression-test/suites/doc/admin-manual/data-admin/restore.md.groovy index f36d225854bae6..e0fb1d4ccfdba0 100644 --- a/regression-test/suites/doc/admin-manual/data-admin/restore.md.groovy +++ b/regression-test/suites/doc/admin-manual/data-admin/restore.md.groovy @@ -17,13 +17,18 @@ import org.junit.jupiter.api.Assertions; -suite("docs/admin-manual/data-admin/restore.md", "p0,nonConcurrent") { +suite("docs/admin-manual/data-admin/restore.md") { + if (isCloudMode()) { + logger.info("skip this case, because not supported in cloud mode") + return + } try { + def uuid = UUID.randomUUID().hashCode().abs() multi_sql """ - CREATE DATABASE IF NOT EXISTS example_db1; - USE example_db1; + CREATE DATABASE IF NOT EXISTS example_db${uuid}; + USE example_db${uuid}; DROP TABLE IF EXISTS backup_tbl; - CREATE TABLE IF NOT EXISTS example_db1.backup_tbl( + CREATE TABLE IF NOT EXISTS example_db${uuid}.backup_tbl( a INT ) PARTITION BY RANGE(a) ( PARTITION p1 VALUES LESS THAN (1), @@ -38,33 +43,32 @@ suite("docs/admin-manual/data-admin/restore.md", "p0,nonConcurrent") { INSERT INTO backup_tbl2 SELECT * FROM backup_tbl; """ - def uuid = UUID.randomUUID().hashCode().abs() def syncer = getSyncer() - syncer.createS3Repository("example_repo") + syncer.createS3Repository("example_repo${uuid}") sql """ - BACKUP SNAPSHOT example_db1.snapshot_1${uuid} - TO example_repo + BACKUP SNAPSHOT example_db${uuid}.snapshot_1${uuid} + TO example_repo${uuid} ON (backup_tbl) PROPERTIES ("type" = "full"); """ - syncer.waitSnapshotFinish("example_db1") + syncer.waitSnapshotFinish("example_db${uuid}") sql """ - BACKUP SNAPSHOT example_db1.snapshot_2${uuid} - TO example_repo + BACKUP SNAPSHOT example_db${uuid}.snapshot_2${uuid} + TO example_repo${uuid} ON (backup_tbl, backup_tbl2) PROPERTIES ("type" = "full"); """ - syncer.waitSnapshotFinish("example_db1") + syncer.waitSnapshotFinish("example_db${uuid}") multi_sql """ truncate table backup_tbl; truncate table backup_tbl2; """ - var timestamp = syncer.getSnapshotTimestamp("example_repo", "snapshot_1${uuid}") + var timestamp = syncer.getSnapshotTimestamp("example_repo${uuid}", "snapshot_1${uuid}") sql """ - RESTORE SNAPSHOT example_db1.`snapshot_1${uuid}` - FROM `example_repo` + RESTORE SNAPSHOT example_db${uuid}.`snapshot_1${uuid}` + FROM `example_repo${uuid}` ON ( `backup_tbl` ) PROPERTIES ( @@ -72,12 +76,12 @@ suite("docs/admin-manual/data-admin/restore.md", "p0,nonConcurrent") { "replication_num" = "1" ); """ - syncer.waitAllRestoreFinish("example_db1") + syncer.waitAllRestoreFinish("example_db${uuid}") - var timestamp2 = syncer.getSnapshotTimestamp("example_repo", "snapshot_2${uuid}") + var timestamp2 = syncer.getSnapshotTimestamp("example_repo${uuid}", "snapshot_2${uuid}") sql """ - RESTORE SNAPSHOT example_db1.`snapshot_2${uuid}` - FROM `example_repo` + RESTORE SNAPSHOT example_db${uuid}.`snapshot_2${uuid}` + FROM `example_repo${uuid}` ON ( `backup_tbl` PARTITION (`p1`, `p2`), @@ -89,7 +93,7 @@ suite("docs/admin-manual/data-admin/restore.md", "p0,nonConcurrent") { "replication_num" = "1" ); """ - syncer.waitAllRestoreFinish("example_db1") + syncer.waitAllRestoreFinish("example_db${uuid}") sql """SHOW RESTORE""" From f06888ddcce97046a567186fc265bf4448225121 Mon Sep 17 00:00:00 2001 From: Siyang Tang Date: Thu, 16 Jan 2025 20:48:06 +0800 Subject: [PATCH 24/26] [fix](metrics) Correct statistical method for file cache stats (#47020) Current file cache statistics in not calculate in right way. It calculate reading bytes num by accumulate reading bytes in current io context repeadly rather than accumulate by increment. Correct this behavior in this PR. --- be/src/io/cache/cached_remote_file_reader.cpp | 10 +++++++--- be/src/io/cache/cached_remote_file_reader.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/be/src/io/cache/cached_remote_file_reader.cpp b/be/src/io/cache/cached_remote_file_reader.cpp index 70765fa707ea87..d1bd0b8023c23e 100644 --- a/be/src/io/cache/cached_remote_file_reader.cpp +++ b/be/src/io/cache/cached_remote_file_reader.cpp @@ -126,8 +126,12 @@ Status CachedRemoteFileReader::read_at_impl(size_t offset, Slice result, size_t* ReadStatistics stats; auto defer_func = [&](int*) { if (io_ctx->file_cache_stats) { - _update_state(stats, io_ctx->file_cache_stats, io_ctx->is_inverted_index); - io::FileCacheProfile::instance().update(io_ctx->file_cache_stats); + // update stats in io_ctx, for query profile + _update_stats(stats, io_ctx->file_cache_stats, io_ctx->is_inverted_index); + // update stats increment in this reading procedure for file cache metrics + FileCacheStatistics fcache_stats_increment; + _update_stats(stats, &fcache_stats_increment, io_ctx->is_inverted_index); + io::FileCacheProfile::instance().update(&fcache_stats_increment); } }; std::unique_ptr defer((int*)0x01, std::move(defer_func)); @@ -316,7 +320,7 @@ Status CachedRemoteFileReader::read_at_impl(size_t offset, Slice result, size_t* return Status::OK(); } -void CachedRemoteFileReader::_update_state(const ReadStatistics& read_stats, +void CachedRemoteFileReader::_update_stats(const ReadStatistics& read_stats, FileCacheStatistics* statis, bool is_inverted_index) const { if (statis == nullptr) { diff --git a/be/src/io/cache/cached_remote_file_reader.h b/be/src/io/cache/cached_remote_file_reader.h index 735e652f94cadc..94e8a5807ba273 100644 --- a/be/src/io/cache/cached_remote_file_reader.h +++ b/be/src/io/cache/cached_remote_file_reader.h @@ -67,7 +67,7 @@ class CachedRemoteFileReader final : public FileReader { std::shared_mutex _mtx; std::map _cache_file_readers; - void _update_state(const ReadStatistics& stats, FileCacheStatistics* state, + void _update_stats(const ReadStatistics& stats, FileCacheStatistics* state, bool is_inverted_index) const; }; From 6382742ad701152a616263a3ae9dd3a37e87622a Mon Sep 17 00:00:00 2001 From: Kaijie Chen Date: Thu, 16 Jan 2025 21:47:36 +0800 Subject: [PATCH 25/26] [test](move-memtable) mitigate flaky injection test `skip_two_backends` (#47082) Mitigate flaky test `skip_two_backends` in test_multi_replica_fault_injection. This test assumes a 3 BE environment and injects failure to 2 of them. However, it is currently running in a 4 BE environment. Depending on the replica distribution, the results may vary. This PR addresses the issue by acknowledging an additional error message. However, certain tablet distributions could still cause the test to be flaky, particularly as the number of backends increases. --- .../test_multi_replica_fault_injection.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/regression-test/suites/fault_injection_p0/test_multi_replica_fault_injection.groovy b/regression-test/suites/fault_injection_p0/test_multi_replica_fault_injection.groovy index d09983d52d0dc3..4e235daf97c20c 100644 --- a/regression-test/suites/fault_injection_p0/test_multi_replica_fault_injection.groovy +++ b/regression-test/suites/fault_injection_p0/test_multi_replica_fault_injection.groovy @@ -75,7 +75,7 @@ suite("test_multi_replica_fault_injection", "nonConcurrent") { file "baseall.txt" } - def load_with_injection = { injection, error_msg, success=false-> + def load_with_injection = { injection, error_msg, success=false, alt_error_msg=null-> try { sql "truncate table test" GetDebugPoint().enableDebugPointForAllBEs(injection) @@ -83,7 +83,8 @@ suite("test_multi_replica_fault_injection", "nonConcurrent") { assertTrue(success, String.format("Expected Exception '%s', actual success", error_msg)) } catch(Exception e) { logger.info(e.getMessage()) - assertTrue(e.getMessage().contains(error_msg), e.toString()) + boolean e_contains_alt_error_msg = (alt_error_msg != null && e.getMessage().contains(alt_error_msg)) + assertTrue(e.getMessage().contains(error_msg) || e_contains_alt_error_msg, e.toString()) } finally { GetDebugPoint().disableDebugPointForAllBEs(injection) } @@ -101,7 +102,7 @@ suite("test_multi_replica_fault_injection", "nonConcurrent") { // test one backend open failure load_with_injection("VTabletWriterV2._open_streams.skip_one_backend", "success", true) // test two backend open failure - load_with_injection("VTabletWriterV2._open_streams.skip_two_backends", "not enough streams 1/3") + load_with_injection("VTabletWriterV2._open_streams.skip_two_backends", "not enough streams 1/3", false, "succ replica num 1 < load required replica num 2") sql """ set enable_memtable_on_sink_node=false """ } } From 56fceb4ca72775ed967dd475c7b239d94de2e0e9 Mon Sep 17 00:00:00 2001 From: "Mingyu Chen (Rayner)" Date: Thu, 16 Jan 2025 22:11:48 +0800 Subject: [PATCH 26/26] [fix](test) fix unstable test cases (#47051) ### What problem does this PR solve? fix unstable test cases --- .../suites/check_before_quit/check_before_quit.groovy | 10 ++++++---- .../hive/test_autoinc_broker_load.groovy | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/regression-test/suites/check_before_quit/check_before_quit.groovy b/regression-test/suites/check_before_quit/check_before_quit.groovy index ad35ce3db7106b..0fc2c2621bf743 100644 --- a/regression-test/suites/check_before_quit/check_before_quit.groovy +++ b/regression-test/suites/check_before_quit/check_before_quit.groovy @@ -269,11 +269,13 @@ suite("check_before_quit", "nonConcurrent,p0") { sql "drop materialized view if exists ${tbl}" } else { sql "drop table if exists ${tbl}" + // only re create table, because the table which view depends may be dropped, + // so recreate view may fail + sql(createTableSql[0][1]) + def createTableSqlResult = sql "show create table ${tbl}" + logger.info("create table/view sql result info: ${createTableSqlResult}") + assertEquals(createTableSqlResult, createTableSql) } - sql(createTableSql[0][1]) - def createTableSqlResult = sql "show create table ${tbl}" - logger.info("create table/view sql result info: ${createTableSqlResult}") - assertEquals(createTableSqlResult, createTableSql) } } diff --git a/regression-test/suites/external_table_p0/hive/test_autoinc_broker_load.groovy b/regression-test/suites/external_table_p0/hive/test_autoinc_broker_load.groovy index cefbd4e69942d6..106263203fcd74 100644 --- a/regression-test/suites/external_table_p0/hive/test_autoinc_broker_load.groovy +++ b/regression-test/suites/external_table_p0/hive/test_autoinc_broker_load.groovy @@ -105,7 +105,7 @@ suite("test_autoinc_broker_load", "p0,external,hive,external_docker,external_doc load_from_hdfs("id, name, value", table, test_load_label, "auto_inc_with_null.csv", "csv") wait_for_load_result(test_load_label, table) sql "sync" - qt_sql "select * from ${table};" + qt_sql "select * from ${table} order by id;" sql """ insert into ${table} values(0, "Bob", 123), (2, "Tom", 323), (4, "Carter", 523);""" qt_sql "select * from ${table} order by id" sql "drop table if exists ${table};"