1#include <Poco/File.h>
2
3#include <Databases/IDatabase.h>
4#include <Interpreters/Context.h>
5#include <Interpreters/DDLWorker.h>
6#include <Interpreters/InterpreterDropQuery.h>
7#include <Interpreters/ExternalDictionariesLoader.h>
8#include <Parsers/ASTDropQuery.h>
9#include <Storages/IStorage.h>
10#include <Common/escapeForFileName.h>
11#include <Common/quoteString.h>
12#include <Common/typeid_cast.h>
13
14
15namespace DB
16{
17
18namespace ErrorCodes
19{
20 extern const int TABLE_WAS_NOT_DROPPED;
21 extern const int DATABASE_NOT_EMPTY;
22 extern const int UNKNOWN_DATABASE;
23 extern const int READONLY;
24 extern const int LOGICAL_ERROR;
25 extern const int SYNTAX_ERROR;
26 extern const int UNKNOWN_TABLE;
27 extern const int QUERY_IS_PROHIBITED;
28 extern const int UNKNOWN_DICTIONARY;
29}
30
31
32InterpreterDropQuery::InterpreterDropQuery(const ASTPtr & query_ptr_, Context & context_) : query_ptr(query_ptr_), context(context_) {}
33
34
35BlockIO InterpreterDropQuery::execute()
36{
37 auto & drop = query_ptr->as<ASTDropQuery &>();
38
39 checkAccess(drop);
40
41 if (!drop.cluster.empty())
42 return executeDDLQueryOnCluster(query_ptr, context, {drop.database});
43
44 if (!drop.table.empty())
45 {
46 if (!drop.is_dictionary)
47 return executeToTable(drop.database, drop.table, drop.kind, drop.if_exists, drop.temporary, drop.no_ddl_lock);
48 else
49 return executeToDictionary(drop.database, drop.table, drop.kind, drop.if_exists, drop.temporary, drop.no_ddl_lock);
50 }
51 else if (!drop.database.empty())
52 return executeToDatabase(drop.database, drop.kind, drop.if_exists);
53 else
54 throw Exception("Nothing to drop, both names are empty.", ErrorCodes::LOGICAL_ERROR);
55}
56
57
58BlockIO InterpreterDropQuery::executeToTable(
59 String & database_name_,
60 String & table_name,
61 ASTDropQuery::Kind kind,
62 bool if_exists,
63 bool if_temporary,
64 bool no_ddl_lock)
65{
66 if (if_temporary || database_name_.empty())
67 {
68 auto & session_context = context.hasSessionContext() ? context.getSessionContext() : context;
69
70 if (session_context.isExternalTableExist(table_name))
71 return executeToTemporaryTable(table_name, kind);
72 }
73
74 String database_name = database_name_.empty() ? context.getCurrentDatabase() : database_name_;
75
76 auto ddl_guard = (!no_ddl_lock ? context.getDDLGuard(database_name, table_name) : nullptr);
77
78 DatabaseAndTable database_and_table = tryGetDatabaseAndTable(database_name, table_name, if_exists);
79
80 if (database_and_table.first && database_and_table.second)
81 {
82 if (kind == ASTDropQuery::Kind::Detach)
83 {
84 database_and_table.second->shutdown();
85 /// If table was already dropped by anyone, an exception will be thrown
86 auto table_lock = database_and_table.second->lockExclusively(context.getCurrentQueryId());
87 /// Drop table from memory, don't touch data and metadata
88 database_and_table.first->detachTable(database_and_table.second->getTableName());
89 }
90 else if (kind == ASTDropQuery::Kind::Truncate)
91 {
92 database_and_table.second->checkTableCanBeDropped();
93
94 /// If table was already dropped by anyone, an exception will be thrown
95 auto table_lock = database_and_table.second->lockExclusively(context.getCurrentQueryId());
96 /// Drop table data, don't touch metadata
97 database_and_table.second->truncate(query_ptr, context, table_lock);
98 }
99 else if (kind == ASTDropQuery::Kind::Drop)
100 {
101 database_and_table.second->checkTableCanBeDropped();
102
103 database_and_table.second->shutdown();
104 /// If table was already dropped by anyone, an exception will be thrown
105
106 auto table_lock = database_and_table.second->lockExclusively(context.getCurrentQueryId());
107
108 const std::string metadata_file_without_extension =
109 database_and_table.first->getMetadataPath()
110 + escapeForFileName(database_and_table.second->getTableName());
111
112 const auto prev_metadata_name = metadata_file_without_extension + ".sql";
113 const auto drop_metadata_name = metadata_file_without_extension + ".sql.tmp_drop";
114
115 /// Try to rename metadata file and delete the data
116 try
117 {
118 /// There some kind of tables that have no metadata - ignore renaming
119 if (Poco::File(prev_metadata_name).exists())
120 Poco::File(prev_metadata_name).renameTo(drop_metadata_name);
121 /// Delete table data
122 database_and_table.second->drop(table_lock);
123 }
124 catch (...)
125 {
126 if (Poco::File(drop_metadata_name).exists())
127 Poco::File(drop_metadata_name).renameTo(prev_metadata_name);
128 throw;
129 }
130
131 String table_data_path_relative = database_and_table.first->getTableDataPath(table_name);
132
133 /// Delete table metadata and table itself from memory
134 database_and_table.first->removeTable(context, database_and_table.second->getTableName());
135 database_and_table.second->is_dropped = true;
136
137 /// If it is not virtual database like Dictionary then drop remaining data dir
138 if (!table_data_path_relative.empty())
139 {
140 String table_data_path = context.getPath() + table_data_path_relative;
141 if (Poco::File(table_data_path).exists())
142 Poco::File(table_data_path).remove(true);
143 }
144 }
145 }
146
147 return {};
148}
149
150
151BlockIO InterpreterDropQuery::executeToDictionary(
152 String & database_name_,
153 String & dictionary_name,
154 ASTDropQuery::Kind kind,
155 bool if_exists,
156 bool is_temporary,
157 bool no_ddl_lock)
158{
159 if (is_temporary)
160 throw Exception("Temporary dictionaries are not possible.", ErrorCodes::SYNTAX_ERROR);
161
162 String database_name = database_name_.empty() ? context.getCurrentDatabase() : database_name_;
163
164 auto ddl_guard = (!no_ddl_lock ? context.getDDLGuard(database_name, dictionary_name) : nullptr);
165
166 DatabasePtr database = tryGetDatabase(database_name, if_exists);
167
168 if (!database || !database->isDictionaryExist(context, dictionary_name))
169 {
170 if (!if_exists)
171 throw Exception(
172 "Dictionary " + backQuoteIfNeed(database_name) + "." + backQuoteIfNeed(dictionary_name) + " doesn't exist.",
173 ErrorCodes::UNKNOWN_DICTIONARY);
174 else
175 return {};
176 }
177
178 if (kind == ASTDropQuery::Kind::Detach)
179 {
180 /// Drop dictionary from memory, don't touch data and metadata
181 database->detachDictionary(dictionary_name, context);
182 }
183 else if (kind == ASTDropQuery::Kind::Truncate)
184 {
185 throw Exception("Cannot TRUNCATE dictionary", ErrorCodes::SYNTAX_ERROR);
186 }
187 else if (kind == ASTDropQuery::Kind::Drop)
188 {
189 database->removeDictionary(context, dictionary_name);
190 }
191 return {};
192}
193
194BlockIO InterpreterDropQuery::executeToTemporaryTable(String & table_name, ASTDropQuery::Kind kind)
195{
196 if (kind == ASTDropQuery::Kind::Detach)
197 throw Exception("Unable to detach temporary table.", ErrorCodes::SYNTAX_ERROR);
198 else
199 {
200 auto & context_handle = context.hasSessionContext() ? context.getSessionContext() : context;
201 StoragePtr table = context_handle.tryGetExternalTable(table_name);
202 if (table)
203 {
204 if (kind == ASTDropQuery::Kind::Truncate)
205 {
206 /// If table was already dropped by anyone, an exception will be thrown
207 auto table_lock = table->lockExclusively(context.getCurrentQueryId());
208 /// Drop table data, don't touch metadata
209 table->truncate(query_ptr, context, table_lock);
210 }
211 else if (kind == ASTDropQuery::Kind::Drop)
212 {
213 context_handle.tryRemoveExternalTable(table_name);
214 table->shutdown();
215 /// If table was already dropped by anyone, an exception will be thrown
216 auto table_lock = table->lockExclusively(context.getCurrentQueryId());
217 /// Delete table data
218 table->drop(table_lock);
219 table->is_dropped = true;
220 }
221 }
222 }
223
224 return {};
225}
226
227BlockIO InterpreterDropQuery::executeToDatabase(String & database_name, ASTDropQuery::Kind kind, bool if_exists)
228{
229 auto ddl_guard = context.getDDLGuard(database_name, "");
230
231 if (auto database = tryGetDatabase(database_name, if_exists))
232 {
233 if (kind == ASTDropQuery::Kind::Truncate)
234 {
235 throw Exception("Unable to truncate database.", ErrorCodes::SYNTAX_ERROR);
236 }
237 else if (kind == ASTDropQuery::Kind::Detach)
238 {
239 context.detachDatabase(database_name);
240 database->shutdown();
241 }
242 else if (kind == ASTDropQuery::Kind::Drop)
243 {
244 for (auto iterator = database->getTablesIterator(context); iterator->isValid(); iterator->next())
245 {
246 String current_table_name = iterator->name();
247 executeToTable(database_name, current_table_name, kind, false, false, false);
248 }
249
250 for (auto iterator = database->getDictionariesIterator(context); iterator->isValid(); iterator->next())
251 {
252 String current_dictionary = iterator->name();
253 executeToDictionary(database_name, current_dictionary, kind, false, false, false);
254 }
255
256 auto context_lock = context.getLock();
257
258 /// Someone could have time to delete the database before us.
259 context.assertDatabaseExists(database_name);
260
261 /// Someone could have time to create a table in the database to be deleted while we deleted the tables without the context lock.
262 if (!context.getDatabase(database_name)->empty(context))
263 throw Exception("New table appeared in database being dropped. Try dropping it again.", ErrorCodes::DATABASE_NOT_EMPTY);
264
265 /// Delete database information from the RAM
266 context.detachDatabase(database_name);
267
268 database->shutdown();
269
270 /// Delete the database.
271 database->drop(context);
272
273 /// Old ClickHouse versions did not store database.sql files
274 Poco::File database_metadata_file(context.getPath() + "metadata/" + escapeForFileName(database_name) + ".sql");
275 if (database_metadata_file.exists())
276 database_metadata_file.remove(false);
277 }
278 }
279
280 return {};
281}
282
283DatabasePtr InterpreterDropQuery::tryGetDatabase(String & database_name, bool if_exists)
284{
285 return if_exists ? context.tryGetDatabase(database_name) : context.getDatabase(database_name);
286}
287
288DatabaseAndTable InterpreterDropQuery::tryGetDatabaseAndTable(String & database_name, String & table_name, bool if_exists)
289{
290 DatabasePtr database = tryGetDatabase(database_name, if_exists);
291
292 if (database)
293 {
294 StoragePtr table = database->tryGetTable(context, table_name);
295 if (!table && !if_exists)
296 throw Exception("Table " + backQuoteIfNeed(database_name) + "." + backQuoteIfNeed(table_name) + " doesn't exist.",
297 ErrorCodes::UNKNOWN_TABLE);
298
299 return {std::move(database), std::move(table)};
300 }
301 return {};
302}
303
304void InterpreterDropQuery::checkAccess(const ASTDropQuery & drop)
305{
306 const Settings & settings = context.getSettingsRef();
307 auto readonly = settings.readonly;
308 bool allow_ddl = settings.allow_ddl;
309
310 /// It's allowed to drop temporary tables.
311 if ((!readonly && allow_ddl) || (drop.database.empty() && context.tryGetExternalTable(drop.table) && readonly >= 2))
312 return;
313
314 if (readonly)
315 throw Exception("Cannot drop table in readonly mode", ErrorCodes::READONLY);
316
317 throw Exception("Cannot drop table. DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED);
318}
319
320}
321