#include "sqllogic_parser.hpp" #include "catch.hpp" #include namespace duckdb { bool SQLLogicParser::OpenFile(const string &path) { this->file_name = path; std::ifstream infile(file_name); if (infile.bad() || infile.fail()) { return false; } string line; while (std::getline(infile, line)) { lines.push_back(StringUtil::Replace(line, "\r", "")); } return !infile.bad(); } bool SQLLogicParser::EmptyOrComment(const string &line) { return line.empty() || StringUtil::StartsWith(line, "#"); } bool SQLLogicParser::NextLineEmptyOrComment() { if (current_line + 1 >= lines.size()) { return true; } else { return EmptyOrComment(lines[current_line + 1]); } } bool SQLLogicParser::NextStatement() { if (seen_statement) { // skip the current statement // but only if we have already seen a statement in the file while (current_line < lines.size() && !EmptyOrComment(lines[current_line])) { current_line++; } } seen_statement = true; // now look for the first non-empty line while (current_line < lines.size() && EmptyOrComment(lines[current_line])) { current_line++; } // return whether or not we reached the end of the file return current_line < lines.size(); } void SQLLogicParser::NextLine() { current_line++; } string SQLLogicParser::ExtractStatement() { string statement; bool first_line = true; while (current_line < lines.size() && !EmptyOrComment(lines[current_line])) { if (lines[current_line] == "----") { break; } if (!first_line) { statement += "\n"; } statement += lines[current_line]; first_line = false; current_line++; } return statement; } vector SQLLogicParser::ExtractExpectedResult() { vector result; // skip the result line (----) if we are still reading that if (current_line < lines.size() && lines[current_line] == "----") { current_line++; } // read the expected result until we encounter a new line while (current_line < lines.size() && !lines[current_line].empty()) { result.push_back(lines[current_line]); current_line++; } return result; } string SQLLogicParser::ExtractExpectedError(bool expect_ok, bool original_sqlite_test) { // check if there is an expected error at all if (current_line >= lines.size() || lines[current_line] != "----") { if (!expect_ok && !original_sqlite_test) { Fail("Failed to parse statement: statement error needs to have an expected error message"); } return string(); } if (expect_ok) { Fail("Failed to parse statement: only statement error can have an expected error message, not statement ok"); } current_line++; string error; vector error_lines; while (current_line < lines.size() && !lines[current_line].empty()) { error_lines.push_back(lines[current_line]); current_line++; } error = StringUtil::Join(error_lines, "\n"); return error; } void SQLLogicParser::FailRecursive(const string &msg, vector &values) { auto error_message = file_name + ":" + to_string(current_line + 1) + ": " + ExceptionFormatValue::Format(msg, values); FAIL(error_message.c_str()); } SQLLogicToken SQLLogicParser::Tokenize() { SQLLogicToken result; if (current_line >= lines.size()) { result.type = SQLLogicTokenType::SQLLOGIC_INVALID; return result; } vector argument_list; auto &line = lines[current_line]; idx_t last_pos = 0; for (idx_t i = 0; i < line.size(); i++) { if (StringUtil::CharacterIsSpace(line[i])) { if (i == last_pos) { last_pos++; } else { argument_list.push_back(line.substr(last_pos, i - last_pos)); last_pos = i + 1; } } } if (last_pos != line.size()) { argument_list.push_back(line.substr(last_pos, line.size() - last_pos)); } if (argument_list.empty()) { Fail("Empty line!?"); } result.type = CommandToToken(argument_list[0]); for (idx_t i = 1; i < argument_list.size(); i++) { result.parameters.push_back(std::move(argument_list[i])); } return result; } // Single line statements should throw a parser error if the next line is not a comment or a newline bool SQLLogicParser::IsSingleLineStatement(SQLLogicToken &token) { switch (token.type) { case SQLLogicTokenType::SQLLOGIC_HASH_THRESHOLD: case SQLLogicTokenType::SQLLOGIC_HALT: case SQLLogicTokenType::SQLLOGIC_MODE: case SQLLogicTokenType::SQLLOGIC_SET: case SQLLogicTokenType::SQLLOGIC_RESET: case SQLLogicTokenType::SQLLOGIC_LOOP: case SQLLogicTokenType::SQLLOGIC_FOREACH: case SQLLogicTokenType::SQLLOGIC_CONCURRENT_LOOP: case SQLLogicTokenType::SQLLOGIC_CONCURRENT_FOREACH: case SQLLogicTokenType::SQLLOGIC_ENDLOOP: case SQLLogicTokenType::SQLLOGIC_REQUIRE: case SQLLogicTokenType::SQLLOGIC_REQUIRE_ENV: case SQLLogicTokenType::SQLLOGIC_TEST_ENV: case SQLLogicTokenType::SQLLOGIC_LOAD: case SQLLogicTokenType::SQLLOGIC_RESTART: case SQLLogicTokenType::SQLLOGIC_RECONNECT: case SQLLogicTokenType::SQLLOGIC_SLEEP: case SQLLogicTokenType::SQLLOGIC_UNZIP: case SQLLogicTokenType::SQLLOGIC_TAGS: return true; case SQLLogicTokenType::SQLLOGIC_SKIP_IF: case SQLLogicTokenType::SQLLOGIC_ONLY_IF: case SQLLogicTokenType::SQLLOGIC_INVALID: case SQLLogicTokenType::SQLLOGIC_STATEMENT: case SQLLogicTokenType::SQLLOGIC_QUERY: return false; default: throw std::runtime_error("Unknown SQLLogic token found!"); } } // (All) Context statements must precede all non-header statements bool SQLLogicParser::IsTestCommand(SQLLogicTokenType &type) { switch (type) { case SQLLogicTokenType::SQLLOGIC_QUERY: case SQLLogicTokenType::SQLLOGIC_STATEMENT: return true; case SQLLogicTokenType::SQLLOGIC_CONCURRENT_FOREACH: case SQLLogicTokenType::SQLLOGIC_CONCURRENT_LOOP: case SQLLogicTokenType::SQLLOGIC_ENDLOOP: case SQLLogicTokenType::SQLLOGIC_FOREACH: case SQLLogicTokenType::SQLLOGIC_HALT: case SQLLogicTokenType::SQLLOGIC_HASH_THRESHOLD: case SQLLogicTokenType::SQLLOGIC_INVALID: case SQLLogicTokenType::SQLLOGIC_LOAD: case SQLLogicTokenType::SQLLOGIC_LOOP: case SQLLogicTokenType::SQLLOGIC_MODE: case SQLLogicTokenType::SQLLOGIC_ONLY_IF: case SQLLogicTokenType::SQLLOGIC_RECONNECT: case SQLLogicTokenType::SQLLOGIC_REQUIRE: case SQLLogicTokenType::SQLLOGIC_REQUIRE_ENV: case SQLLogicTokenType::SQLLOGIC_RESET: case SQLLogicTokenType::SQLLOGIC_RESTART: case SQLLogicTokenType::SQLLOGIC_SET: case SQLLogicTokenType::SQLLOGIC_SKIP_IF: case SQLLogicTokenType::SQLLOGIC_SLEEP: case SQLLogicTokenType::SQLLOGIC_TAGS: case SQLLogicTokenType::SQLLOGIC_TEST_ENV: case SQLLogicTokenType::SQLLOGIC_UNZIP: return false; default: throw std::runtime_error("Unknown SQLLogic token found!"); } } SQLLogicTokenType SQLLogicParser::CommandToToken(const string &token) { if (token == "skipif") { return SQLLogicTokenType::SQLLOGIC_SKIP_IF; } else if (token == "onlyif") { return SQLLogicTokenType::SQLLOGIC_ONLY_IF; } else if (token == "statement") { return SQLLogicTokenType::SQLLOGIC_STATEMENT; } else if (token == "query") { return SQLLogicTokenType::SQLLOGIC_QUERY; } else if (token == "hash-threshold") { return SQLLogicTokenType::SQLLOGIC_HASH_THRESHOLD; } else if (token == "halt") { return SQLLogicTokenType::SQLLOGIC_HALT; } else if (token == "mode") { return SQLLogicTokenType::SQLLOGIC_MODE; } else if (token == "set") { return SQLLogicTokenType::SQLLOGIC_SET; } else if (token == "reset") { return SQLLogicTokenType::SQLLOGIC_RESET; } else if (token == "loop") { return SQLLogicTokenType::SQLLOGIC_LOOP; } else if (token == "concurrentloop") { return SQLLogicTokenType::SQLLOGIC_CONCURRENT_LOOP; } else if (token == "foreach") { return SQLLogicTokenType::SQLLOGIC_FOREACH; } else if (token == "concurrentforeach") { return SQLLogicTokenType::SQLLOGIC_CONCURRENT_FOREACH; } else if (token == "endloop") { return SQLLogicTokenType::SQLLOGIC_ENDLOOP; } else if (token == "require") { return SQLLogicTokenType::SQLLOGIC_REQUIRE; } else if (token == "require-env") { return SQLLogicTokenType::SQLLOGIC_REQUIRE_ENV; } else if (token == "test-env") { return SQLLogicTokenType::SQLLOGIC_TEST_ENV; } else if (token == "load") { return SQLLogicTokenType::SQLLOGIC_LOAD; } else if (token == "restart") { return SQLLogicTokenType::SQLLOGIC_RESTART; } else if (token == "reconnect") { return SQLLogicTokenType::SQLLOGIC_RECONNECT; } else if (token == "sleep") { return SQLLogicTokenType::SQLLOGIC_SLEEP; } else if (token == "unzip") { return SQLLogicTokenType::SQLLOGIC_UNZIP; } else if (token == "tags") { return SQLLogicTokenType::SQLLOGIC_TAGS; } Fail("Unrecognized parameter %s", token); return SQLLogicTokenType::SQLLOGIC_INVALID; } } // namespace duckdb