Skip to content

Commit 5fed831

Browse files
authored
Merge pull request ClickHouse#78597 from yariks5s/truncate_all_tables_with_like
Support for `TRUNCATE TABLES FROM .. LIKE ..` syntax
2 parents d74c7c1 + ac2cf73 commit 5fed831

File tree

7 files changed

+328
-78
lines changed

7 files changed

+328
-78
lines changed

docs/en/sql-reference/statements/truncate.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ If the `alter_sync` is set to `2` and some replicas are not active for more than
2727

2828
## TRUNCATE ALL TABLES {#truncate-all-tables}
2929
```sql
30-
TRUNCATE ALL TABLES FROM [IF EXISTS] db [ON CLUSTER cluster]
30+
TRUNCATE [ALL] TABLES FROM [IF EXISTS] db [LIKE | ILIKE | NOT LIKE '<pattern>'] [ON CLUSTER cluster]
3131
```
3232

3333
Removes all data from all tables in a database.

src/Interpreters/InterpreterDropQuery.cpp

Lines changed: 161 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
#include <Storages/StorageMaterializedView.h>
1616
#include <Common/escapeForFileName.h>
1717
#include <Common/quoteString.h>
18+
#include <Common/typeid_cast.h>
1819
#include <Common/thread_local_rng.h>
20+
#include <Common/likePatternToRegexp.h>
21+
#include <Common/re2.h>
1922
#include <Core/Settings.h>
2023
#include <Databases/DatabaseReplicated.h>
2124

@@ -381,6 +384,28 @@ BlockIO InterpreterDropQuery::executeToDatabase(const ASTDropQuery & query)
381384
return res;
382385
}
383386

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+
384409
BlockIO InterpreterDropQuery::executeToDatabaseImpl(const ASTDropQuery & query, DatabasePtr & database, std::vector<UUID> & uuids_to_wait)
385410
{
386411
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,
421446
query_for_table.kind = query.kind;
422447
// For truncate operation on database, drop the tables
423448
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;
425450
query_for_table.if_exists = true;
426451
query_for_table.if_empty = false;
427452
query_for_table.setDatabase(database_name);
428453
query_for_table.sync = query.sync;
429454

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+
};
442509

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();
446511

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)
450516
{
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+
}
460522
}
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())
468524
{
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();
476531
}
477-
runner.waitForAllToFinishAndRethrowFirstError();
478-
tables.clear(); // don't hold extra shared pointers
479-
};
480532

481-
collect_tables();
533+
prepare_tables(tables_to_prepare);
482534

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)
489536
{
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);
492545
}
493546
}
494-
if (!tables_to_prepare_early.empty())
495-
{
496-
tables_to_prepare.clear();
497-
tables_to_drop.clear();
547+
}
498548

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);
500554

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);
502571
}
503572

504-
prepare_tables(tables_to_prepare);
573+
std::mutex mutex_for_uuids;
574+
ThreadPoolCallbackRunnerLocal<void> runner(
575+
getDatabaseCatalogDropTablesThreadPool().get(),
576+
"TruncTbls"
577+
);
505578

506-
for (const auto & table : tables_to_drop)
579+
for (const auto & table_id : tables_to_truncate)
507580
{
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+
});
516606
}
607+
runner.waitForAllToFinishAndRethrowFirstError();
517608
}
518609

519610
// only if operation is DETACH

src/Parsers/ASTDropQuery.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <Parsers/ASTDropQuery.h>
22
#include <Parsers/ASTIdentifier.h>
33
#include <Parsers/ASTExpressionList.h>
4+
#include <Common/quoteString.h>
45
#include <IO/Operators.h>
56

67

@@ -47,8 +48,10 @@ void ASTDropQuery::formatQueryImpl(WriteBuffer & ostr, const FormatSettings & se
4748
if (temporary)
4849
ostr << "TEMPORARY ";
4950

50-
if (has_all_tables)
51-
ostr << "ALL TABLES FROM ";
51+
if (has_all)
52+
ostr << "ALL ";
53+
if (has_tables)
54+
ostr << "TABLES FROM ";
5255
else if (!table && !database_and_tables && database)
5356
ostr << "DATABASE ";
5457
else if (is_dictionary)
@@ -105,6 +108,13 @@ void ASTDropQuery::formatQueryImpl(WriteBuffer & ostr, const FormatSettings & se
105108
table->format(ostr, settings, state, frame);
106109
}
107110

111+
if (!like.empty())
112+
ostr << (settings.hilite ? hilite_keyword : "")
113+
<< (not_like ? " NOT" : "")
114+
<< (case_insensitive_like ? " ILIKE " : " LIKE")
115+
<< (settings.hilite ? hilite_none : "")
116+
<< DB::quote << like;
117+
108118
formatOnCluster(ostr, settings);
109119

110120
if (permanently)

src/Parsers/ASTDropQuery.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,16 @@ class ASTDropQuery : public ASTQueryWithTableAndOutput, public ASTQueryWithOnClu
2727
bool no_ddl_lock{false};
2828

2929
/// For `TRUNCATE ALL TABLES` query
30-
bool has_all_tables{false};
30+
bool has_all{false};
31+
32+
/// For `TRUNCATE TABLES` query (basically the same as above)
33+
bool has_tables{false};
34+
35+
/// For specifying table name patterns for `TRUNCATE ALL TABLES` query
36+
String like;
37+
38+
bool not_like = false;
39+
bool case_insensitive_like = false;
3140

3241
/// We dropping dictionary, so print correct word
3342
bool is_dictionary{false};

0 commit comments

Comments
 (0)