@@ -83,14 +83,14 @@ use table::table_name::TableName;
8383use crate :: error:: {
8484 self , AlterExprToRequestSnafu , BuildDfLogicalPlanSnafu , CatalogSnafu , ColumnDataTypeSnafu ,
8585 ColumnNotFoundSnafu , ConvertSchemaSnafu , CreateLogicalTablesSnafu , CreateTableInfoSnafu ,
86- EmptyDdlExprSnafu , ExternalSnafu , ExtractTableNamesSnafu , FlowNotFoundSnafu ,
87- InvalidPartitionRuleSnafu , InvalidPartitionSnafu , InvalidSqlSnafu , InvalidTableNameSnafu ,
88- InvalidViewNameSnafu , InvalidViewStmtSnafu , NotSupportedSnafu , PartitionExprToPbSnafu , Result ,
89- SchemaInUseSnafu , SchemaNotFoundSnafu , SchemaReadOnlySnafu , SubstraitCodecSnafu ,
90- TableAlreadyExistsSnafu , TableMetadataManagerSnafu , TableNotFoundSnafu ,
86+ DeserializePartitionExprSnafu , EmptyDdlExprSnafu , ExternalSnafu , ExtractTableNamesSnafu ,
87+ FlowNotFoundSnafu , InvalidPartitionRuleSnafu , InvalidPartitionSnafu , InvalidSqlSnafu ,
88+ InvalidTableNameSnafu , InvalidViewNameSnafu , InvalidViewStmtSnafu , NotSupportedSnafu ,
89+ PartitionExprToPbSnafu , Result , SchemaInUseSnafu , SchemaNotFoundSnafu , SchemaReadOnlySnafu ,
90+ SubstraitCodecSnafu , TableAlreadyExistsSnafu , TableMetadataManagerSnafu , TableNotFoundSnafu ,
9191 UnrecognizedTableOptionSnafu , ViewAlreadyExistsSnafu ,
9292} ;
93- use crate :: expr_helper;
93+ use crate :: expr_helper:: { self , RepartitionRequest } ;
9494use crate :: statement:: StatementExecutor ;
9595use crate :: statement:: show:: create_partitions_stmt;
9696
@@ -1262,17 +1262,175 @@ impl StatementExecutor {
12621262 alter_table. alter_operation( ) ,
12631263 AlterTableOperation :: Repartition { .. }
12641264 ) {
1265- let _request = expr_helper:: to_repartition_request ( alter_table, & query_context) ?;
1266- return NotSupportedSnafu {
1267- feat : "ALTER TABLE REPARTITION" ,
1268- }
1269- . fail ( ) ;
1265+ let request = expr_helper:: to_repartition_request ( alter_table, & query_context) ?;
1266+ return self . repartition_table ( request, & query_context) . await ;
12701267 }
12711268
12721269 let expr = expr_helper:: to_alter_table_expr ( alter_table, & query_context) ?;
12731270 self . alter_table_inner ( expr, query_context) . await
12741271 }
12751272
1273+ #[ tracing:: instrument( skip_all) ]
1274+ pub async fn repartition_table (
1275+ & self ,
1276+ request : RepartitionRequest ,
1277+ query_context : & QueryContextRef ,
1278+ ) -> Result < Output > {
1279+ // Check if the schema is read-only.
1280+ ensure ! (
1281+ !is_readonly_schema( & request. schema_name) ,
1282+ SchemaReadOnlySnafu {
1283+ name: request. schema_name. clone( )
1284+ }
1285+ ) ;
1286+
1287+ let catalog_name = request. catalog_name ;
1288+ let schema_name = request. schema_name ;
1289+ let table_name = request. table_name ;
1290+ // Get the table from the catalog.
1291+ let table = self
1292+ . catalog_manager
1293+ . table (
1294+ & catalog_name,
1295+ & schema_name,
1296+ & table_name,
1297+ Some ( query_context) ,
1298+ )
1299+ . await
1300+ . context ( CatalogSnafu ) ?
1301+ . with_context ( || TableNotFoundSnafu {
1302+ table_name : format_full_table_name ( & catalog_name, & schema_name, & table_name) ,
1303+ } ) ?;
1304+ let table_id = table. table_info ( ) . ident . table_id ;
1305+ // Get existing partition expressions from the table route.
1306+ let ( physical_table_id, physical_table_route) = self
1307+ . table_metadata_manager
1308+ . table_route_manager ( )
1309+ . get_physical_table_route ( table_id)
1310+ . await
1311+ . context ( TableMetadataManagerSnafu ) ?;
1312+
1313+ ensure ! (
1314+ physical_table_id == table_id,
1315+ NotSupportedSnafu {
1316+ feat: "REPARTITION on logical tables"
1317+ }
1318+ ) ;
1319+
1320+ let table_info = table. table_info ( ) ;
1321+ // Get partition column names from the table metadata.
1322+ let existing_partition_columns: Vec < String > =
1323+ table_info. meta . partition_column_names ( ) . cloned ( ) . collect ( ) ;
1324+ // Repartition requires the table to have partition columns.
1325+ ensure ! (
1326+ !existing_partition_columns. is_empty( ) ,
1327+ InvalidPartitionRuleSnafu {
1328+ reason: format!(
1329+ "table {}.{}.{} does not have partition columns, cannot repartition" ,
1330+ catalog_name, schema_name, table_name
1331+ )
1332+ }
1333+ ) ;
1334+
1335+ // Build column name to type mapping for partition columns only.
1336+ let table_schema = & table_info. meta . schema ;
1337+ let column_schemas = table_schema. column_schemas ( ) ;
1338+ // Repartition operations involving columns outside the existing partition columns are not supported.
1339+ // This restriction ensures repartition only applies to current partition columns.
1340+ let column_name_and_type: HashMap < & String , ConcreteDataType > = existing_partition_columns
1341+ . iter ( )
1342+ . map ( |pc| {
1343+ let column = column_schemas
1344+ . iter ( )
1345+ . find ( |c| & c. name == pc)
1346+ // partition column must exist in schema
1347+ . unwrap ( ) ;
1348+ ( & column. name , column. data_type . clone ( ) )
1349+ } )
1350+ . collect ( ) ;
1351+ let timezone = query_context. timezone ( ) ;
1352+ // Convert SQL Exprs to PartitionExprs.
1353+ let from_partition_exprs = request
1354+ . from_exprs
1355+ . iter ( )
1356+ . map ( |expr| convert_one_expr ( expr, & column_name_and_type, & timezone) )
1357+ . collect :: < Result < Vec < _ > > > ( ) ?;
1358+
1359+ let into_partition_exprs = request
1360+ . into_exprs
1361+ . iter ( )
1362+ . map ( |expr| convert_one_expr ( expr, & column_name_and_type, & timezone) )
1363+ . collect :: < Result < Vec < _ > > > ( ) ?;
1364+
1365+ // Parse existing partition expressions from region routes.
1366+ let mut existing_partition_exprs =
1367+ Vec :: with_capacity ( physical_table_route. region_routes . len ( ) ) ;
1368+ for route in & physical_table_route. region_routes {
1369+ let expr_json = route. region . partition_expr ( ) ;
1370+ if !expr_json. is_empty ( ) {
1371+ match PartitionExpr :: from_json_str ( & expr_json) {
1372+ Ok ( Some ( expr) ) => existing_partition_exprs. push ( expr) ,
1373+ Ok ( None ) => {
1374+ // Empty
1375+ }
1376+ Err ( e) => {
1377+ return Err ( e) . context ( DeserializePartitionExprSnafu ) ;
1378+ }
1379+ }
1380+ }
1381+ }
1382+
1383+ // Validate that from_partition_exprs are a subset of existing partition exprs.
1384+ // We compare PartitionExpr directly since it implements Eq.
1385+ for from_expr in & from_partition_exprs {
1386+ ensure ! (
1387+ existing_partition_exprs. contains( from_expr) ,
1388+ InvalidPartitionRuleSnafu {
1389+ reason: format!(
1390+ "partition expression '{}' does not exist in table {}.{}.{}" ,
1391+ from_expr, catalog_name, schema_name, table_name
1392+ )
1393+ }
1394+ ) ;
1395+ }
1396+
1397+ // Build the new partition expressions:
1398+ // new_exprs = existing_exprs - from_exprs + into_exprs
1399+ let new_partition_exprs: Vec < PartitionExpr > = existing_partition_exprs
1400+ . into_iter ( )
1401+ . filter ( |expr| !from_partition_exprs. contains ( expr) )
1402+ . chain ( into_partition_exprs. clone ( ) . into_iter ( ) )
1403+ . collect ( ) ;
1404+ let new_partition_exprs_len = new_partition_exprs. len ( ) ;
1405+
1406+ // Validate the new partition expressions using MultiDimPartitionRule and PartitionChecker.
1407+ let _ = MultiDimPartitionRule :: try_new (
1408+ existing_partition_columns. clone ( ) ,
1409+ vec ! [ ] ,
1410+ new_partition_exprs,
1411+ true ,
1412+ )
1413+ . context ( InvalidPartitionSnafu ) ?;
1414+
1415+ info ! (
1416+ "Repartition table {}.{}.{} (table_id={}) from {:?} to {:?}, new partition count: {}" ,
1417+ catalog_name,
1418+ schema_name,
1419+ table_name,
1420+ table_id,
1421+ from_partition_exprs,
1422+ into_partition_exprs,
1423+ new_partition_exprs_len
1424+ ) ;
1425+
1426+ // TODO(weny): Implement the repartition procedure submission.
1427+ // The repartition procedure infrastructure is not yet fully integrated with the DDL task system.
1428+ NotSupportedSnafu {
1429+ feat : "ALTER TABLE REPARTITION" ,
1430+ }
1431+ . fail ( )
1432+ }
1433+
12761434 #[ tracing:: instrument( skip_all) ]
12771435 pub async fn alter_table_inner (
12781436 & self ,
0 commit comments