mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-24 08:32:02 +00:00
rewrote everything again
This commit is contained in:
parent
c9628386fc
commit
765aa4d4e3
@ -193,15 +193,14 @@ private:
|
||||
/// Parsed query. Is used to determine some settings (e.g. format, output file).
|
||||
ASTPtr parsed_query;
|
||||
|
||||
/// The last exception that was received from the server. Is used for the return code in batch mode.
|
||||
std::unique_ptr<Exception> last_exception_received_from_server;
|
||||
/// The last exception that was received from the server. Is used for the
|
||||
/// return code in batch mode.
|
||||
std::unique_ptr<Exception> server_exception;
|
||||
/// Likewise, the last exception that occurred on the client.
|
||||
std::unique_ptr<Exception> client_exception;
|
||||
|
||||
/// If the last query resulted in exception.
|
||||
bool received_exception_from_server = false;
|
||||
int expected_server_error = 0;
|
||||
int expected_client_error = 0;
|
||||
int actual_server_error = 0;
|
||||
int actual_client_error = 0;
|
||||
bool have_error = false;
|
||||
|
||||
UInt64 server_revision = 0;
|
||||
String server_version;
|
||||
@ -667,19 +666,14 @@ private:
|
||||
}
|
||||
catch (const Exception & e)
|
||||
{
|
||||
actual_client_error = e.code();
|
||||
if (!actual_client_error || actual_client_error != expected_client_error)
|
||||
{
|
||||
std::cerr << std::endl
|
||||
<< "Exception on client:" << std::endl
|
||||
<< "Code: " << e.code() << ". " << e.displayText() << std::endl;
|
||||
std::cerr << std::endl
|
||||
<< "Exception on client:" << std::endl
|
||||
<< "Code: " << e.code() << ". " << e.displayText() << std::endl;
|
||||
|
||||
if (config().getBool("stacktrace", false))
|
||||
std::cerr << "Stack trace:" << std::endl << e.getStackTraceString() << std::endl;
|
||||
if (config().getBool("stacktrace", false))
|
||||
std::cerr << "Stack trace:" << std::endl << e.getStackTraceString() << std::endl;
|
||||
|
||||
std::cerr << std::endl;
|
||||
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
|
||||
/// Client-side exception during query execution can result in the loss of
|
||||
/// sync in the connection protocol.
|
||||
@ -705,9 +699,20 @@ private:
|
||||
|
||||
nonInteractive();
|
||||
|
||||
/// If exception code isn't zero, we should return non-zero return code anyway.
|
||||
if (last_exception_received_from_server)
|
||||
return last_exception_received_from_server->code() != 0 ? last_exception_received_from_server->code() : -1;
|
||||
// If exception code isn't zero, we should return non-zero return
|
||||
// code anyway.
|
||||
const auto * exception = server_exception
|
||||
? server_exception.get() : client_exception.get();
|
||||
if (exception)
|
||||
{
|
||||
return exception->code() != 0 ? exception->code() : -1;
|
||||
}
|
||||
if (have_error)
|
||||
{
|
||||
// Shouldn't be set without an exception, but check it just in
|
||||
// case so that at least we don't lose an error.
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -845,8 +850,59 @@ private:
|
||||
return processMultiQuery(text);
|
||||
}
|
||||
|
||||
// Consumes trailing semicolons and tries to consume the same-line trailing
|
||||
// comment.
|
||||
void adjustQueryEnd(const char *& this_query_end,
|
||||
const char * all_queries_end, int max_parser_depth)
|
||||
{
|
||||
// We have to skip the trailing semicolon that might be left
|
||||
// after VALUES parsing or just after a normal semicolon-terminated query.
|
||||
Tokens after_query_tokens(this_query_end, all_queries_end);
|
||||
IParser::Pos after_query_iterator(after_query_tokens, max_parser_depth);
|
||||
while (after_query_iterator.isValid()
|
||||
&& after_query_iterator->type == TokenType::Semicolon)
|
||||
{
|
||||
this_query_end = after_query_iterator->end;
|
||||
++after_query_iterator;
|
||||
}
|
||||
|
||||
// Now we have to do some extra work to add the trailing
|
||||
// same-line comment to the query, but preserve the leading
|
||||
// comments of the next query. The trailing comment is important
|
||||
// because the test hints are usually written this way, e.g.:
|
||||
// select nonexistent_column; -- { serverError 12345 }.
|
||||
// The token iterator skips comments and whitespace, so we have
|
||||
// to find the newline in the string manually. If it's earlier
|
||||
// than the next significant token, it means that the text before
|
||||
// newline is some trailing whitespace or comment, and we should
|
||||
// add it to our query. There are also several special cases
|
||||
// that are described below.
|
||||
const auto * newline = find_first_symbols<'\n'>(this_query_end,
|
||||
all_queries_end);
|
||||
const char * next_query_begin = after_query_iterator->begin;
|
||||
|
||||
// We include the entire line if the next query starts after
|
||||
// it. This is a generic case of trailing in-line comment.
|
||||
// The "equals" condition is for case of end of input (they both equal
|
||||
// all_queries_end);
|
||||
if (newline <= next_query_begin)
|
||||
{
|
||||
assert(newline >= this_query_end);
|
||||
this_query_end = newline;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Many queries on one line, can't do anything. By the way, this
|
||||
// syntax is probably going to work as expected:
|
||||
// select nonexistent /* { serverError 12345 } */; select 1
|
||||
}
|
||||
}
|
||||
|
||||
bool processMultiQuery(const String & all_queries_text)
|
||||
{
|
||||
// It makes sense not to base any control flow on this, so that it is
|
||||
// the same in tests and in normal usage. The only difference is that in
|
||||
// normal mode we ignore the test hints.
|
||||
const bool test_mode = config().has("testmode");
|
||||
|
||||
{
|
||||
@ -895,12 +951,6 @@ private:
|
||||
Tokens tokens(this_query_begin, all_queries_end);
|
||||
IParser::Pos token_iterator(tokens,
|
||||
context.getSettingsRef().max_parser_depth);
|
||||
while (token_iterator->type == TokenType::Semicolon
|
||||
&& token_iterator.isValid())
|
||||
{
|
||||
++token_iterator;
|
||||
}
|
||||
|
||||
if (!token_iterator.isValid())
|
||||
{
|
||||
break;
|
||||
@ -915,16 +965,23 @@ private:
|
||||
}
|
||||
catch (Exception & e)
|
||||
{
|
||||
if (!test_mode)
|
||||
throw;
|
||||
// Try to find test hint for syntax error. We don't know where
|
||||
// the query ends because we failed to parse it, so we consume
|
||||
// the entire line.
|
||||
this_query_end = find_first_symbols<'\n'>(this_query_end,
|
||||
all_queries_end);
|
||||
|
||||
// Try find test hint for syntax error. parseQuery() would add
|
||||
// the relevant comment to the parsed query text for us, even if
|
||||
// the parsing failed.
|
||||
TestHint hint(true /* enabled */,
|
||||
TestHint hint(test_mode,
|
||||
String(this_query_begin, this_query_end - this_query_begin));
|
||||
if (hint.serverError()) /// Syntax errors are considered as client errors
|
||||
|
||||
if (hint.serverError())
|
||||
{
|
||||
// Syntax errors are considered as client errors
|
||||
e.addMessage("\nExpected server error '{}'.",
|
||||
hint.serverError());
|
||||
throw;
|
||||
}
|
||||
|
||||
if (hint.clientError() != e.code())
|
||||
{
|
||||
if (hint.clientError())
|
||||
@ -949,7 +1006,7 @@ private:
|
||||
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
// INSERT queries may have the inserted data in the query text
|
||||
@ -960,10 +1017,14 @@ private:
|
||||
// The VALUES format needs even more handling -- we also allow the
|
||||
// data to be delimited by semicolon. This case is handled later by
|
||||
// the format parser itself.
|
||||
// We can't do multiline INSERTs with inline data, because most
|
||||
// row input formats (e.g. TSV) can't tell when the input stops,
|
||||
// unlike VALUES.
|
||||
auto * insert_ast = parsed_query->as<ASTInsertQuery>();
|
||||
if (insert_ast && insert_ast->data)
|
||||
{
|
||||
this_query_end = find_first_symbols<'\n'>(insert_ast->data, all_queries_end);
|
||||
this_query_end = find_first_symbols<'\n'>(insert_ast->data,
|
||||
all_queries_end);
|
||||
insert_ast->end = this_query_end;
|
||||
query_to_send = all_queries_text.substr(
|
||||
this_query_begin - all_queries_text.data(),
|
||||
@ -976,81 +1037,201 @@ private:
|
||||
this_query_end - this_query_begin);
|
||||
}
|
||||
|
||||
// full_query is the query + inline INSERT data.
|
||||
// Try to include the trailing comment with test hints. It is just
|
||||
// a guess for now, because we don't yet know where the query ends
|
||||
// if it is an INSERT query with inline data. We will do it again
|
||||
// after we have processed the query. But even this guess is
|
||||
// beneficial so that we see proper trailing comments in "echo" and
|
||||
// server log.
|
||||
adjustQueryEnd(this_query_end, all_queries_end,
|
||||
context.getSettingsRef().max_parser_depth);
|
||||
|
||||
// full_query is the query + inline INSERT data + trailing comments
|
||||
// (the latter is our best guess for now).
|
||||
full_query = all_queries_text.substr(
|
||||
this_query_begin - all_queries_text.data(),
|
||||
this_query_end - this_query_begin);
|
||||
|
||||
// Look for the hint in the text of query + insert data, if any.
|
||||
// e.g. insert into t format CSV 'a' -- { serverError 123 }.
|
||||
TestHint test_hint(test_mode, full_query);
|
||||
expected_client_error = test_hint.clientError();
|
||||
expected_server_error = test_hint.serverError();
|
||||
// fmt::print(stderr, "parsed query '{}', left '{}'\n",
|
||||
// std::string_view(this_query_begin,
|
||||
// this_query_end - this_query_begin),
|
||||
// std::string_view(this_query_end,
|
||||
// all_queries_end - this_query_end));
|
||||
//
|
||||
// fmt::print(stderr, "query_to_send '{}', full_query '{}'\n",
|
||||
// query_to_send, full_query);
|
||||
|
||||
if (query_fuzzer_runs)
|
||||
{
|
||||
if (!processWithFuzzing(full_query))
|
||||
return false;
|
||||
|
||||
this_query_begin = this_query_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
processParsedSingleQuery();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Surprisingly, this is a client error. A server error would
|
||||
// have been reported w/o throwing (see onReceiveSeverException()).
|
||||
client_exception = std::make_unique<Exception>(
|
||||
getCurrentExceptionMessage(true), getCurrentExceptionCode());
|
||||
have_error = true;
|
||||
}
|
||||
|
||||
// For INSERTs with inline data: use the end of inline data as
|
||||
// reported by the format parser (it is saved in sendData()).
|
||||
// This allows us to handle queries like:
|
||||
// insert into t values (1); select 1
|
||||
// , where the inline data is delimited by semicolon and not by a
|
||||
// newline.
|
||||
if (insert_ast && insert_ast->data)
|
||||
{
|
||||
this_query_end = insert_ast->end;
|
||||
adjustQueryEnd(this_query_end, all_queries_end,
|
||||
context.getSettingsRef().max_parser_depth);
|
||||
}
|
||||
|
||||
// Now we know for sure where the query ends.
|
||||
// Look for the hint in the text of query + insert data + trailing
|
||||
// comments,
|
||||
// e.g. insert into t format CSV 'a' -- { serverError 123 }.
|
||||
// Use the updated query boundaries we just calculated.
|
||||
TestHint test_hint(test_mode, std::string(this_query_begin,
|
||||
this_query_end - this_query_begin));
|
||||
|
||||
// Check whether the error (or its absence) matches the test hints
|
||||
// (or their absence).
|
||||
bool error_matches_hint = true;
|
||||
if (have_error)
|
||||
{
|
||||
if (test_hint.serverError())
|
||||
{
|
||||
if (!server_exception)
|
||||
{
|
||||
error_matches_hint = false;
|
||||
fmt::print(stderr,
|
||||
"Expected server error code '{}' but got no server error.\n",
|
||||
test_hint.serverError());
|
||||
}
|
||||
else if (server_exception->code() != test_hint.serverError())
|
||||
{
|
||||
error_matches_hint = false;
|
||||
std::cerr << "Expected server error code: " <<
|
||||
test_hint.serverError() << " but got: " <<
|
||||
server_exception->code() << "." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (test_hint.clientError())
|
||||
{
|
||||
if (!client_exception)
|
||||
{
|
||||
error_matches_hint = false;
|
||||
fmt::print(stderr,
|
||||
"Expected client error code '{}' but got no client error.\n",
|
||||
test_hint.clientError());
|
||||
}
|
||||
else if (client_exception->code() != test_hint.clientError())
|
||||
{
|
||||
error_matches_hint = false;
|
||||
fmt::print(stderr,
|
||||
"Expected client error code '{}' but got '{}'.\n",
|
||||
test_hint.clientError(),
|
||||
client_exception->code());
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_hint.clientError() && !test_hint.serverError())
|
||||
{
|
||||
// No error was expected but it still ocurred. This is the
|
||||
// default case w/o test hint, doesn't need additional
|
||||
// diagnostics.
|
||||
error_matches_hint = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
if (test_hint.clientError())
|
||||
{
|
||||
processParsedSingleQuery();
|
||||
|
||||
if (insert_ast
|
||||
&& insert_ast->data
|
||||
&& insert_ast->end > this_query_end)
|
||||
{
|
||||
// For VALUES format: use the end of inline data as
|
||||
// reported by the format parser (it is saved in
|
||||
// sendData()). This allows us to handle queries like:
|
||||
// insert into t values (1); select 1
|
||||
//, where the inline data is delimited by semicolon and
|
||||
// not by a newline.
|
||||
// We must be careful not to move the query end backwards:
|
||||
// it may already be set (by parseQuery()) further than
|
||||
// the end of data, if there is a test hint after the
|
||||
// VALUES.
|
||||
this_query_end = insert_ast->end;
|
||||
// We also have to skip the trailing semicolon that might
|
||||
// be left after VALUES parsing.
|
||||
Tokens after_insert_tokens(this_query_end,
|
||||
all_queries_end);
|
||||
IParser::Pos after_insert_iterator(after_insert_tokens,
|
||||
context.getSettingsRef().max_parser_depth);
|
||||
while (after_insert_iterator.isValid()
|
||||
&& after_insert_iterator->type == TokenType::Semicolon)
|
||||
{
|
||||
this_query_end = after_insert_iterator->end;
|
||||
++after_insert_iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
last_exception_received_from_server = std::make_unique<Exception>(getCurrentExceptionMessage(true), getCurrentExceptionCode());
|
||||
actual_client_error = last_exception_received_from_server->code();
|
||||
if (!ignore_error && (!actual_client_error || actual_client_error != expected_client_error))
|
||||
std::cerr << "Error on processing query: " << full_query << std::endl << last_exception_received_from_server->message();
|
||||
received_exception_from_server = true;
|
||||
fmt::print(stderr,
|
||||
"The query succeeded but the client error '{}' was expected.\n",
|
||||
test_hint.clientError());
|
||||
error_matches_hint = false;
|
||||
}
|
||||
|
||||
if (!test_hint.checkActual(
|
||||
actual_server_error, actual_client_error, received_exception_from_server, last_exception_received_from_server))
|
||||
if (test_hint.serverError())
|
||||
{
|
||||
connection->forceConnected(connection_parameters.timeouts);
|
||||
}
|
||||
|
||||
if (received_exception_from_server && !ignore_error)
|
||||
{
|
||||
if (is_interactive)
|
||||
break;
|
||||
else
|
||||
return false;
|
||||
fmt::print(stderr,
|
||||
"The query succeeded but the server error '{}' was expected.\n",
|
||||
test_hint.serverError());
|
||||
error_matches_hint = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the error is expected, force reconnect and ignore it.
|
||||
if (have_error && error_matches_hint)
|
||||
{
|
||||
client_exception.reset();
|
||||
server_exception.reset();
|
||||
have_error = false;
|
||||
connection->forceConnected(connection_parameters.timeouts);
|
||||
}
|
||||
|
||||
// Report error.
|
||||
if (have_error)
|
||||
{
|
||||
// If we probably have progress bar, we should add additional
|
||||
// newline, otherwise exception may display concatenated with
|
||||
// the progress bar.
|
||||
if (need_render_progress)
|
||||
std::cerr << '\n';
|
||||
|
||||
if (server_exception)
|
||||
{
|
||||
std::string text = server_exception->displayText();
|
||||
auto embedded_stack_trace_pos = text.find("Stack trace");
|
||||
if (std::string::npos != embedded_stack_trace_pos
|
||||
&& !config().getBool("stacktrace", false))
|
||||
{
|
||||
text.resize(embedded_stack_trace_pos);
|
||||
}
|
||||
std::cerr << "Received exception from server (version "
|
||||
<< server_version << "):" << std::endl << "Code: "
|
||||
<< server_exception->code() << ". " << text << std::endl;
|
||||
}
|
||||
|
||||
if (client_exception)
|
||||
{
|
||||
fmt::print(stderr,
|
||||
"Error on processing query '{}':\n{}",
|
||||
full_query, client_exception->message());
|
||||
}
|
||||
}
|
||||
|
||||
// Stop processing queries if needed.
|
||||
if (have_error && !ignore_error)
|
||||
{
|
||||
if (is_interactive)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// fmt::print(stderr, "final query '{}', left '{}'\n",
|
||||
// std::string_view(this_query_begin,
|
||||
// this_query_end - this_query_begin),
|
||||
// std::string_view(this_query_end,
|
||||
// all_queries_end - this_query_end));
|
||||
|
||||
this_query_begin = this_query_end;
|
||||
}
|
||||
|
||||
@ -1156,15 +1337,20 @@ private:
|
||||
// Some functions (e.g. protocol parsers) don't throw, but
|
||||
// set last_exception instead, so we'll also do it here for
|
||||
// uniformity.
|
||||
last_exception_received_from_server = std::make_unique<Exception>(getCurrentExceptionMessage(true), getCurrentExceptionCode());
|
||||
received_exception_from_server = true;
|
||||
// Surprisingly, this is a client exception, because we get the
|
||||
// server exception w/o throwing (see onReceiveException()).
|
||||
client_exception = std::make_unique<Exception>(
|
||||
getCurrentExceptionMessage(true), getCurrentExceptionCode());
|
||||
have_error = true;
|
||||
}
|
||||
|
||||
if (received_exception_from_server)
|
||||
if (have_error)
|
||||
{
|
||||
const auto * exception = server_exception
|
||||
? server_exception.get() : client_exception.get();
|
||||
fmt::print(stderr, "Error on processing query '{}': {}\n",
|
||||
ast_to_process->formatForErrorMessage(),
|
||||
last_exception_received_from_server->message());
|
||||
exception->message());
|
||||
}
|
||||
|
||||
if (!connection->isConnected())
|
||||
@ -1177,13 +1363,14 @@ private:
|
||||
|
||||
// The server is still alive so we're going to continue fuzzing.
|
||||
// Determine what we're going to use as the starting AST.
|
||||
if (received_exception_from_server)
|
||||
if (have_error)
|
||||
{
|
||||
// Query completed with error, keep the previous starting AST.
|
||||
// Also discard the exception that we now know to be non-fatal,
|
||||
// so that it doesn't influence the exit code.
|
||||
last_exception_received_from_server.reset(nullptr);
|
||||
received_exception_from_server = false;
|
||||
server_exception.reset();
|
||||
client_exception.reset();
|
||||
have_error = false;
|
||||
}
|
||||
else if (ast_to_process->formatForErrorMessage().size() > 500)
|
||||
{
|
||||
@ -1237,8 +1424,9 @@ private:
|
||||
void processParsedSingleQuery()
|
||||
{
|
||||
resetOutput();
|
||||
last_exception_received_from_server.reset();
|
||||
received_exception_from_server = false;
|
||||
client_exception.reset();
|
||||
server_exception.reset();
|
||||
have_error = false;
|
||||
|
||||
if (echo_queries)
|
||||
{
|
||||
@ -1303,7 +1491,7 @@ private:
|
||||
}
|
||||
|
||||
/// Do not change context (current DB, settings) in case of an exception.
|
||||
if (!received_exception_from_server)
|
||||
if (!have_error)
|
||||
{
|
||||
if (const auto * set_query = parsed_query->as<ASTSetQuery>())
|
||||
{
|
||||
@ -1714,8 +1902,7 @@ private:
|
||||
return true;
|
||||
|
||||
case Protocol::Server::Exception:
|
||||
onReceiveExceptionFromServer(*packet.exception);
|
||||
last_exception_received_from_server = std::move(packet.exception);
|
||||
onReceiveExceptionFromServer(std::move(packet.exception));
|
||||
return false;
|
||||
|
||||
case Protocol::Server::Log:
|
||||
@ -1747,8 +1934,7 @@ private:
|
||||
return true;
|
||||
|
||||
case Protocol::Server::Exception:
|
||||
onReceiveExceptionFromServer(*packet.exception);
|
||||
last_exception_received_from_server = std::move(packet.exception);
|
||||
onReceiveExceptionFromServer(std::move(packet.exception));
|
||||
return false;
|
||||
|
||||
case Protocol::Server::Log:
|
||||
@ -1781,8 +1967,7 @@ private:
|
||||
return true;
|
||||
|
||||
case Protocol::Server::Exception:
|
||||
onReceiveExceptionFromServer(*packet.exception);
|
||||
last_exception_received_from_server = std::move(packet.exception);
|
||||
onReceiveExceptionFromServer(std::move(packet.exception));
|
||||
return false;
|
||||
|
||||
case Protocol::Server::Log:
|
||||
@ -2091,32 +2276,11 @@ private:
|
||||
}
|
||||
|
||||
|
||||
void onReceiveExceptionFromServer(const Exception & e)
|
||||
void onReceiveExceptionFromServer(std::unique_ptr<Exception> && e)
|
||||
{
|
||||
have_error = true;
|
||||
server_exception = std::move(e);
|
||||
resetOutput();
|
||||
received_exception_from_server = true;
|
||||
|
||||
actual_server_error = e.code();
|
||||
if (expected_server_error)
|
||||
{
|
||||
if (actual_server_error == expected_server_error)
|
||||
return;
|
||||
std::cerr << "Expected error code: " << expected_server_error << " but got: " << actual_server_error << "." << std::endl;
|
||||
}
|
||||
|
||||
std::string text = e.displayText();
|
||||
|
||||
auto embedded_stack_trace_pos = text.find("Stack trace");
|
||||
if (std::string::npos != embedded_stack_trace_pos && !config().getBool("stacktrace", false))
|
||||
text.resize(embedded_stack_trace_pos);
|
||||
|
||||
/// If we probably have progress bar, we should add additional newline,
|
||||
/// otherwise exception may display concatenated with the progress bar.
|
||||
if (need_render_progress)
|
||||
std::cerr << '\n';
|
||||
|
||||
std::cerr << "Received exception from server (version " << server_version << "):" << std::endl
|
||||
<< "Code: " << e.code() << ". " << text << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
@ -23,18 +23,26 @@ namespace ErrorCodes
|
||||
class TestHint
|
||||
{
|
||||
public:
|
||||
TestHint(bool enabled_, const String & query_)
|
||||
: enabled(enabled_)
|
||||
, query(query_)
|
||||
TestHint(bool enabled_, const String & query_) :
|
||||
query(query_)
|
||||
{
|
||||
if (!enabled_)
|
||||
return;
|
||||
|
||||
// Don't parse error hints in leading comments, because it feels weird.
|
||||
// Leading 'echo' hint is OK.
|
||||
bool is_leading_hint = true;
|
||||
|
||||
Lexer lexer(query.data(), query.data() + query.size());
|
||||
|
||||
for (Token token = lexer.nextToken(); !token.isEnd(); token = lexer.nextToken())
|
||||
{
|
||||
if (token.type == TokenType::Comment)
|
||||
if (token.type != TokenType::Comment
|
||||
&& token.type != TokenType::Whitespace)
|
||||
{
|
||||
is_leading_hint = false;
|
||||
}
|
||||
else if (token.type == TokenType::Comment)
|
||||
{
|
||||
String comment(token.begin, token.begin + token.size());
|
||||
|
||||
@ -47,7 +55,7 @@ public:
|
||||
if (pos_end != String::npos)
|
||||
{
|
||||
String hint(comment.begin() + pos_start + 1, comment.begin() + pos_end);
|
||||
parse(hint);
|
||||
parse(hint, is_leading_hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,46 +63,17 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns true if it's possible to continue without reconnect
|
||||
bool checkActual(int & actual_server_error, int & actual_client_error,
|
||||
bool & got_exception, std::unique_ptr<Exception> & last_exception) const
|
||||
{
|
||||
if (!enabled)
|
||||
return true;
|
||||
|
||||
if (allErrorsExpected(actual_server_error, actual_client_error))
|
||||
{
|
||||
got_exception = false;
|
||||
last_exception.reset();
|
||||
actual_server_error = 0;
|
||||
actual_client_error = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lostExpectedError(actual_server_error, actual_client_error))
|
||||
{
|
||||
std::cerr << "Success when error expected in query: " << query << "It expects server error "
|
||||
<< server_error << ", client error " << client_error << "." << std::endl;
|
||||
got_exception = true;
|
||||
last_exception = std::make_unique<Exception>("Success when error expected", ErrorCodes::UNEXPECTED_ERROR_CODE); /// return error to OS
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int serverError() const { return server_error; }
|
||||
int clientError() const { return client_error; }
|
||||
bool echoQueries() const { return echo; }
|
||||
|
||||
private:
|
||||
bool enabled = false;
|
||||
const String & query;
|
||||
int server_error = 0;
|
||||
int client_error = 0;
|
||||
bool echo = false;
|
||||
|
||||
void parse(const String & hint)
|
||||
void parse(const String & hint, bool is_leading_hint)
|
||||
{
|
||||
std::stringstream ss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
ss << hint;
|
||||
@ -106,11 +85,15 @@ private:
|
||||
if (ss.eof())
|
||||
break;
|
||||
|
||||
if (item == "serverError")
|
||||
ss >> server_error;
|
||||
else if (item == "clientError")
|
||||
ss >> client_error;
|
||||
else if (item == "echo")
|
||||
if (!is_leading_hint)
|
||||
{
|
||||
if (item == "serverError")
|
||||
ss >> server_error;
|
||||
else if (item == "clientError")
|
||||
ss >> client_error;
|
||||
}
|
||||
|
||||
if (item == "echo")
|
||||
echo = true;
|
||||
}
|
||||
}
|
||||
|
@ -258,63 +258,12 @@ ASTPtr tryParseQuery(
|
||||
ASTPtr res;
|
||||
const bool parse_res = parser.parse(token_iterator, res, expected);
|
||||
const auto last_token = token_iterator.max();
|
||||
_out_query_end = last_token.end;
|
||||
|
||||
ASTInsertQuery * insert = nullptr;
|
||||
if (parse_res)
|
||||
insert = res->as<ASTInsertQuery>();
|
||||
|
||||
// We have to do some extra work to determine where the next query begins,
|
||||
// preserving its leading comments.
|
||||
// The query we just parsed may contain a test hint comment in the same line,
|
||||
// e.g. select nonexistent_column; -- { serverError 12345 }.
|
||||
// We must add this comment to the query text, so that it is handled by the
|
||||
// test hint parser. It is important to do this before handling syntax errors,
|
||||
// so that we can expect for syntax client errors in test hints.
|
||||
// The token iterator skips comments and whitespace, so we have to find the
|
||||
// newline in the string manually. If it's earlier than the next significant
|
||||
// token, it means that the text before newline is some trailing whitespace
|
||||
// or comment, and we should add it to our query.
|
||||
const auto * newline = find_first_symbols<'\n'>(last_token.end, all_queries_end);
|
||||
|
||||
Tokens next_query_tokens(last_token.end, all_queries_end, max_query_size);
|
||||
IParser::Pos next_query_iterator(next_query_tokens, max_parser_depth);
|
||||
|
||||
while (next_query_iterator.isValid()
|
||||
&& next_query_iterator->type == TokenType::Semicolon)
|
||||
{
|
||||
++next_query_iterator;
|
||||
}
|
||||
const char * next_query_begin = next_query_iterator->begin;
|
||||
|
||||
// 1) We must always include the entire line in case of parse errors, because
|
||||
// we don't know where the query ends. OTOH, we can't always include it,
|
||||
// because there might be several valid queries on one line.
|
||||
// 2) We must include the entire line for INSERT queries with inline data.
|
||||
// We don't know where the data ends, but there might be a test hint on the
|
||||
// same line in case of values format.
|
||||
// 3) We include the entire line if the next query starts after it. This is
|
||||
// a generic case of trailing in-line comment.
|
||||
if (!parse_res
|
||||
|| (insert && insert->data)
|
||||
|| newline < next_query_begin)
|
||||
{
|
||||
assert(newline >= last_token.end);
|
||||
_out_query_end = newline;
|
||||
}
|
||||
else
|
||||
{
|
||||
_out_query_end = last_token.end;
|
||||
}
|
||||
|
||||
// Another corner case -- test hint comment that is ended by the end of data.
|
||||
// Reproduced by test cases without newline at end, your editor may be
|
||||
// configured to add one on save.
|
||||
if (!next_query_iterator.isValid())
|
||||
{
|
||||
assert(next_query_iterator->end == all_queries_end);
|
||||
_out_query_end = all_queries_end;
|
||||
}
|
||||
|
||||
// If parsed query ends at data for insertion. Data for insertion could be
|
||||
// in any format and not necessary be lexical correct, so we can't perform
|
||||
// most of the checks.
|
||||
@ -331,7 +280,6 @@ ASTPtr tryParseQuery(
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// More granular checks for queries other than INSERT w/inline data.
|
||||
/// Lexical error
|
||||
if (last_token.isError())
|
||||
|
@ -9,7 +9,7 @@ namespace DB
|
||||
/// Parse query or set 'out_error_message'.
|
||||
ASTPtr tryParseQuery(
|
||||
IParser & parser,
|
||||
const char * & pos, /// Moved to end of parsed fragment.
|
||||
const char * & _out_query_end, // query start as input parameter, query end as output
|
||||
const char * end,
|
||||
std::string & out_error_message,
|
||||
bool hilite,
|
||||
|
@ -21,3 +21,5 @@ insert into values_01564 values (11); -- { serverError 469 }
|
||||
select nonexistent column; -- { serverError 47 }
|
||||
-- query after values on the same line
|
||||
insert into values_01564 values (1); select 1;
|
||||
select 1;
|
||||
1
|
||||
|
@ -33,8 +33,14 @@ select nonexistent column; -- { serverError 47 }
|
||||
-- query after values on the same line
|
||||
insert into values_01564 values (1); select 1;
|
||||
|
||||
-- even this works (not sure why we need it lol)
|
||||
-- insert into values_01564 values (11) /*{ serverError 469 }*/; select 1;
|
||||
|
||||
-- syntax error, where the last token we can parse is long before the semicolon.
|
||||
select this is too many words for an alias; -- { clientError 62 }
|
||||
--OPTIMIZE TABLE values_01564 DEDUPLICATE BY; -- { clientError 62 }
|
||||
--OPTIMIZE TABLE values_01564 DEDUPLICATE BY a EXCEPT a; -- { clientError 62 }
|
||||
--select 'a' || distinct one || 'c' from system.one; -- { clientError 62 }
|
||||
OPTIMIZE TABLE values_01564 DEDUPLICATE BY; -- { clientError 62 }
|
||||
OPTIMIZE TABLE values_01564 DEDUPLICATE BY a EXCEPT a; -- { clientError 62 }
|
||||
select 'a' || distinct one || 'c' from system.one; -- { clientError 62 }
|
||||
|
||||
-- the return code must be zero after the final query has failed with expected error
|
||||
insert into values_01564 values (11); -- { serverError 469 }
|
||||
|
@ -1,8 +1,6 @@
|
||||
-- { echo }
|
||||
|
||||
set allow_experimental_window_functions = 1;
|
||||
|
||||
|
||||
-- just something basic
|
||||
select number, count() over (partition by intDiv(number, 3) order by number) from numbers(10);
|
||||
0 1
|
||||
@ -15,8 +13,6 @@ select number, count() over (partition by intDiv(number, 3) order by number) fro
|
||||
7 2
|
||||
8 3
|
||||
9 1
|
||||
|
||||
|
||||
-- proper calculation across blocks
|
||||
select number, max(number) over (partition by intDiv(number, 3) order by number desc) from numbers(10) settings max_block_size = 2;
|
||||
2 2
|
||||
@ -29,12 +25,8 @@ select number, max(number) over (partition by intDiv(number, 3) order by number
|
||||
7 8
|
||||
6 8
|
||||
9 9
|
||||
|
||||
|
||||
-- not a window function
|
||||
select number, abs(number) over (partition by toString(intDiv(number, 3))) from numbers(10); -- { serverError 63 }
|
||||
|
||||
|
||||
-- no partition by
|
||||
select number, avg(number) over (order by number) from numbers(10);
|
||||
0 0
|
||||
@ -47,8 +39,6 @@ select number, avg(number) over (order by number) from numbers(10);
|
||||
7 3.5
|
||||
8 4
|
||||
9 4.5
|
||||
|
||||
|
||||
-- no order by
|
||||
select number, quantileExact(number) over (partition by intDiv(number, 3)) from numbers(10);
|
||||
0 0
|
||||
@ -61,8 +51,6 @@ select number, quantileExact(number) over (partition by intDiv(number, 3)) from
|
||||
7 7
|
||||
8 7
|
||||
9 9
|
||||
|
||||
|
||||
-- can add an alias after window spec
|
||||
select number, quantileExact(number) over (partition by intDiv(number, 3)) q from numbers(10);
|
||||
0 0
|
||||
@ -75,20 +63,14 @@ select number, quantileExact(number) over (partition by intDiv(number, 3)) q fro
|
||||
7 7
|
||||
8 7
|
||||
9 9
|
||||
|
||||
|
||||
-- can't reference it yet -- the window functions are calculated at the
|
||||
-- last stage of select, after all other functions.
|
||||
select q * 10, quantileExact(number) over (partition by intDiv(number, 3)) q from numbers(10); -- { serverError 47 }
|
||||
|
||||
|
||||
-- must work in WHERE if you wrap it in a subquery
|
||||
select * from (select count(*) over () c from numbers(3)) where c > 0;
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
|
||||
-- should work in ORDER BY
|
||||
select number, max(number) over (partition by intDiv(number, 3) order by number desc) m from numbers(10) order by m desc, number;
|
||||
9 9
|
||||
@ -101,15 +83,11 @@ select number, max(number) over (partition by intDiv(number, 3) order by number
|
||||
0 2
|
||||
1 2
|
||||
2 2
|
||||
|
||||
|
||||
-- also works in ORDER BY if you wrap it in a subquery
|
||||
select * from (select count(*) over () c from numbers(3)) order by c;
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
|
||||
-- Example with window function only in ORDER BY. Here we make a rank of all
|
||||
-- numbers sorted descending, and then sort by this rank descending, and must get
|
||||
-- the ascending order.
|
||||
@ -119,8 +97,6 @@ select * from (select * from numbers(5) order by rand()) order by count() over (
|
||||
2
|
||||
3
|
||||
4
|
||||
|
||||
|
||||
-- Aggregate functions as window function arguments. This query is semantically
|
||||
-- the same as the above one, only we replace `number` with
|
||||
-- `any(number) group by number` and so on.
|
||||
@ -130,18 +106,13 @@ select * from (select * from numbers(5) order by rand()) group by number order b
|
||||
2
|
||||
3
|
||||
4
|
||||
|
||||
-- some more simple cases w/aggregate functions
|
||||
select sum(any(number)) over () from numbers(1);
|
||||
0
|
||||
|
||||
select sum(any(number) + 1) over () from numbers(1);
|
||||
1
|
||||
|
||||
select sum(any(number + 1)) over () from numbers(1);
|
||||
1
|
||||
|
||||
|
||||
-- different windows
|
||||
-- an explain test would also be helpful, but it's too immature now and I don't
|
||||
-- want to change reference all the time
|
||||
@ -177,8 +148,6 @@ select number, max(number) over (partition by intDiv(number, 3) order by number
|
||||
28 29 4
|
||||
29 29 5
|
||||
30 30 1
|
||||
|
||||
|
||||
-- two functions over the same window
|
||||
-- an explain test would also be helpful, but it's too immature now and I don't
|
||||
-- want to change reference all the time
|
||||
@ -190,34 +159,23 @@ select number, max(number) over (partition by intDiv(number, 3) order by number
|
||||
4 5 2
|
||||
5 5 1
|
||||
6 6 1
|
||||
|
||||
|
||||
-- check that we can work with constant columns
|
||||
select median(x) over (partition by x) from (select 1 x);
|
||||
1
|
||||
|
||||
|
||||
-- an empty window definition is valid as well
|
||||
select groupArray(number) over () from numbers(3);
|
||||
[0]
|
||||
[0,1]
|
||||
[0,1,2]
|
||||
|
||||
|
||||
-- This one tests we properly process the window function arguments.
|
||||
-- Seen errors like 'column `1` not found' from count(1).
|
||||
select count(1) over (), max(number + 1) over () from numbers(3);
|
||||
1 3
|
||||
|
||||
|
||||
-- Should work in DISTINCT
|
||||
select distinct sum(0) over () from numbers(2);
|
||||
0
|
||||
|
||||
select distinct any(number) over () from numbers(2);
|
||||
0
|
||||
|
||||
|
||||
-- Various kinds of aliases are properly substituted into various parts of window
|
||||
-- function definition.
|
||||
with number + 1 as x select intDiv(number, 3) as y, sum(x + y) over (partition by y order by x) from numbers(7);
|
||||
@ -228,13 +186,9 @@ with number + 1 as x select intDiv(number, 3) as y, sum(x + y) over (partition b
|
||||
1 11
|
||||
1 18
|
||||
2 9
|
||||
|
||||
|
||||
-- WINDOW clause
|
||||
select 1 window w1 as ();
|
||||
1
|
||||
|
||||
|
||||
select sum(number) over w1, sum(number) over w2
|
||||
from numbers(10)
|
||||
window
|
||||
@ -251,8 +205,6 @@ window
|
||||
28 13
|
||||
36 21
|
||||
45 9
|
||||
|
||||
|
||||
select
|
||||
sum(number) over w1,
|
||||
sum(number) over (partition by intDiv(number, 3))
|
||||
|
Loading…
Reference in New Issue
Block a user