From 765aa4d4e30fe0759103884cf3b6d3ffa7dda880 Mon Sep 17 00:00:00 2001 From: Alexander Kuzmenkov Date: Fri, 15 Jan 2021 23:30:44 +0300 Subject: [PATCH] rewrote everything again --- programs/client/Client.cpp | 428 ++++++++++++------ programs/client/TestHint.h | 63 +-- src/Parsers/parseQuery.cpp | 54 +-- src/Parsers/parseQuery.h | 2 +- .../01564_test_hint_woes.reference | 2 + .../0_stateless/01564_test_hint_woes.sql | 12 +- .../01591_window_functions.reference | 48 -- 7 files changed, 332 insertions(+), 277 deletions(-) diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 4384364d25a..672944a2d8b 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -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 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 server_exception; + /// Likewise, the last exception that occurred on the client. + std::unique_ptr 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(); 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( + 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(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(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( + 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()) { @@ -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 && 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; } diff --git a/programs/client/TestHint.h b/programs/client/TestHint.h index f1998588261..4d7c9b72a21 100644 --- a/programs/client/TestHint.h +++ b/programs/client/TestHint.h @@ -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 & 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("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; } } diff --git a/src/Parsers/parseQuery.cpp b/src/Parsers/parseQuery.cpp index ccd4832d35e..48a92534e74 100644 --- a/src/Parsers/parseQuery.cpp +++ b/src/Parsers/parseQuery.cpp @@ -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(); - // 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()) diff --git a/src/Parsers/parseQuery.h b/src/Parsers/parseQuery.h index 14a9a85b22c..e6a6b26a462 100644 --- a/src/Parsers/parseQuery.h +++ b/src/Parsers/parseQuery.h @@ -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, diff --git a/tests/queries/0_stateless/01564_test_hint_woes.reference b/tests/queries/0_stateless/01564_test_hint_woes.reference index fbc0227b500..41c8bb32d0c 100644 --- a/tests/queries/0_stateless/01564_test_hint_woes.reference +++ b/tests/queries/0_stateless/01564_test_hint_woes.reference @@ -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 diff --git a/tests/queries/0_stateless/01564_test_hint_woes.sql b/tests/queries/0_stateless/01564_test_hint_woes.sql index 8e33d940dc6..be41a8dffaf 100644 --- a/tests/queries/0_stateless/01564_test_hint_woes.sql +++ b/tests/queries/0_stateless/01564_test_hint_woes.sql @@ -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 } diff --git a/tests/queries/0_stateless/01591_window_functions.reference b/tests/queries/0_stateless/01591_window_functions.reference index d51555ee872..45cb4ac3994 100644 --- a/tests/queries/0_stateless/01591_window_functions.reference +++ b/tests/queries/0_stateless/01591_window_functions.reference @@ -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))