|
15 | 15 | #include <Storages/StorageMaterializedView.h> |
16 | 16 | #include <Common/escapeForFileName.h> |
17 | 17 | #include <Common/quoteString.h> |
| 18 | +#include <Common/typeid_cast.h> |
18 | 19 | #include <Common/thread_local_rng.h> |
| 20 | +#include <Common/likePatternToRegexp.h> |
| 21 | +#include <Common/re2.h> |
19 | 22 | #include <Core/Settings.h> |
20 | 23 | #include <Databases/DatabaseReplicated.h> |
21 | 24 |
|
@@ -381,6 +384,28 @@ BlockIO InterpreterDropQuery::executeToDatabase(const ASTDropQuery & query) |
381 | 384 | return res; |
382 | 385 | } |
383 | 386 |
|
| 387 | +bool matchesLikePattern(const String & haystack, |
| 388 | + const String & like_pattern, |
| 389 | + bool case_insensitive) |
| 390 | +{ |
| 391 | + /// Converts LIKE pattern (with % and _) to a RE2 pattern |
| 392 | + String regex_str = likePatternToRegexp(like_pattern); |
| 393 | + |
| 394 | + /// Sets up RE2 with case insensitivity if needed |
| 395 | + RE2::Options options; |
| 396 | + options.set_log_errors(false); |
| 397 | + if (case_insensitive) |
| 398 | + options.set_case_sensitive(false); |
| 399 | + |
| 400 | + /// Builds the RE2 regex |
| 401 | + RE2 re(regex_str, options); |
| 402 | + if (!re.ok()) |
| 403 | + throw Exception(ErrorCodes::SYNTAX_ERROR, "Invalid regex: {}", regex_str); |
| 404 | + |
| 405 | + /// Returns true if the entire string matches |
| 406 | + return RE2::PartialMatch(haystack, re); |
| 407 | +} |
| 408 | + |
384 | 409 | BlockIO InterpreterDropQuery::executeToDatabaseImpl(const ASTDropQuery & query, DatabasePtr & database, std::vector<UUID> & uuids_to_wait) |
385 | 410 | { |
386 | 411 | if (query.kind != ASTDropQuery::Kind::Detach && query.kind != ASTDropQuery::Kind::Drop && query.kind != ASTDropQuery::Kind::Truncate) |
@@ -421,99 +446,165 @@ BlockIO InterpreterDropQuery::executeToDatabaseImpl(const ASTDropQuery & query, |
421 | 446 | query_for_table.kind = query.kind; |
422 | 447 | // For truncate operation on database, drop the tables |
423 | 448 | if (truncate) |
424 | | - query_for_table.kind = query.has_all_tables ? ASTDropQuery::Kind::Truncate : ASTDropQuery::Kind::Drop; |
| 449 | + query_for_table.kind = query.has_tables ? ASTDropQuery::Kind::Truncate : ASTDropQuery::Kind::Drop; |
425 | 450 | query_for_table.if_exists = true; |
426 | 451 | query_for_table.if_empty = false; |
427 | 452 | query_for_table.setDatabase(database_name); |
428 | 453 | query_for_table.sync = query.sync; |
429 | 454 |
|
430 | | - /// Flush should not be done if shouldBeEmptyOnDetach() == false, |
431 | | - /// since in this case getTablesIterator() may do some additional work, |
432 | | - /// see DatabaseMaterialized...SQL::getTablesIterator() |
433 | | - auto table_context = Context::createCopy(getContext()); |
434 | | - table_context->setInternalQuery(true); |
435 | | - |
436 | | - /// List the tables, then call flushAndPrepareForShutdown() on them in parallel, then call |
437 | | - /// executeToTableImpl on them in sequence. |
438 | | - /// |
439 | | - /// Complication: if some tables (refreshable materialized views) have background tasks that |
440 | | - /// create/drop other tables, we have to stop those tasks first (using flushAndPrepareForShutdown()), |
441 | | - /// then list tables again. |
| 455 | + /// If we have a TRUNCATE TABLES .. LIKE, we should not truncate all tables, |
| 456 | + /// the logic regarding finding suitable tables is a bit below |
| 457 | + if (!truncate || !query.has_tables || query.like.empty()) |
| 458 | + { |
| 459 | + /// Flush should not be done if shouldBeEmptyOnDetach() == false, |
| 460 | + /// since in this case getTablesIterator() may do some additional work, |
| 461 | + /// see DatabaseMaterialized...SQL::getTablesIterator() |
| 462 | + auto table_context = Context::createCopy(getContext()); |
| 463 | + table_context->setInternalQuery(true); |
| 464 | + |
| 465 | + /// List the tables, then call flushAndPrepareForShutdown() on them in parallel, then call |
| 466 | + /// executeToTableImpl on them in sequence. |
| 467 | + /// |
| 468 | + /// Complication: if some tables (refreshable materialized views) have background tasks that |
| 469 | + /// create/drop other tables, we have to stop those tasks first (using flushAndPrepareForShutdown()), |
| 470 | + /// then list tables again. |
| 471 | + |
| 472 | + std::unordered_set<UUID> prepared_tables; |
| 473 | + std::vector<std::pair<StorageID, bool>> tables_to_drop; |
| 474 | + std::vector<StoragePtr> tables_to_prepare; |
| 475 | + |
| 476 | + auto collect_tables = [&] { |
| 477 | + // NOTE: This means we wait for all tables to be loaded inside getTablesIterator() call in case of `async_load_databases = true`. |
| 478 | + for (auto iterator = database->getTablesIterator(table_context); iterator->isValid(); iterator->next()) |
| 479 | + { |
| 480 | + auto table_ptr = iterator->table(); |
| 481 | + StorageID storage_id = table_ptr->getStorageID(); |
| 482 | + tables_to_drop.push_back({storage_id, table_ptr->isDictionary()}); |
| 483 | + /// If the database doesn't support table UUIDs, we might call |
| 484 | + /// IStorage::flushAndPrepareForShutdown() twice. That's ok. |
| 485 | + /// (And shouldn't normally happen because refreshable materialized views don't work |
| 486 | + /// in such DBs.) |
| 487 | + if (!storage_id.hasUUID() || !prepared_tables.contains(storage_id.uuid)) |
| 488 | + tables_to_prepare.push_back(table_ptr); |
| 489 | + } |
| 490 | + }; |
| 491 | + |
| 492 | + auto prepare_tables = [&](std::vector<StoragePtr> & tables) |
| 493 | + { |
| 494 | + /// Prepare tables for shutdown in parallel. |
| 495 | + ThreadPoolCallbackRunnerLocal<void> runner(getDatabaseCatalogDropTablesThreadPool().get(), "DropTables"); |
| 496 | + for (StoragePtr & table_ptr : tables) |
| 497 | + { |
| 498 | + StorageID storage_id = table_ptr->getStorageID(); |
| 499 | + if (storage_id.hasUUID()) |
| 500 | + prepared_tables.insert(storage_id.uuid); |
| 501 | + runner([my_table_ptr = std::move(table_ptr)]() |
| 502 | + { |
| 503 | + my_table_ptr->flushAndPrepareForShutdown(); |
| 504 | + }); |
| 505 | + } |
| 506 | + runner.waitForAllToFinishAndRethrowFirstError(); |
| 507 | + tables.clear(); // don't hold extra shared pointers |
| 508 | + }; |
442 | 509 |
|
443 | | - std::unordered_set<UUID> prepared_tables; |
444 | | - std::vector<std::pair<StorageID, bool>> tables_to_drop; |
445 | | - std::vector<StoragePtr> tables_to_prepare; |
| 510 | + collect_tables(); |
446 | 511 |
|
447 | | - auto collect_tables = [&] { |
448 | | - // NOTE: This means we wait for all tables to be loaded inside getTablesIterator() call in case of `async_load_databases = true`. |
449 | | - for (auto iterator = database->getTablesIterator(table_context); iterator->isValid(); iterator->next()) |
| 512 | + /// If there are refreshable materialized views, we need to stop them before getting the |
| 513 | + /// final list of tables to drop. |
| 514 | + std::vector<StoragePtr> tables_to_prepare_early; |
| 515 | + for (const StoragePtr & table_ptr : tables_to_prepare) |
450 | 516 | { |
451 | | - auto table_ptr = iterator->table(); |
452 | | - StorageID storage_id = table_ptr->getStorageID(); |
453 | | - tables_to_drop.push_back({storage_id, table_ptr->isDictionary()}); |
454 | | - /// If the database doesn't support table UUIDs, we might call |
455 | | - /// IStorage::flushAndPrepareForShutdown() twice. That's ok. |
456 | | - /// (And shouldn't normally happen because refreshable materialized views don't work |
457 | | - /// in such DBs.) |
458 | | - if (!storage_id.hasUUID() || !prepared_tables.contains(storage_id.uuid)) |
459 | | - tables_to_prepare.push_back(table_ptr); |
| 517 | + if (const auto * materialized_view = typeid_cast<const StorageMaterializedView *>(table_ptr.get())) |
| 518 | + { |
| 519 | + if (materialized_view->canCreateOrDropOtherTables()) |
| 520 | + tables_to_prepare_early.push_back(table_ptr); |
| 521 | + } |
460 | 522 | } |
461 | | - }; |
462 | | - |
463 | | - auto prepare_tables = [&](std::vector<StoragePtr> & tables) |
464 | | - { |
465 | | - /// Prepare tables for shutdown in parallel. |
466 | | - ThreadPoolCallbackRunnerLocal<void> runner(getDatabaseCatalogDropTablesThreadPool().get(), "DropTables"); |
467 | | - for (StoragePtr & table_ptr : tables) |
| 523 | + if (!tables_to_prepare_early.empty()) |
468 | 524 | { |
469 | | - StorageID storage_id = table_ptr->getStorageID(); |
470 | | - if (storage_id.hasUUID()) |
471 | | - prepared_tables.insert(storage_id.uuid); |
472 | | - runner([my_table_ptr = std::move(table_ptr)]() |
473 | | - { |
474 | | - my_table_ptr->flushAndPrepareForShutdown(); |
475 | | - }); |
| 525 | + tables_to_prepare.clear(); |
| 526 | + tables_to_drop.clear(); |
| 527 | + |
| 528 | + prepare_tables(tables_to_prepare_early); |
| 529 | + |
| 530 | + collect_tables(); |
476 | 531 | } |
477 | | - runner.waitForAllToFinishAndRethrowFirstError(); |
478 | | - tables.clear(); // don't hold extra shared pointers |
479 | | - }; |
480 | 532 |
|
481 | | - collect_tables(); |
| 533 | + prepare_tables(tables_to_prepare); |
482 | 534 |
|
483 | | - /// If there are refreshable materialized views, we need to stop them before getting the |
484 | | - /// final list of tables to drop. |
485 | | - std::vector<StoragePtr> tables_to_prepare_early; |
486 | | - for (const StoragePtr & table_ptr : tables_to_prepare) |
487 | | - { |
488 | | - if (const auto * materialized_view = typeid_cast<const StorageMaterializedView *>(table_ptr.get())) |
| 535 | + for (const auto & table : tables_to_drop) |
489 | 536 | { |
490 | | - if (materialized_view->canCreateOrDropOtherTables()) |
491 | | - tables_to_prepare_early.push_back(table_ptr); |
| 537 | + query_for_table.setTable(table.first.getTableName()); |
| 538 | + query_for_table.is_dictionary = table.second; |
| 539 | + DatabasePtr db; |
| 540 | + UUID table_to_wait = UUIDHelpers::Nil; |
| 541 | + /// Note: if this throws exception, the remaining tables won't be dropped and will stay in a |
| 542 | + /// limbo state where flushAndPrepareForShutdown() was called but no shutdown() followed. Not ideal. |
| 543 | + executeToTableImpl(table_context, query_for_table, db, table_to_wait); |
| 544 | + uuids_to_wait.push_back(table_to_wait); |
492 | 545 | } |
493 | 546 | } |
494 | | - if (!tables_to_prepare_early.empty()) |
495 | | - { |
496 | | - tables_to_prepare.clear(); |
497 | | - tables_to_drop.clear(); |
| 547 | + } |
498 | 548 |
|
499 | | - prepare_tables(tables_to_prepare_early); |
| 549 | + /// In case of TRUNCATE TABLES .. LIKE, we truncate only suitable tables |
| 550 | + if (truncate && query.has_tables && !query.like.empty()) |
| 551 | + { |
| 552 | + auto table_context = Context::createCopy(getContext()); |
| 553 | + table_context->setInternalQuery(true); |
500 | 554 |
|
501 | | - collect_tables(); |
| 555 | + std::vector<StorageID> tables_to_truncate; |
| 556 | + for (auto it = database->getTablesIterator(table_context); it->isValid(); it->next()) |
| 557 | + { |
| 558 | + const auto & table_ptr = it->table(); |
| 559 | + const auto & storage_id = table_ptr->getStorageID(); |
| 560 | + const auto & tname = storage_id.table_name; |
| 561 | + |
| 562 | + if (!query.like.empty()) |
| 563 | + { |
| 564 | + bool match = matchesLikePattern(tname, query.like, query.case_insensitive_like); |
| 565 | + if (query.not_like) |
| 566 | + match = !match; |
| 567 | + if (!match) |
| 568 | + continue; |
| 569 | + } |
| 570 | + tables_to_truncate.push_back(storage_id); |
502 | 571 | } |
503 | 572 |
|
504 | | - prepare_tables(tables_to_prepare); |
| 573 | + std::mutex mutex_for_uuids; |
| 574 | + ThreadPoolCallbackRunnerLocal<void> runner( |
| 575 | + getDatabaseCatalogDropTablesThreadPool().get(), |
| 576 | + "TruncTbls" |
| 577 | + ); |
505 | 578 |
|
506 | | - for (const auto & table : tables_to_drop) |
| 579 | + for (const auto & table_id : tables_to_truncate) |
507 | 580 | { |
508 | | - query_for_table.setTable(table.first.getTableName()); |
509 | | - query_for_table.is_dictionary = table.second; |
510 | | - DatabasePtr db; |
511 | | - UUID table_to_wait = UUIDHelpers::Nil; |
512 | | - /// Note: if this throws exception, the remaining tables won't be dropped and will stay in a |
513 | | - /// limbo state where flushAndPrepareForShutdown() was called but no shutdown() followed. Not ideal. |
514 | | - executeToTableImpl(table_context, query_for_table, db, table_to_wait); |
515 | | - uuids_to_wait.push_back(table_to_wait); |
| 581 | + runner([&, table_id]() |
| 582 | + { |
| 583 | + // Create a proper AST for a single-table TRUNCATE query. |
| 584 | + auto sub_query_ptr = std::make_shared<ASTDropQuery>(); |
| 585 | + auto & sub_query = sub_query_ptr->as<ASTDropQuery &>(); |
| 586 | + sub_query.kind = ASTDropQuery::Kind::Truncate; |
| 587 | + sub_query.if_exists = true; |
| 588 | + sub_query.sync = query.sync; |
| 589 | + // Set the target database and table: |
| 590 | + sub_query.setDatabase(table_id.database_name); |
| 591 | + sub_query.setTable(table_id.table_name); |
| 592 | + // Optionally, add these nodes to sub_query->children if needed: |
| 593 | + sub_query.children.push_back(std::make_shared<ASTIdentifier>(table_id.database_name)); |
| 594 | + sub_query.children.push_back(std::make_shared<ASTIdentifier>(table_id.table_name)); |
| 595 | + |
| 596 | + DatabasePtr dummy_db; |
| 597 | + UUID table_uuid = UUIDHelpers::Nil; |
| 598 | + executeToTableImpl(table_context, sub_query, dummy_db, table_uuid); |
| 599 | + |
| 600 | + if (query.sync) |
| 601 | + { |
| 602 | + std::lock_guard<std::mutex> lock(mutex_for_uuids); |
| 603 | + uuids_to_wait.push_back(table_uuid); |
| 604 | + } |
| 605 | + }); |
516 | 606 | } |
| 607 | + runner.waitForAllToFinishAndRethrowFirstError(); |
517 | 608 | } |
518 | 609 |
|
519 | 610 | // only if operation is DETACH |
|
0 commit comments