diff --git a/builds/win32/msvc15/isql_static.vcxproj b/builds/win32/msvc15/isql_static.vcxproj index 2fe7388b96..647eff13af 100644 --- a/builds/win32/msvc15/isql_static.vcxproj +++ b/builds/win32/msvc15/isql_static.vcxproj @@ -24,6 +24,7 @@ + @@ -40,6 +41,7 @@ + diff --git a/builds/win32/msvc15/isql_static.vcxproj.filters b/builds/win32/msvc15/isql_static.vcxproj.filters index 5605a4551f..285226069d 100644 --- a/builds/win32/msvc15/isql_static.vcxproj.filters +++ b/builds/win32/msvc15/isql_static.vcxproj.filters @@ -30,6 +30,9 @@ ISQL files + + ISQL files + ISQL files @@ -73,6 +76,9 @@ Header files + + Header files + Header files diff --git a/builds/win32/msvc15/isql_test.vcxproj b/builds/win32/msvc15/isql_test.vcxproj index dabd1312a7..d8912dbe15 100644 --- a/builds/win32/msvc15/isql_test.vcxproj +++ b/builds/win32/msvc15/isql_test.vcxproj @@ -177,6 +177,7 @@ + diff --git a/builds/win32/msvc15/isql_test.vcxproj.filters b/builds/win32/msvc15/isql_test.vcxproj.filters index 331157ba2b..26868fd4e6 100644 --- a/builds/win32/msvc15/isql_test.vcxproj.filters +++ b/builds/win32/msvc15/isql_test.vcxproj.filters @@ -10,6 +10,9 @@ source + + source + source diff --git a/src/common/StdHelper.h b/src/common/StdHelper.h new file mode 100644 index 0000000000..c055a1257e --- /dev/null +++ b/src/common/StdHelper.h @@ -0,0 +1,42 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2024 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + */ + +#ifndef FB_COMMON_STD_HELPER_H +#define FB_COMMON_STD_HELPER_H + +namespace Firebird { + +// To be used with std::visit + +template +struct StdVisitOverloads : Ts... +{ + using Ts::operator()...; +}; + +template +StdVisitOverloads(Ts...) -> StdVisitOverloads; + +} // namespace Firebird + +#endif // FB_COMMON_STD_HELPER_H diff --git a/src/common/classes/MetaString.h b/src/common/classes/MetaString.h index 251aedd360..6c954d3c07 100644 --- a/src/common/classes/MetaString.h +++ b/src/common/classes/MetaString.h @@ -93,6 +93,30 @@ public: int compare(const AbstractString& s) const { return compare(s.c_str(), s.length()); } int compare(const MetaString& m) const { return memcmp(data, m.data, MAX_SQL_IDENTIFIER_SIZE); } + string toQuotedString() const + { + string s; + + if (hasData()) + { + s.reserve(count + 2); + + s.append("\""); + + for (const auto c : *this) + { + if (c == '"') + s.append("\""); + + s.append(&c, 1); + } + + s.append("\""); + } + + return s; + } + bool operator==(const char* s) const { return compare(s) == 0; } bool operator!=(const char* s) const { return compare(s) != 0; } bool operator==(const AbstractString& s) const { return compare(s) == 0; } diff --git a/src/isql/FrontendLexer.cpp b/src/isql/FrontendLexer.cpp index 5c5a63d911..2a3c3602a0 100644 --- a/src/isql/FrontendLexer.cpp +++ b/src/isql/FrontendLexer.cpp @@ -28,9 +28,7 @@ #include -static std::string trim(std::string_view str); - -static std::string trim(std::string_view str) +std::string FrontendLexer::trim(std::string_view str) { auto finish = str.end(); auto start = str.begin(); @@ -142,7 +140,7 @@ std::variant= term.length() && std::equal(term.begin(), term.end(), pos)) + if (std::size_t(end - pos) >= term.length() && std::equal(term.begin(), term.end(), pos)) { const auto initialStatement = std::string(buffer.cbegin(), pos); pos += term.length(); @@ -224,13 +222,58 @@ FrontendLexer::Token FrontendLexer::getToken() return token; } -std::optional FrontendLexer::getStringToken() +FrontendLexer::Token FrontendLexer::getNameToken() { + skipSpacesAndComments(); + Token token; + if (pos >= end) + { + token.type = Token::TYPE_EOF; + return token; + } + + if (const auto optStringToken = getStringToken(); optStringToken.has_value()) + return optStringToken.value(); + + const auto start = pos; + bool first = true; + + while (pos < end) + { + const auto c = *pos++; + + if (!((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + c == '{' || + c == '}' || + (!first && c >= '0' && c <= '9') || + (!first && c == '$') || + (!first && c == '_'))) + { + if (!first) + --pos; + + break; + } + + first = false; + } + + token.processedText = token.rawText = std::string(start, pos); + std::transform(token.processedText.begin(), token.processedText.end(), + token.processedText.begin(), toupper); + + return token; +} + +std::optional FrontendLexer::getStringToken() +{ if (pos >= end) return std::nullopt; + Token token; const auto start = pos; switch (toupper(*pos)) diff --git a/src/isql/FrontendLexer.h b/src/isql/FrontendLexer.h index d2ac1f88f2..1357da7a14 100644 --- a/src/isql/FrontendLexer.h +++ b/src/isql/FrontendLexer.h @@ -48,6 +48,12 @@ public: Type type = TYPE_OTHER; std::string rawText; std::string processedText; + + std::string getProcessedString() const + { + return type == FrontendLexer::Token::TYPE_STRING || type == FrontendLexer::Token::TYPE_META_STRING ? + processedText : rawText; + } }; struct SingleStatement @@ -74,6 +80,7 @@ public: FrontendLexer& operator=(const FrontendLexer&) = delete; public: + static std::string trim(std::string_view str); static std::string stripComments(std::string_view statement); public: @@ -87,6 +94,11 @@ public: return pos; } + void setPos(std::string::const_iterator newPos) + { + pos = newPos; + } + void rewind() { deletePos = buffer.begin(); @@ -97,7 +109,9 @@ public: void appendBuffer(std::string_view newBuffer); void reset(); std::variant getSingleStatement(std::string_view term); + Token getToken(); + Token getNameToken(); private: std::optional getStringToken(); diff --git a/src/isql/FrontendParser.cpp b/src/isql/FrontendParser.cpp new file mode 100644 index 0000000000..46735e7350 --- /dev/null +++ b/src/isql/FrontendParser.cpp @@ -0,0 +1,732 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2024 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + */ + +#include "firebird.h" +#include "../isql/FrontendParser.h" +#include + + +FrontendParser::AnyNode FrontendParser::internalParse() +{ + static constexpr std::string_view TOKEN_ADD("ADD"); + static constexpr std::string_view TOKEN_BLOBDUMP("BLOBDUMP"); + static constexpr std::string_view TOKEN_BLOBVIEW("BLOBVIEW"); + static constexpr std::string_view TOKEN_CONNECT("CONNECT"); + static constexpr std::string_view TOKEN_COPY("COPY"); + static constexpr std::string_view TOKEN_CREATE("CREATE"); + static constexpr std::string_view TOKEN_DROP("DROP"); + static constexpr std::string_view TOKEN_EDIT("EDIT"); + static constexpr std::string_view TOKEN_EXIT("EXIT"); + static constexpr std::string_view TOKEN_EXPLAIN("EXPLAIN"); + static constexpr std::string_view TOKEN_HELP("HELP"); + static constexpr std::string_view TOKEN_INPUT("INPUT"); + static constexpr std::string_view TOKEN_OUTPUT("OUTPUT"); + static constexpr std::string_view TOKEN_QUIT("QUIT"); + static constexpr std::string_view TOKEN_SET("SET"); + static constexpr std::string_view TOKEN_SHELL("SHELL"); + static constexpr std::string_view TOKEN_SHOW("SHOW"); + + const auto commandToken = lexer.getToken(); + + if (commandToken.type == Token::TYPE_OTHER) + { + const auto& command = commandToken.processedText; + + if (command == TOKEN_ADD) + { + if (const auto tableName = parseName()) + { + AddNode node; + node.tableName = std::move(tableName.value()); + + if (parseEof()) + return node; + } + } + else if (command == TOKEN_BLOBDUMP || command == TOKEN_BLOBVIEW) + { + if (const auto blobId = lexer.getToken(); blobId.type != Token::TYPE_EOF) + { + BlobDumpViewNode node; + + // Find the high and low values of the blob id + if (blobId.processedText.empty()) + return InvalidNode(); + + sscanf(blobId.processedText.c_str(), "%" xLONGFORMAT":%" xLONGFORMAT, + &node.blobId.gds_quad_high, &node.blobId.gds_quad_low); + + if (command == TOKEN_BLOBDUMP) + { + if (const auto file = parseFileName()) + { + node.file = std::move(file.value()); + + if (parseEof()) + return node; + } + } + else + { + if (parseEof()) + return node; + } + } + } + else if (command == TOKEN_CONNECT) + { + ConnectNode node; + + do + { + const auto token = lexer.getToken(); + + if (token.type == Token::TYPE_EOF) + { + if (node.args.empty()) + break; + else + return node; + } + else if (token.type != Token::TYPE_OTHER && + token.type != Token::TYPE_STRING && + token.type != Token::TYPE_META_STRING) + { + return InvalidNode(); + } + + node.args.push_back(std::move(token)); + } while(true); + } + else if (command == TOKEN_COPY) + { + CopyNode node; + + if (const auto source = parseName()) + node.source = std::move(source.value()); + else + return InvalidNode(); + + if (const auto destination = parseName()) + node.destination = std::move(destination.value()); + else + return InvalidNode(); + + if (const auto database = parseFileName()) + node.database = std::move(database.value()); + else + return InvalidNode(); + + if (parseEof()) + return node; + } + else if (command == TOKEN_CREATE) + { + if (const auto createWhat = lexer.getToken(); + createWhat.type == Token::TYPE_OTHER && + (createWhat.processedText == "DATABASE" || + (options.schemaAsDatabase && createWhat.processedText == "SCHEMA"))) + { + CreateDatabaseNode node; + + do + { + const auto token = lexer.getToken(); + + if (token.type == Token::TYPE_EOF) + { + if (node.args.empty()) + break; + else + return node; + } + else if (token.type != Token::TYPE_OTHER && + token.type != Token::TYPE_STRING && + token.type != Token::TYPE_META_STRING) + { + return InvalidNode(); + } + + node.args.push_back(std::move(token)); + } while(true); + } + } + else if (command == TOKEN_DROP) + { + if (const auto dropWhat = lexer.getToken(); + dropWhat.type == Token::TYPE_OTHER && + (dropWhat.processedText == "DATABASE" || + (options.schemaAsDatabase && dropWhat.processedText == "SCHEMA"))) + { + if (parseEof()) + return DropDatabaseNode(); + } + } + else if (command == TOKEN_EDIT) + { + EditNode node; + node.file = parseFileName(); + + if (parseEof()) + return node; + } + else if (command == TOKEN_EXIT) + { + if (parseEof()) + return ExitNode(); + } + else if (command == TOKEN_EXPLAIN) + { + ExplainNode node; + + if (const auto query = parseUtilEof()) + { + node.query = std::move(query.value()); + return node; + } + } + else if (command == TOKEN_HELP || command == "?") + { + HelpNode node; + + if (const auto token = lexer.getToken(); token.type == Token::TYPE_EOF) + return node; + else if (token.type == Token::TYPE_OTHER) + { + node.command = token.processedText; + + if (parseEof()) + return node; + } + } + else if (command.length() >= 2 && TOKEN_INPUT.find(command) == 0) + { + if (const auto file = parseFileName()) + { + InputNode node; + node.file = std::move(file.value()); + + if (parseEof()) + return node; + } + } + else if (command.length() >= 3 && TOKEN_OUTPUT.find(command) == 0) + { + OutputNode node; + node.file = parseFileName(); + + if (parseEof()) + return node; + } + else if (command == TOKEN_QUIT) + { + if (parseEof()) + return QuitNode(); + } + else if (command == TOKEN_SET) + { + if (const auto setNode = parseSet(); !std::holds_alternative(setNode)) + return setNode; + } + else if (command == TOKEN_SHELL) + { + ShellNode node; + node.command = parseUtilEof(); + return node; + } + else if (command == TOKEN_SHOW) + { + if (const auto showNode = parseShow(); !std::holds_alternative(showNode)) + return showNode; + } + } + + return InvalidNode(); +} + +FrontendParser::AnySetNode FrontendParser::parseSet() +{ + static constexpr std::string_view TOKEN_AUTODDL("AUTODDL"); + static constexpr std::string_view TOKEN_AUTOTERM("AUTOTERM"); + static constexpr std::string_view TOKEN_BAIL("BAIL"); + static constexpr std::string_view TOKEN_BLOBDISPLAY("BLOBDISPLAY"); + static constexpr std::string_view TOKEN_BULK_INSERT("BULK_INSERT"); + static constexpr std::string_view TOKEN_COUNT("COUNT"); + static constexpr std::string_view TOKEN_ECHO("ECHO"); + static constexpr std::string_view TOKEN_EXEC_PATH_DISPLAY("EXEC_PATH_DISPLAY"); + static constexpr std::string_view TOKEN_EXPLAIN("EXPLAIN"); + static constexpr std::string_view TOKEN_HEADING("HEADING"); + static constexpr std::string_view TOKEN_KEEP_TRAN_PARAMS("KEEP_TRAN_PARAMS"); + static constexpr std::string_view TOKEN_LIST("LIST"); + static constexpr std::string_view TOKEN_LOCAL_TIMEOUT("LOCAL_TIMEOUT"); + static constexpr std::string_view TOKEN_MAXROWS("MAXROWS"); + static constexpr std::string_view TOKEN_NAMES("NAMES"); + static constexpr std::string_view TOKEN_PER_TABLE_STATS("PER_TABLE_STATS"); + static constexpr std::string_view TOKEN_PLAN("PLAN"); + static constexpr std::string_view TOKEN_PLANONLY("PLANONLY"); + static constexpr std::string_view TOKEN_ROWCOUNT("ROWCOUNT"); + static constexpr std::string_view TOKEN_SQL("SQL"); + static constexpr std::string_view TOKEN_SQLDA_DISPLAY("SQLDA_DISPLAY"); + static constexpr std::string_view TOKEN_STATS("STATS"); + static constexpr std::string_view TOKEN_TERMINATOR("TERMINATOR"); + static constexpr std::string_view TOKEN_TIME("TIME"); + static constexpr std::string_view TOKEN_TRANSACTION("TRANSACTION"); + static constexpr std::string_view TOKEN_WARNINGS("WARNINGS"); + static constexpr std::string_view TOKEN_WIDTH("WIDTH"); + static constexpr std::string_view TOKEN_WNG("WNG"); + static constexpr std::string_view TOKEN_WIRE_STATS("WIRE_STATS"); + + switch (const auto setCommandToken = lexer.getToken(); setCommandToken.type) + { + case Token::TYPE_EOF: + return SetNode(); + + case Token::TYPE_OTHER: + { + const auto& text = setCommandToken.processedText; + + if (const auto parsed = parseSet(text, TOKEN_AUTODDL, 4)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_AUTOTERM)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_BAIL)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_BLOBDISPLAY, 4)) + return parsed.value(); + else if (text == TOKEN_BULK_INSERT) + { + SetBulkInsertNode node; + + if (const auto statement = parseUtilEof()) + { + node.statement = statement.value(); + return node; + } + } + else if (const auto parsed = parseSet(text, TOKEN_COUNT)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_ECHO)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_EXEC_PATH_DISPLAY)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_EXPLAIN)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_HEADING)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_KEEP_TRAN_PARAMS, 9)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_LIST)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_LOCAL_TIMEOUT)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_MAXROWS)) + return parsed.value(); + else if (text == TOKEN_NAMES) + { + SetNamesNode node; + node.name = parseName(); + + if (parseEof()) + return node; + } + else if (const auto parsed = parseSet(text, TOKEN_PER_TABLE_STATS, 7)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_PLAN)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_PLANONLY)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_ROWCOUNT)) + return parsed.value(); + else if (text == TOKEN_SQL) + { + SetSqlDialectNode node; + + if (const auto dialectToken = lexer.getToken(); + dialectToken.type == Token::TYPE_OTHER && dialectToken.processedText == "DIALECT") + { + if (const auto arg = lexer.getToken(); arg.type != Token::TYPE_EOF) + { + node.arg = arg.processedText; + + if (parseEof()) + return node; + } + } + } + else if (const auto parsed = parseSet(text, TOKEN_SQLDA_DISPLAY)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_STATS, 4)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_TERMINATOR, 4, false)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_TIME)) + { + if (const auto setTimeNode = std::get_if(&parsed.value()); + setTimeNode && setTimeNode->arg == "ZONE") + { + return InvalidNode(); + } + + return parsed.value(); + } + else if (text.length() >= 5 && std::string(TOKEN_TRANSACTION).find(text) == 0) + { + SetTransactionNode node; + node.statement = lexer.getBuffer(); + return node; + } + else if (const auto parsed = parseSet(text, TOKEN_WARNINGS, 7)) + return parsed.value(); + else if (const auto parsed = parseSet(text, TOKEN_WNG)) + return parsed.value(); + else if (text == TOKEN_WIDTH) + { + SetWidthNode node; + + if (const auto column = lexer.getToken(); column.type != Token::TYPE_EOF) + { + node.column = column.processedText; + + if (const auto width = lexer.getToken(); width.type != Token::TYPE_EOF) + { + node.width = width.processedText; + + if (!parseEof()) + return InvalidNode(); + } + + return node; + } + } + else if (const auto parsed = parseSet(text, TOKEN_WIRE_STATS, 4)) + return parsed.value(); + + break; + } + } + + return InvalidNode(); +} + +template +std::optional FrontendParser::parseSet(std::string_view setCommand, + std::string_view testCommand, unsigned testCommandMinLen, bool useProcessedText) +{ + if (setCommand == testCommand || + (testCommandMinLen && setCommand.length() >= testCommandMinLen && + std::string(testCommand).find(setCommand) == 0)) + { + Node node; + + if (const auto arg = lexer.getToken(); arg.type != Token::TYPE_EOF) + { + node.arg = useProcessedText ? arg.processedText : arg.rawText; + + if (!parseEof()) + return InvalidNode(); + } + + return node; + } + + return std::nullopt; +} + +FrontendParser::AnyShowNode FrontendParser::parseShow() +{ + static constexpr std::string_view TOKEN_CHECKS("CHECKS"); + static constexpr std::string_view TOKEN_COLLATES("COLLATES"); + static constexpr std::string_view TOKEN_COLLATIONS("COLLATIONS"); + static constexpr std::string_view TOKEN_COMMENTS("COMMENTS"); + static constexpr std::string_view TOKEN_DATABASE("DATABASE"); + static constexpr std::string_view TOKEN_DEPENDENCIES("DEPENDENCIES"); + static constexpr std::string_view TOKEN_DEPENDENCY("DEPENDENCY"); + static constexpr std::string_view TOKEN_DOMAINS("DOMAINS"); + static constexpr std::string_view TOKEN_EXCEPTIONS("EXCEPTIONS"); + static constexpr std::string_view TOKEN_FILTERS("FILTERS"); + static constexpr std::string_view TOKEN_FUNCTIONS("FUNCTIONS"); + static constexpr std::string_view TOKEN_INDEXES("INDEXES"); + static constexpr std::string_view TOKEN_INDICES("INDICES"); + static constexpr std::string_view TOKEN_GENERATORS("GENERATORS"); + static constexpr std::string_view TOKEN_GRANTS("GRANTS"); + static constexpr std::string_view TOKEN_MAPPINGS("MAPPINGS"); + static constexpr std::string_view TOKEN_PACKAGES("PACKAGES"); + static constexpr std::string_view TOKEN_PROCEDURES("PROCEDURES"); + static constexpr std::string_view TOKEN_PUBLICATIONS("PUBLICATIONS"); + static constexpr std::string_view TOKEN_ROLES("ROLES"); + static constexpr std::string_view TOKEN_SECCLASSES("SECCLASSES"); + static constexpr std::string_view TOKEN_SEQUENCES("SEQUENCES"); + static constexpr std::string_view TOKEN_SQL("SQL"); + static constexpr std::string_view TOKEN_SYSTEM("SYSTEM"); + static constexpr std::string_view TOKEN_TABLES("TABLES"); + static constexpr std::string_view TOKEN_TRIGGERS("TRIGGERS"); + static constexpr std::string_view TOKEN_USERS("USERS"); + static constexpr std::string_view TOKEN_VER("VER"); + static constexpr std::string_view TOKEN_VERSION("VERSION"); + static constexpr std::string_view TOKEN_VIEWS("VIEWS"); + static constexpr std::string_view TOKEN_WIRE_STATISTICS("WIRE_STATISTICS"); + static constexpr std::string_view TOKEN_WIRE_STATS("WIRE_STATS"); + + switch (const auto showCommandToken = lexer.getToken(); showCommandToken.type) + { + case Token::TYPE_EOF: + return ShowNode(); + + case Token::TYPE_OTHER: + { + const auto& text = showCommandToken.processedText; + + if (const auto parsed = parseShowOptName(text, TOKEN_CHECKS, 5)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_COLLATES, 7)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_COLLATIONS, 9)) + return parsed.value(); + else if (text.length() >= 7 && std::string(TOKEN_COMMENTS).find(text) == 0) + { + if (parseEof()) + return ShowCommentsNode(); + } + else if (text == TOKEN_DATABASE) + { + if (parseEof()) + return ShowDatabaseNode(); + } + else if (const auto parsed = parseShowOptName(text, TOKEN_DEPENDENCIES, 5)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_DEPENDENCY, 5)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_DOMAINS, 6)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_EXCEPTIONS, 5)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_FILTERS, 6)) + return parsed.value(); + else if (text.length() >= 4 && std::string(TOKEN_FUNCTIONS).find(text) == 0) + { + ShowFunctionsNode node; + node.name = parseName(); + + if (node.name) + { + if (const auto token = lexer.getNameToken(); + token.type == Token::TYPE_OTHER && token.rawText == ".") + { + node.package = node.name; + node.name = parseName(); + + if (parseEof()) + return node; + } + else if (token.type == Token::TYPE_EOF) + { + return node; + } + } + else + return node; + } + else if (const auto parsed = parseShowOptName(text, TOKEN_INDEXES, 3)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_INDICES, 0)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_GENERATORS, 3)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_GRANTS, 5)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_MAPPINGS, 3)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_PACKAGES, 4)) + return parsed.value(); + else if (text.length() >= 4 && std::string(TOKEN_PROCEDURES).find(text) == 0) + { + ShowProceduresNode node; + node.name = parseName(); + + if (node.name) + { + if (const auto token = lexer.getNameToken(); + token.type == Token::TYPE_OTHER && token.rawText == ".") + { + node.package = node.name; + node.name = parseName(); + + if (parseEof()) + return node; + } + else if (token.type == Token::TYPE_EOF) + { + return node; + } + } + else + return node; + } + else if (const auto parsed = parseShowOptName(text, TOKEN_PUBLICATIONS, 3)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_ROLES, 4)) + return parsed.value(); + else if (text.length() >= 6 && std::string(TOKEN_SECCLASSES).find(text) == 0) + { + const auto lexerPos = lexer.getPos(); + const auto token = lexer.getNameToken(); + + ShowSecClassesNode node; + + if (!(token.type == Token::TYPE_OTHER && token.rawText == "*")) + { + lexer.setPos(lexerPos); + node.name = parseName(); + + if (!node.name) + return InvalidNode(); + } + + const auto optDetail = parseName(); + node.detail = optDetail == "DET" || optDetail == "DETAIL"; + + if (!node.detail && optDetail) + return InvalidNode(); + + if (parseEof()) + return node; + } + else if (const auto parsed = parseShowOptName(text, TOKEN_SEQUENCES, 3)) + return parsed.value(); + else if (text == TOKEN_SQL) + { + if (const auto dialectToken = lexer.getToken(); + dialectToken.type == Token::TYPE_OTHER && dialectToken.processedText == "DIALECT") + { + if (parseEof()) + return ShowSqlDialectNode(); + } + } + else if (text.length() >= 3 && std::string(TOKEN_SYSTEM).find(text) == 0) + { + ShowSystemNode node; + + if (const auto objectType = parseName()) + { + const auto objectTypeText = std::string(objectType->c_str()); + + if ((objectTypeText.length() >= 7 && std::string(TOKEN_COLLATES).find(objectTypeText) == 0) || + (objectTypeText.length() >= 9 && std::string(TOKEN_COLLATIONS).find(objectTypeText) == 0)) + { + node.objType = obj_collation; + } + else if (objectTypeText.length() >= 4 && std::string(TOKEN_FUNCTIONS).find(objectTypeText) == 0) + node.objType = obj_udf; + else if (objectTypeText.length() >= 5 && std::string(TOKEN_TABLES).find(objectTypeText) == 0) + node.objType = obj_relation; + else if (objectTypeText.length() >= 4 && std::string(TOKEN_ROLES).find(objectTypeText) == 0) + node.objType = obj_sql_role; + else if (objectTypeText.length() >= 4 && std::string(TOKEN_PROCEDURES).find(objectTypeText) == 0) + node.objType = obj_procedure; + else if (objectTypeText.length() >= 4 && std::string(TOKEN_PACKAGES).find(objectTypeText) == 0) + node.objType = obj_package_header; + else if (objectTypeText.length() >= 3 && std::string(TOKEN_PUBLICATIONS).find(objectTypeText) == 0) + node.objType = obj_publication; + else + return InvalidNode(); + + if (!parseEof()) + return InvalidNode(); + } + + return node; + } + else if (const auto parsed = parseShowOptName(text, TOKEN_TABLES, 5)) + return parsed.value(); + else if (const auto parsed = parseShowOptName(text, TOKEN_TRIGGERS, 4)) + return parsed.value(); + else if (text == TOKEN_USERS) + { + if (parseEof()) + return ShowUsersNode(); + } + else if (text == TOKEN_VER || text == TOKEN_VERSION) + { + if (parseEof()) + return ShowVersionNode(); + } + else if (const auto parsed = parseShowOptName(text, TOKEN_VIEWS, 4)) + return parsed.value(); + else if (text.length() >= 9 && std::string(TOKEN_WIRE_STATISTICS).find(text) == 0 || + text == TOKEN_WIRE_STATS) + { + if (parseEof()) + return ShowWireStatsNode(); + } + + break; + } + } + + return InvalidNode(); +} + +template +std::optional FrontendParser::parseShowOptName(std::string_view showCommand, + std::string_view testCommand, unsigned testCommandMinLen) +{ + if (showCommand == testCommand || + (testCommandMinLen && showCommand.length() >= testCommandMinLen && + std::string(testCommand).find(showCommand) == 0)) + { + Node node; + node.name = parseName(); + + if (!parseEof()) + return InvalidNode(); + + return node; + } + + return std::nullopt; +} + +std::optional FrontendParser::parseUtilEof() +{ + const auto startPos = lexer.getPos(); + auto lastPos = startPos; + bool first = true; + + do + { + const auto token = lexer.getToken(); + + if (token.type == Token::TYPE_EOF) + { + if (first) + return std::nullopt; + + return FrontendLexer::trim(std::string(startPos, lastPos)); + } + + lastPos = lexer.getPos(); + first = false; + } while (true); + + return std::nullopt; +} diff --git a/src/isql/FrontendParser.h b/src/isql/FrontendParser.h new file mode 100644 index 0000000000..940c94f5f7 --- /dev/null +++ b/src/isql/FrontendParser.h @@ -0,0 +1,285 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2024 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + */ + +#ifndef FB_ISQL_FRONTEND_PARSER_H +#define FB_ISQL_FRONTEND_PARSER_H + +#include "../isql/FrontendLexer.h" +#include "../jrd/obj.h" +#include "../common/classes/MetaString.h" +#include +#include +#include +#include +#include + +class FrontendParser +{ +private: + using Token = FrontendLexer::Token; + +public: + struct Options + { + bool schemaAsDatabase = false; + }; + + struct InvalidNode {}; + + struct AddNode { Firebird::MetaString tableName; }; + struct BlobDumpViewNode { ISC_QUAD blobId; std::optional file; }; + struct ConnectNode { std::vector args; }; + struct CopyNode { Firebird::MetaString source; Firebird::MetaString destination; std::string database; }; + struct CreateDatabaseNode { std::vector args; }; + struct DropDatabaseNode {}; + struct EditNode { std::optional file; }; + struct ExitNode {}; + struct ExplainNode { std::string query; }; + struct HelpNode { std::optional command; }; + struct InputNode { std::string file; }; + struct OutputNode { std::optional file; }; + struct QuitNode {}; + struct ShellNode { std::optional command; }; + + struct SetNode {}; + struct SetAutoDdlNode { std::string arg; }; + struct SetAutoTermNode { std::string arg; }; + struct SetBailNode { std::string arg; }; + struct SetBlobDisplayNode { std::string arg; }; + struct SetBulkInsertNode { std::string statement; }; + struct SetCountNode { std::string arg; }; + struct SetEchoNode { std::string arg; }; + struct SetExecPathDisplayNode { std::string arg; }; + struct SetExplainNode { std::string arg; }; + struct SetHeadingNode { std::string arg; }; + struct SetKeepTranParamsNode { std::string arg; }; + struct SetListNode { std::string arg; }; + struct SetLocalTimeoutNode { std::string arg; }; + struct SetMaxRowsNode { std::string arg; }; + struct SetNamesNode { std::optional name; }; + struct SetPerTableStatsNode { std::string arg; }; + struct SetPlanNode { std::string arg; }; + struct SetPlanOnlyNode { std::string arg; }; + struct SetSqldaDisplayNode { std::string arg; }; + struct SetSqlDialectNode { std::string arg; }; + struct SetStatsNode { std::string arg; }; + struct SetTermNode { std::string arg; }; + struct SetTimeNode { std::string arg; }; + struct SetTransactionNode { std::string statement; }; + struct SetWarningsNode { std::string arg; }; + struct SetWidthNode { std::string column; std::string width; }; + struct SetWireStatsNode { std::string arg; }; + + struct ShowNode {}; + struct ShowChecksNode { std::optional name; }; + struct ShowCollationsNode { std::optional name; }; + struct ShowCommentsNode {}; + struct ShowDatabaseNode {}; + struct ShowDomainsNode { std::optional name; }; + struct ShowDependenciesNode { std::optional name; }; + struct ShowExceptionsNode { std::optional name; }; + struct ShowFiltersNode { std::optional name; }; + struct ShowFunctionsNode { std::optional name; std::optional package; }; + struct ShowGeneratorsNode { std::optional name; }; + struct ShowGrantsNode { std::optional name; }; + struct ShowIndexesNode { std::optional name; }; + struct ShowMappingsNode { std::optional name; }; + struct ShowPackagesNode { std::optional name; }; + struct ShowProceduresNode { std::optional name; std::optional package; }; + struct ShowPublicationsNode { std::optional name; }; + struct ShowRolesNode { std::optional name; }; + struct ShowSecClassesNode { std::optional name; bool detail = false; }; + struct ShowSqlDialectNode {}; + struct ShowSystemNode { std::optional objType; }; + struct ShowTablesNode { std::optional name; }; + struct ShowTriggersNode { std::optional name; }; + struct ShowUsersNode {}; + struct ShowVersionNode {}; + struct ShowViewsNode { std::optional name; }; + struct ShowWireStatsNode {}; + + using AnySetNode = std::variant< + SetNode, + + SetAutoDdlNode, + SetAutoTermNode, + SetBailNode, + SetBlobDisplayNode, + SetBulkInsertNode, + SetCountNode, + SetEchoNode, + SetExecPathDisplayNode, + SetExplainNode, + SetHeadingNode, + SetKeepTranParamsNode, + SetListNode, + SetLocalTimeoutNode, + SetMaxRowsNode, + SetNamesNode, + SetPerTableStatsNode, + SetPlanNode, + SetPlanOnlyNode, + SetSqldaDisplayNode, + SetSqlDialectNode, + SetStatsNode, + SetTermNode, + SetTimeNode, + SetTransactionNode, + SetWarningsNode, + SetWidthNode, + SetWireStatsNode, + + InvalidNode + >; + + using AnyShowNode = std::variant< + ShowNode, + + ShowChecksNode, + ShowCollationsNode, + ShowCommentsNode, + ShowDatabaseNode, + ShowDomainsNode, + ShowDependenciesNode, + ShowExceptionsNode, + ShowFiltersNode, + ShowFunctionsNode, + ShowGeneratorsNode, + ShowGrantsNode, + ShowIndexesNode, + ShowMappingsNode, + ShowPackagesNode, + ShowProceduresNode, + ShowPublicationsNode, + ShowRolesNode, + ShowSecClassesNode, + ShowSqlDialectNode, + ShowSystemNode, + ShowTablesNode, + ShowTriggersNode, + ShowUsersNode, + ShowVersionNode, + ShowViewsNode, + ShowWireStatsNode, + + InvalidNode + >; + + using AnyNode = std::variant< + AddNode, + BlobDumpViewNode, + ConnectNode, + CopyNode, + CreateDatabaseNode, + DropDatabaseNode, + EditNode, + ExitNode, + ExplainNode, + HelpNode, + InputNode, + OutputNode, + QuitNode, + ShellNode, + + AnySetNode, + AnyShowNode, + + InvalidNode + >; + + template + static inline constexpr bool AlwaysFalseV = false; + +public: + FrontendParser(std::string_view statement, const Options& aOptions) + : lexer(statement), + options(aOptions) + { + } + + FrontendParser(const FrontendParser&) = delete; + FrontendParser& operator=(const FrontendParser&) = delete; + +public: + static AnyNode parse(std::string_view statement, const Options& options) + { + try + { + FrontendParser parser(statement, options); + return parser.internalParse(); + } + catch (const FrontendLexer::IncompleteTokenError&) + { + return InvalidNode(); + } + } + +private: + AnyNode internalParse(); + AnySetNode parseSet(); + + template + std::optional parseSet(std::string_view setCommand, + std::string_view testCommand, unsigned testCommandMinLen = 0, bool useProcessedText = true); + + AnyShowNode parseShow(); + + template + std::optional parseShowOptName(std::string_view showCommand, + std::string_view testCommand, unsigned testCommandMinLen = 0); + + bool parseEof() + { + return lexer.getToken().type == Token::TYPE_EOF; + } + + std::optional parseName() + { + const auto token = lexer.getNameToken(); + + if (token.type != Token::TYPE_EOF) + return Firebird::MetaString(token.processedText.c_str()); + + return std::nullopt; + } + + std::optional parseFileName() + { + const auto token = lexer.getToken(); + + if (token.type == Token::TYPE_STRING || token.type == Token::TYPE_META_STRING) + return token.processedText; + else if (token.type != Token::TYPE_EOF) + return token.rawText; + + return std::nullopt; + } + + std::optional parseUtilEof(); + +private: + FrontendLexer lexer; + const Options options; +}; + +#endif // FB_ISQL_FRONTEND_PARSER_H diff --git a/src/isql/isql.epp b/src/isql/isql.epp index 8de2929f97..7370e160fe 100644 --- a/src/isql/isql.epp +++ b/src/isql/isql.epp @@ -55,6 +55,8 @@ #include #include #include "../isql/FrontendLexer.h" +#include "../isql/FrontendParser.h" +#include "../common/StdHelper.h" #include "../common/utils_proto.h" #include "../common/classes/array.h" #include "../common/classes/init.h" @@ -450,30 +452,24 @@ struct ri_actions const SCHAR* ri_action_print_mixed; }; -static processing_state add_row(TEXT*); -static processing_state blobedit(const TEXT*, const TEXT* const*); +static processing_state add_row(const MetaString&); +static processing_state blobedit(ISC_QUAD, const char*); static processing_state bulk_insert_hack(const char* command); static bool bulk_insert_retriever(const char* prompt); static void check_autoterm(); static bool check_date(const tm& times); static bool check_time(const tm& times); static bool check_timestamp(const tm& times, const int msec); -static size_t chop_at(char target[], const size_t size); -static void col_check(const TEXT*, unsigned*); -static processing_state copy_table(TEXT*, TEXT*, TEXT*); -static processing_state create_db(const TEXT*, TEXT*); +static void col_check(const MetaString&, unsigned*); +static processing_state copy_table(const MetaString&, const MetaString&, const TEXT*); +static processing_state create_db(const FrontendParser::CreateDatabaseNode& node); static void do_isql(); static processing_state drop_db(); -static processing_state edit(const TEXT* const*); +static processing_state edit(const TEXT*); static processing_state end_trans(); static processing_state escape(const TEXT*); static processing_state execSetDebugCommand(); -static processing_state frontend(const TEXT*); -static processing_state frontend_set(const char* cmd, const char* const* parms, - const char* const* lparms, char* const bad_dialect_buf, bool& bad_dialect); -static void frontend_free_parms(TEXT*[], TEXT*[], TEXT parm_defaults[][1]); -static void frontend_load_parms(const TEXT* p, TEXT* parms[], TEXT* lparms[], - TEXT parm_defaults[][1]); +static processing_state frontend(const std::string&); static processing_state do_set_command(const TEXT*, bool*); static processing_state get_dialect(const char* const dialect_str, char* const bad_dialect_buf, bool& bad_dialect); @@ -483,16 +479,13 @@ static processing_state print_sets(); static processing_state explain(const TEXT*); static processing_state help(const TEXT*); static bool isyesno(const TEXT*); -static processing_state newdb(TEXT*, const TEXT*, const TEXT*, int, const TEXT*, bool); +static processing_state newdb(const TEXT*, const TEXT*, const TEXT*, int, const TEXT*, bool); static processing_state newinput(const TEXT*); static processing_state newoutput(const TEXT*); static processing_state newsize(const TEXT*, const TEXT*); static processing_state newMaxRows(const TEXT* newMaxRowsStr); static processing_state newtrans(const TEXT*); static processing_state parse_arg(int, SCHAR**, SCHAR*); //, FILE**); -#ifdef DEV_BUILD -static processing_state passthrough(const char* cmd); -#endif static unsigned print_item(TEXT**, const IsqlVar*, const unsigned); static void print_item_numeric(SINT64, int, int, TEXT*); static processing_state print_line(Firebird::IMessageMetadata*, UCHAR*, const unsigned pad[], TEXT line[]); @@ -2388,7 +2381,7 @@ static string get_numeric_value(const char* fromStr) } -static processing_state add_row(TEXT* tabname) +static processing_state add_row(const MetaString& tabname) { /************************************** * @@ -2407,7 +2400,7 @@ static processing_state add_row(TEXT* tabname) * tabname -- Name of table to insert into * **************************************/ - if (!*tabname) + if (tabname.isEmpty()) return (ps_ERR); if (!Interactive) @@ -2428,13 +2421,9 @@ static processing_state add_row(TEXT* tabname) return FAIL; } - chop_at(tabname, QUOTED_NAME_SIZE); - if (tabname[0] != DBL_QUOTE) - IUTILS_make_upper(tabname); - // Query to obtain relation information string str2; - str2.printf("SELECT * FROM %s", tabname); + str2.printf("SELECT * FROM %s", IUTILS_name_to_string(tabname).c_str()); if (global_Stmt) { @@ -2472,7 +2461,7 @@ static processing_state add_row(TEXT* tabname) // There is a question mark for each column that's known to be updatable. - insertstring.printf("INSERT INTO %s (", tabname); + insertstring.printf("INSERT INTO %s (", IUTILS_name_to_string(tabname).c_str()); unsigned i_cols = 0; { // scope @@ -2999,7 +2988,7 @@ static processing_state add_row(TEXT* tabname) } -static processing_state blobedit(const TEXT* action, const TEXT* const* cmd) +static processing_state blobedit(ISC_QUAD blobId, const char* path) { /************************************** * @@ -3016,20 +3005,14 @@ static processing_state blobedit(const TEXT* action, const TEXT* const* cmd) if (!ISQL_dbcheck()) return FAIL; - if (*cmd[1] == 0) + if (blobId.gds_quad_high == 0 && blobId.gds_quad_low == 0) return ps_ERR; - const TEXT* p = cmd[1]; - - // Find the high and low values of the blob id - ISC_QUAD blobid; - sscanf(p, "%" xLONGFORMAT":%" xLONGFORMAT, &blobid.gds_quad_high, &blobid.gds_quad_low); - // If it isn't an explicit blobedit, then do a dump. Since this is a // user operation, put it on the M__trans handle. processing_state rc = SKIP; - if (!strcmp(action, "BLOBVIEW")) + if (!path) { Firebird::UtilInterfacePtr utl; PathName tmpf = TempFile::create(fbStatus, "blob"); @@ -3037,7 +3020,7 @@ static processing_state blobedit(const TEXT* action, const TEXT* const* cmd) return ps_ERR; const char* filename = tmpf.c_str(); - utl->dumpBlob(fbStatus, &blobid, DB, M__trans, filename, FB_TRUE); + utl->dumpBlob(fbStatus, &blobId, DB, M__trans, filename, FB_TRUE); if (ISQL_errmsg(fbStatus)) rc = ps_ERR; else @@ -3045,14 +3028,12 @@ static processing_state blobedit(const TEXT* action, const TEXT* const* cmd) unlink(filename); } - else if ((!strcmp(action, "BLOBDUMP")) && (*cmd[2])) + else if (*path) { // If this is a blobdump, make sure there is a file name // We can't be sure if the BLOB is TEXT or BINARY data, // as we're not sure, we'll dump it in BINARY mode. - TEXT path[MAXPATHLEN]; - strip_quotes(cmd[2], path); - Firebird::UtilInterfacePtr()->dumpBlob(fbStatus, &blobid, DB, M__trans, path, FB_FALSE); + Firebird::UtilInterfacePtr()->dumpBlob(fbStatus, &blobId, DB, M__trans, path, FB_FALSE); } else rc = ps_ERR; @@ -3096,14 +3077,6 @@ static processing_state blobedit(const TEXT* action, const TEXT* const* cmd) // If you came here looking for robust parsing code, you're at the wrong place. static processing_state bulk_insert_hack(const char* command) { - // Skip "SET BULK_INSERT" part. - for (int j = 0; j < 2; ++j) - { - while (*command && *command != 0x20) - ++command; - while (*command && *command == 0x20) - ++command; - } if (!*command) return ps_ERR; @@ -4095,23 +4068,7 @@ static bool check_timestamp(const tm& times, const int msec) } -// ************* -// c h o p _ a t -// ************* -// Simply ensure a given string argument fits in a size, terminator included. -static size_t chop_at(char target[], const size_t size) -{ - size_t len = strlen(target); - if (len >= size) - { - len = size - 1; - target[len] = 0; - } - return len; -} - - -static void col_check(const TEXT* tabname, unsigned* colnumber) +static void col_check(const MetaString& tabname, unsigned* colnumber) { /************************************** * @@ -4132,7 +4089,7 @@ static void col_check(const TEXT* tabname, unsigned* colnumber) FOR F IN RDB$FIELDS CROSS R IN RDB$RELATION_FIELDS WITH F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE AND - R.RDB$RELATION_NAME EQ tabname + R.RDB$RELATION_NAME EQ tabname.c_str() SORTED BY R.RDB$FIELD_POSITION, R.RDB$FIELD_NAME if ((!F.RDB$DIMENSIONS.NULL && F.RDB$DIMENSIONS) || (!F.RDB$COMPUTED_BLR.NULL)) @@ -4147,9 +4104,7 @@ static void col_check(const TEXT* tabname, unsigned* colnumber) } -static processing_state copy_table(TEXT* source, - TEXT* destination, - TEXT* otherdb) +static processing_state copy_table(const MetaString& source, const MetaString& destination, const TEXT* otherdb) { /************************************** * @@ -4164,7 +4119,7 @@ static processing_state copy_table(TEXT* source, * destination == name of newly created table * **************************************/ - if (!source[0] || !destination[0]) + if (source.isEmpty() || destination.isEmpty()) { STDERROUT("Either source or destination tables are missing"); return SKIP; @@ -4193,43 +4148,9 @@ static processing_state copy_table(TEXT* source, return END; } - chop_at(source, QUOTED_NAME_SIZE); - if (source[0] != DBL_QUOTE) - IUTILS_make_upper(source); - /* - chop_at(source_tbl, MAX_SQL_IDENTIFIER_SIZE); - TEXT source[QUOTED_NAME_SIZE]; - bool delimited_yes = source_tbl[0] == DBL_QUOTE; - if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) { - IUTILS_copy_SQL_id(source_tbl, source, DBL_QUOTE); - } - else + if (EXTRACT_list_table(source.c_str(), destination.c_str(), domain_flag, -1)) { - strcpy(source, source_tbl); - IUTILS_make_upper(source); - } - */ - - chop_at(destination, QUOTED_NAME_SIZE); - if (destination[0] != DBL_QUOTE) - IUTILS_make_upper(destination); - /* - chop_at(destination_tbl, MAX_SQL_IDENTIFIER_SIZE); - TEXT destination[QUOTED_NAME_SIZE]; - delimited_yes = destination_tbl[0] == DBL_QUOTE; - if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) { - IUTILS_copy_SQL_id(destination_tbl, destination, DBL_QUOTE); - } - else - { - strcpy(destination, destination_tbl); - IUTILS_make_upper(destination); - } - */ - - if (EXTRACT_list_table(source, destination, domain_flag, -1)) - { - IUTILS_msg_get(NOT_FOUND, errbuf, SafeArg() << source); + IUTILS_msg_get(NOT_FOUND, errbuf, SafeArg() << source.c_str()); STDERROUT(errbuf); fclose(isqlGlob.Out); } @@ -4245,7 +4166,7 @@ static processing_state copy_table(TEXT* source, sprintf(cmd, "isql -q %s -i %s", altdb, ftmp.c_str()); if (system(cmd)) { - IUTILS_msg_get(COPY_ERR, errbuf, SafeArg() << destination << altdb); + IUTILS_msg_get(COPY_ERR, errbuf, SafeArg() << destination.c_str() << altdb); STDERROUT(errbuf); } } @@ -4257,21 +4178,7 @@ static processing_state copy_table(TEXT* source, } -static void appendClause(string& to, const char* label, const TEXT* value, char quote = 0) -{ - to += ' '; - to += label; - to += ' '; - if (quote) - to += quote; - to += value; - if (quote) - to += quote; - to += ' '; -} - - -static processing_state create_db(const TEXT* statement, TEXT* d_name) +static processing_state create_db(const FrontendParser::CreateDatabaseNode& node) { /************************************** * @@ -4283,109 +4190,37 @@ static processing_state create_db(const TEXT* statement, TEXT* d_name) * Intercept create database commands to * adjust the DB and transaction handles * - * Parameters: statement == the entire statement for processing. - * * Note: SQL ROLE setting must be taken into an account no matter * that the newly created database will not have any user roles defined * in it. Role may affect right to create new database. * **************************************/ + processing_state ret = SKIP; // Disconnect from the database and cleanup ISQL_disconnect_database(false); - // Parse statement to tokens - const char* quotes = "\"'"; - string nlStatement(statement); - nlStatement += '\n'; - Firebird::Tokens toks; - toks.quotes(quotes); - toks.parse(0, nlStatement.c_str()); - - const unsigned KEY_USER = 0; - const unsigned KEY_PASS = 1; - const unsigned KEY_ROLE = 2; - const unsigned KEY_NAMES = 3; - const unsigned KEY_SET = 4; - - struct Key + for (const auto createWithRole : {true, false}) { - const char* text; - bool has; - }; + std::string statement = "create database " + node.args[0].rawText; - Key keys[5] = { - { "USER", false }, - { "PASSWORD", false }, - { "ROLE", false }, - { "NAMES", false }, - { "SET", false } - }; + // If there are global parameters, we will set them into the create statement. - for (unsigned t = 0; t < toks.getCount(); ++t) - { - Firebird::NoCaseString token(toks[t].text, toks[t].length); - unsigned k; + if (global_usr) + statement += std::string(" USER ") + isqlGlob.User; - for (k = 0; k < FB_NELEM(keys); ++k) - { - if (token == keys[k].text) - { - if (k != KEY_NAMES || keys[KEY_SET].has) - keys[k].has = true; - break; - } - } + if (global_psw) + statement += std::string(" PASSWORD '") + Password + "'"; - if (k != KEY_SET) - keys[KEY_SET].has = false; - } + if (global_role && createWithRole) + statement += std::string(" ROLE ") + isqlGlob.Role; - for (int createWithoutRole = 0; createWithoutRole < 2; ++createWithoutRole) - { - ret = SKIP; - TEXT usr[USER_LENGTH]; - TEXT psw[PASSWORD_LENGTH]; - TEXT role[ROLE_LENGTH]; + if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET) != 0) + statement += std::string(" SET NAMES '") + setValues.ISQL_charset + "'"; - string modifiedCreateStatement(statement); - - TEXT quote = DBL_QUOTE; - const TEXT* p = NULL; - - // If there is a global parameter, we will set it into the create stmt. - if (global_usr || global_role || global_psw || - (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET))) - { - strcpy(usr, isqlGlob.User); - strip_quotes(Password, psw); - strcpy(role, isqlGlob.Role); - - string clauses; - - if (global_usr && !keys[KEY_USER].has) - appendClause(clauses, keys[KEY_USER].text, usr); - - if (global_psw && !keys[KEY_PASS].has) - appendClause(clauses, keys[KEY_PASS].text, psw, SINGLE_QUOTE); - - if (global_role && (!keys[KEY_ROLE].has) && createWithoutRole == 0) - appendClause(clauses, keys[KEY_ROLE].text, role); - - if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)&& !keys[KEY_NAMES].has) - { - string setNames = keys[KEY_SET].text; - setNames += ' '; - setNames += keys[KEY_NAMES].text; - appendClause(clauses, setNames.c_str(), setValues.ISQL_charset, SINGLE_QUOTE); - } - - if (toks.getCount() > 3) - modifiedCreateStatement.insert(toks[3].origin, clauses); - else - modifiedCreateStatement += clauses; - } + for (std::size_t i = 1; i < node.args.size(); ++i) + statement += std::string(" ") + node.args[i].rawText; // execute the create statement // If the isqlGlob.SQL_dialect is not set or set to 2, create the database @@ -4394,19 +4229,18 @@ static processing_state create_db(const TEXT* statement, TEXT* d_name) (isqlGlob.SQL_dialect == 0 || isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION) ? requested_SQL_dialect : isqlGlob.SQL_dialect; - DB = Firebird::UtilInterfacePtr()->executeCreateDatabase(fbStatus, modifiedCreateStatement.length(), - modifiedCreateStatement.c_str(), dialect, NULL); + DB = Firebird::UtilInterfacePtr()->executeCreateDatabase(fbStatus, statement.length(), + statement.c_str(), dialect, nullptr); - if ((!DB) && (createWithoutRole == 0) && (fbStatus->getErrors()[1] == isc_dsql_error)) + if (!DB && createWithRole && fbStatus->getErrors()[1] == isc_dsql_error) { - // OLd server failed to parse ROLE clause + // Old server failed to parse ROLE clause continue; } if (ISQL_errmsg(fbStatus)) - { ret = FAIL; - } + break; } @@ -4423,12 +4257,7 @@ static processing_state create_db(const TEXT* statement, TEXT* d_name) // CVC: Someone may decide to play strange games with undocumented ability // to write crap between CREATE DATABASE and the db name, as described by // Helen on CORE-932. Let's see if we can discover the real db name. - string s; - if (toks.getCount() > 2) - s.assign(toks[2].text, toks[2].length); - else - s = d_name; - strip_quotes(s.c_str(), isqlGlob.global_Db_name); + strcpy(isqlGlob.global_Db_name, node.args[0].getProcessedString().c_str()); ISQL_get_version(true); @@ -4792,7 +4621,7 @@ static processing_state drop_db() } -static processing_state edit(const TEXT* const* cmd) +static processing_state edit(const TEXT* file) { /************************************** * @@ -4810,9 +4639,6 @@ static processing_state edit(const TEXT* const* cmd) **************************************/ // Set up editing command for shell - - const TEXT* file = cmd[1]; - // If there is a file name specified, try to open it processing_state rc = SKIP; @@ -4998,9 +4824,6 @@ static processing_state escape(const TEXT* cmd) const TEXT* shellcmd = cmd; - // Search past the 'shell' keyword - shellcmd += strlen("shell"); - // Eat whitespace at beginning of command while (*shellcmd && fb_utils::isspace(*shellcmd)) shellcmd++; @@ -5058,7 +4881,7 @@ static processing_state execSetDebugCommand() } -static processing_state frontend(const TEXT* statement) +static processing_state frontend(const std::string& statement) { /************************************** * @@ -5075,186 +4898,57 @@ static processing_state frontend(const TEXT* statement) * **************************************/ - class FrontOptions : public OptionsBase - { - public: - enum front_commands - { - show, add, copy, - blobview, output, shell, set, create, drop, connect, - edit, input, quit, exit, explain, help, -#ifdef DEV_BUILD - passthrough, -#endif - wrong - }; - FrontOptions(const optionsMap* inmap, size_t insize, int wrongval) - : OptionsBase(inmap, insize, wrongval) - {} - }; + FrontendParser::Options parserOptions; + parserOptions.schemaAsDatabase = isqlGlob.major_ods > 0 && isqlGlob.major_ods < ODS_VERSION12; - static const FrontOptions::optionsMap options[] = - { - {FrontOptions::show, "SHOW", 0}, - {FrontOptions::add, "ADD", 0}, - {FrontOptions::copy, "COPY", 0}, - {FrontOptions::blobview, "BLOBVIEW", 0}, - {FrontOptions::blobview, "BLOBDUMP", 0}, - //{FrontOptions::output, "OUT", }, - {FrontOptions::output, "OUTPUT", 3}, - {FrontOptions::shell, "SHELL", 0}, - {FrontOptions::set, "SET", 0}, - {FrontOptions::create, "CREATE", 0}, - {FrontOptions::drop, "DROP", 0}, - {FrontOptions::connect, "CONNECT", 0}, - {FrontOptions::edit, "EDIT", 0}, - //{FrontOptions::input, "IN", }, - {FrontOptions::input, "INPUT", 2}, - {FrontOptions::quit, "QUIT", 0}, - {FrontOptions::exit, "EXIT", 0}, - {FrontOptions::explain, "EXPLAIN", 0}, - {FrontOptions::help, "?", 0}, - {FrontOptions::help, "HELP", 0} -#ifdef DEV_BUILD - , - {FrontOptions::passthrough, "PASSTHROUGH", 0} -#endif - }; - - TEXT errbuf[MSG_LENGTH]; - - // Store the first NUM_TERMS words as they appear in parms, using blanks - // to delimit. Each word beyond a real word gets a null char - // Shift parms to upper case, leaving original case in lparms - typedef TEXT* isql_params_t[MAX_TERMS]; - isql_params_t parms, lparms; - for (FB_SIZE_T iter = 0; iter < FB_NELEM(lparms); ++iter) - { - lparms[iter] = NULL; - parms[iter] = NULL; - } - TEXT parm_defaults[MAX_TERMS][1]; - - // Any whitespace and comments at the beginning are already swallowed by get_statement() - - // Set beginning of statement past comment - const TEXT* const cmd = statement; - if (!*cmd) - { - // In case any default transaction was started - commit it here - if (fbTrans) - commit_trans(&fbTrans); - - return SKIP; - } - - frontend_load_parms(statement, parms, lparms, parm_defaults); - - char bad_dialect_buf[512]; + const auto node = FrontendParser::parse(statement, parserOptions); bool bad_dialect = false; + char bad_dialect_buf[512]; - // Look to see if the words (parms) match any known verbs. If nothing - // matches then just hand the statement to process_statement - processing_state ret = SKIP; - const FrontOptions frontoptions(options, FB_NELEM(options), FrontOptions::wrong); - switch (frontoptions.getCommand(parms[0])) - { - case FrontOptions::show: - if (DB && !frontendTransaction()) + const auto ret = std::visit(StdVisitOverloads{ + [](const FrontendParser::InvalidNode&) { - // Free the frontend command - frontend_free_parms(parms, lparms, parm_defaults); - return FAIL; - } + return CONT; + }, - ret = SHOW_metadata(parms, lparms); - if (fbTrans) - commit_trans(&fbTrans); - break; - - case FrontOptions::add: - if (!frontendTransaction()) + [](const FrontendParser::AddNode& node) { - // Free the frontend command - frontend_free_parms(parms, lparms, parm_defaults); - return FAIL; - } + if (!frontendTransaction()) + return FAIL; - ret = add_row(lparms[1]); - if (fbTrans) - commit_trans(&fbTrans); - break; + const auto ret = add_row(node.tableName); + if (fbTrans) + commit_trans(&fbTrans); - case FrontOptions::copy: - if (!frontendTransaction()) + return ret; + }, + + [](const FrontendParser::BlobDumpViewNode& node) { - // Free the frontend command - frontend_free_parms(parms, lparms, parm_defaults); - return FAIL; - } + return blobedit(node.blobId, (node.file ? node.file->c_str() : nullptr)); + }, - ret = copy_table(lparms[1], lparms[2], lparms[3]); - if (fbTrans) - commit_trans(&fbTrans); - break; - - case FrontOptions::blobview: - ret = blobedit(parms[0], lparms); - break; - - case FrontOptions::output: - ret = newoutput(lparms[1]); - break; - - case FrontOptions::shell: - ret = escape(cmd); - break; - - case FrontOptions::set: - ret = frontend_set(cmd, parms, lparms, bad_dialect_buf, bad_dialect); - break; - - case FrontOptions::create: - if (!strcmp(parms[1], "DATABASE") || - (!strcmp(parms[1], "SCHEMA") && isqlGlob.major_ods > 0 && isqlGlob.major_ods < ODS_VERSION12)) + [](const FrontendParser::ConnectNode& node) { - ret = create_db(cmd, lparms[2]); - } - else - ret = CONT; - break; - - case FrontOptions::drop: - if (!strcmp(parms[1], "DATABASE") || - (!strcmp(parms[1], "SCHEMA") && isqlGlob.major_ods > 0 && isqlGlob.major_ods < ODS_VERSION12)) - { - if (*parms[2]) - ret = ps_ERR; - else - ret = drop_db(); - } - else - ret = CONT; - break; - - case FrontOptions::connect: - { - const TEXT* psw = NULL; - const TEXT* usr = NULL; - const TEXT* sql_role_nm = NULL; + std::optional psw; + const char* usr = nullptr; + const char* sql_role_nm = nullptr; int numbufs = 0; // if a parameter is given in the command more than once, the // last one will be used. The parameters can appear each any // order, but each must provide a value. - ret = SKIP; - for (int i = 2; i < (MAX_TERMS - 1);) + auto ret = SKIP; + + for (std::size_t i = 1; i < node.args.size() - 1;) { - if (!strcmp(parms[i], "CACHE") && *lparms[i + 1]) + const auto& clause = node.args[i].processedText; + + if (clause == "CACHE") { char* err; - long value = strtol(lparms[i + 1], &err, 10); + long value = strtol(node.args[i + 1].processedText.c_str(), &err, 10); if (*err || (value <= 0) || (value >= INT_MAX)) { ret = ps_ERR; @@ -5263,85 +4957,387 @@ static processing_state frontend(const TEXT* statement) numbufs = (int) value; i += 2; } - else if (!strcmp(parms[i], "USER") && *lparms[i + 1]) + else if (clause == "USER") { - usr = lparms[i + 1]; + usr = node.args[i + 1].processedText.c_str(); i += 2; } - else if (!strcmp(parms[i], "PASSWORD") && *lparms[i + 1]) + else if (clause == "PASSWORD") { - psw = lparms[i + 1]; + psw = node.args[i + 1].getProcessedString(); i += 2; } - else if (!strcmp(parms[i], "ROLE") && *lparms[i + 1]) + else if (clause == "ROLE") { - sql_role_nm = lparms[i + 1]; + sql_role_nm = node.args[i + 1].processedText.c_str(); i += 2; } - else if (*parms[i]) + else if (!clause.empty()) { // Unrecognized option to CONNECT ret = ps_ERR; break; } else - i++; + ++i; } + if (ret != ps_ERR) - ret = newdb(lparms[1], usr, psw, numbufs, sql_role_nm, true); + { + ret = newdb( + node.args[0].getProcessedString().c_str(), + usr, + (psw ? psw->c_str() : nullptr), + numbufs, + sql_role_nm, + true); + } + + return SKIP; + }, + + [](const FrontendParser::CopyNode& node) + { + if (!frontendTransaction()) + return FAIL; + + const auto ret = copy_table(node.source, node.destination, node.database.c_str()); + + if (fbTrans) + commit_trans(&fbTrans); + + return ret; + }, + + [](const FrontendParser::CreateDatabaseNode& node) + { + return create_db(node); + }, + + [](const FrontendParser::DropDatabaseNode&) + { + return drop_db(); + }, + + [](const FrontendParser::EditNode& node) + { + return edit(node.file.value_or("").c_str()); + }, + + [](const FrontendParser::ExitNode& node) + { + return EXIT; + }, + + [](const FrontendParser::ExplainNode& node) + { + return explain(node.query.c_str()); + }, + + [](const FrontendParser::HelpNode& node) + { + return help(node.command.value_or("").c_str()); + }, + + [](const FrontendParser::InputNode& node) + { + return newinput(node.file.c_str()); + }, + + [](const FrontendParser::OutputNode& node) + { + return newoutput(node.file.value_or("").c_str()); + }, + + [](const FrontendParser::QuitNode& node) + { + return BACKOUT; + }, + + [](const FrontendParser::ShellNode& node) + { + return escape(node.command.value_or("").c_str()); + }, + + // SET commands + + [&](const FrontendParser::AnySetNode& anySet) + { + return std::visit(StdVisitOverloads{ + [](const FrontendParser::InvalidNode&) + { + return CONT; + }, + + [](const FrontendParser::SetNode&) + { + return print_sets(); + }, + + [](const FrontendParser::SetAutoDdlNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.Autocommit); + }, + + [](const FrontendParser::SetAutoTermNode& node) + { + const auto ret = do_set_command(node.arg.c_str(), &setValues.AutoTerm); + if (setValues.AutoTerm) + { + isqlGlob.Termlen = 1; + strcpy(isqlGlob.global_Term, ";"); + } + return ret; + }, + + [](const FrontendParser::SetBailNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.BailOnError); + }, + + [](const FrontendParser::SetBlobDisplayNode& node) + { + // No arg means turn off blob display + if (node.arg.empty() || node.arg == "OFF") + setValues.Doblob = NO_BLOBS; + else if (node.arg == "ALL") + setValues.Doblob = ALL_BLOBS; + else + setValues.Doblob = atoi(node.arg.c_str()); + return SKIP; + }, + + [](const FrontendParser::SetBulkInsertNode& node) + { + return bulk_insert_hack(node.statement.c_str()); + }, + + [](const FrontendParser::SetCountNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.Docount); + }, + + [](const FrontendParser::SetEchoNode& node) + { + const auto ret = do_set_command(node.arg.c_str(), &setValues.Echo); + if (!setValues.Echo) + ISQL_prompt(""); + return ret; + }, + + [](const FrontendParser::SetExecPathDisplayNode& node) + { + if (node.arg.empty()) + return ps_ERR; + else if (node.arg == "OFF") + setValues.ExecPathDisplay[0] = 0; + else + { + static constexpr UCHAR execPath[] = {isc_info_sql_exec_path_blr_text}; + + if (node.arg != "BLR") + return ps_ERR; + + memcpy(setValues.ExecPathDisplay, execPath, FB_NELEM(execPath)); + setValues.ExecPathDisplay[FB_NELEM(execPath)] = 0; + } + + return execSetDebugCommand(); + }, + + [](const FrontendParser::SetExplainNode& node) + { + auto ret = do_set_command(node.arg.c_str(), &setValues.ExplainPlan); + if (setValues.ExplainPlan) + ret = do_set_command("ON", &setValues.Plan); + return ret; + }, + + [](const FrontendParser::SetHeadingNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.Heading); + }, + + [](const FrontendParser::SetKeepTranParamsNode& node) + { + const bool oldValue = setValues.KeepTranParams; + const auto ret = do_set_command(node.arg.c_str(), &setValues.KeepTranParams); + if ((ret != ps_ERR) && (oldValue != setValues.KeepTranParams)) + TranParams->assign(setValues.KeepTranParams ? DEFAULT_DML_TRANS_SQL : ""); + return ret; + }, + + [](const FrontendParser::SetListNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.List); + }, + + [](const FrontendParser::SetLocalTimeoutNode& node) + { + int val = strtol(node.arg.c_str(), nullptr, 10); + + if (val < 0) + return ps_ERR; + else + { + setValues.StmtTimeout = val; + return SKIP; + } + }, + + [](const FrontendParser::SetMaxRowsNode& node) + { + return newMaxRows((node.arg.empty() ? "0" : node.arg.c_str())); + }, + + [](const FrontendParser::SetNamesNode& node) + { + const auto setNames = node.name ? node.name->c_str() : DEFCHARSET; + + const size_t lgth = strlen(setNames); + if (lgth < MAXCHARSET_SIZE) + strcpy(setValues.ISQL_charset, setNames); + else + fb_utils::copy_terminate(setValues.ISQL_charset, setNames, MAXCHARSET_SIZE); + + return SKIP; + }, + + [](const FrontendParser::SetPerTableStatsNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.PerTableStats); + }, + + [](const FrontendParser::SetPlanNode& node) + { + auto ret = do_set_command(node.arg.c_str(), &setValues.Plan); + if (setValues.Planonly && !setValues.Plan) + ret = do_set_command("OFF", &setValues.Planonly); + return ret; + }, + + [](const FrontendParser::SetPlanOnlyNode& node) + { + auto ret = do_set_command(node.arg.c_str(), &setValues.Planonly); + if (setValues.Planonly && !setValues.Plan) + { + // turn on plan + ret = do_set_command("ON", &setValues.Plan); + } + return ret; + }, + + [](const FrontendParser::SetSqldaDisplayNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.Sqlda_display); + }, + + [&](const FrontendParser::SetSqlDialectNode& node) + { + return get_dialect(node.arg.c_str(), bad_dialect_buf, bad_dialect); + }, + + [](const FrontendParser::SetStatsNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.Stats); + }, + + [](const FrontendParser::SetTermNode& node) + { + const TEXT* termStr = node.arg.empty() ? DEFTERM : node.arg.c_str(); + + for (size_t iter = 0; iter < sizeof(FORBIDDEN_TERM_CHARS); ++iter) + { + if (strchr(termStr, FORBIDDEN_TERM_CHARS[iter])) + { + TEXT msg_string[MSG_LENGTH]; + IUTILS_msg_get(INVALID_TERM_CHARS, msg_string, SafeArg() << FORBIDDEN_TERM_CHARS_DISPLAY); + isqlGlob.printf("%s\n", msg_string); + return ps_ERR; + } + } + + setValues.AutoTerm = false; + + isqlGlob.Termlen = strlen(termStr); + if (isqlGlob.Termlen < MAXTERM_SIZE) + strcpy(isqlGlob.global_Term, termStr); + else + { + isqlGlob.Termlen = MAXTERM_SIZE - 1; + fb_utils::copy_terminate(isqlGlob.global_Term, termStr, isqlGlob.Termlen + 1); + } + + return SKIP; + }, + + [](const FrontendParser::SetTimeNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.Time_display); + }, + + [](const FrontendParser::SetTransactionNode& node) + { + return newtrans(node.statement.c_str()); + }, + + [](const FrontendParser::SetWarningsNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.Warnings); + }, + + [](const FrontendParser::SetWidthNode& node) + { + return newsize(node.column.c_str(), node.width.c_str()); + }, + + [](const FrontendParser::SetWireStatsNode& node) + { + return do_set_command(node.arg.c_str(), &setValues.WireStats); + }, + + [](auto& arg) + { + static_assert(FrontendParser::AlwaysFalseV, + "Add visitor method for that set node type"); + } + }, anySet); + }, + + // SHOW commands + + [](const FrontendParser::AnyShowNode& node) + { + if (DB && !frontendTransaction()) + return FAIL; + + const auto ret = SHOW_metadata(node); + + if (fbTrans) + commit_trans(&fbTrans); + + return ret; + }, + + [](auto& arg) + { + static_assert(FrontendParser::AlwaysFalseV, "Add visitor method for that node type"); } - break; - - case FrontOptions::edit: - ret = edit(lparms); - break; - - case FrontOptions::input: - // CVC: Set by newinput() below only if successful. - //Input_file = true; - ret = newinput(lparms[1]); - break; - - case FrontOptions::quit: - ret = BACKOUT; - break; - - case FrontOptions::exit: - ret = EXIT; - break; - - case FrontOptions::explain: - ret = explain(cmd + 7); - break; - - case FrontOptions::help: - ret = help(parms[1]); - break; - -#ifdef DEV_BUILD - case FrontOptions::passthrough: - ret = passthrough(cmd + 11); - break; -#endif - - default: // Didn't match, it must be SQL - ret = CONT; - break; - } + }, node); // In case any default transaction was started - commit it here if (fbTrans) commit_trans(&fbTrans); - // Free the frontend command - frontend_free_parms(parms, lparms, parm_defaults); - if (ret == ps_ERR) { + TEXT errbuf[MSG_LENGTH]; + if (bad_dialect) IUTILS_msg_get(CMD_ERR, errbuf, SafeArg() << bad_dialect_buf); else - IUTILS_msg_get(CMD_ERR, errbuf, SafeArg() << cmd); + IUTILS_msg_get(CMD_ERR, errbuf, SafeArg() << statement.c_str()); + STDERROUT(errbuf); } @@ -5349,426 +5345,6 @@ static processing_state frontend(const TEXT* statement) } -static void frontend_free_parms(TEXT* parms[], TEXT* lparms[], TEXT parm_defaults[][1]) -{ - for (int j = 0; j < MAX_TERMS; j++) - { - if (parms[j] && parms[j] != parm_defaults[j]) - { - ISQL_FREE(parms[j]); - ISQL_FREE(lparms[j]); - } - } -} - - -static void frontend_load_parms(const TEXT* p, TEXT* parms[], TEXT* lparms[], - TEXT parm_defaults[][1]) -{ - TEXT buffer[BUFFER_LENGTH256]; - - for (int i = 0; i < MAX_TERMS; ++i) - { - if (!*p) - { - parms[i] = lparms[i] = parm_defaults[i]; - parm_defaults[i][0] = '\0'; - continue; - } - - bool role_found = false; - TEXT* a = buffer; - int j = 0; - const bool quoted = *p == DBL_QUOTE || *p == SINGLE_QUOTE; - if (quoted) - { - if (i > 0 && (!strcmp(parms[i - 1], "ROLE"))) - role_found = true; - bool delimited_done = false; - const TEXT end_quote = *p; - j++; - *a++ = *p++; - // Allow a quoted string to have embedded spaces - // Prevent overflow - while (*p && !delimited_done && j < BUFFER_LENGTH256 - 1) - { - if (*p == end_quote) - { - j++; - *a++ = *p++; - if (*p && *p == end_quote && j < BUFFER_LENGTH256 - 1) - { - j++; // do not skip the escape quote here - *a++ = *p++; - } - else - delimited_done = true; - } - else - { - j++; - *a++ = *p++; - } - } - *a = '\0'; - } - else - { - // Prevent overflow. Do not copy the string (redundant). - while (*p && !fb_utils::isspace(*p) && j < BUFFER_LENGTH256 - 1) - { - j++; - ++p; - } - } - fb_assert(!quoted || strlen(buffer) == size_t(j)); - const size_t length = quoted ? strlen(buffer) : j; - parms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1)); - lparms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1)); - memcpy(parms[i], quoted ? buffer : p - j, length); - parms[i][length] = 0; - while (*p && fb_utils::isspace(*p)) - p++; - strcpy(lparms[i], parms[i]); - if (!role_found) - IUTILS_make_upper(parms[i]); - } -} - - -// *********************** -// f r o n t e n d _ s e t -// *********************** -// Validates and executes the SET {option {params}} command. -static processing_state frontend_set(const char* cmd, const char* const* parms, - const char* const* lparms, char* const bad_dialect_buf, bool& bad_dialect) -{ - - class SetOptions : public OptionsBase - { - public: - enum set_commands - { - stat, count, list, plan, planonly, explain, blobdisplay, echo, autoddl, - autoterm, width, transaction, terminator, names, time, - sqlda_display, - exec_path_display, - sql, warning, sqlCont, heading, bail, - bulk_insert, maxrows, stmtTimeout, - keepTranParams, perTableStats, wireStats, - wrong - }; - SetOptions(const optionsMap* inmap, size_t insize, int wrongval) - : OptionsBase(inmap, insize, wrongval) - {} - }; - - static const SetOptions::optionsMap options[] = - { - {SetOptions::stat, "STATS", 4}, - {SetOptions::count, "COUNT", 0}, - {SetOptions::list, "LIST", 0}, - {SetOptions::plan, "PLAN", 0}, - {SetOptions::planonly, "PLANONLY", 0}, - {SetOptions::explain, "EXPLAIN", 0}, - {SetOptions::blobdisplay, "BLOBDISPLAY", 4}, - {SetOptions::echo, "ECHO", 0}, - {SetOptions::autoddl, "AUTODDL", 4}, - {SetOptions::autoterm, "AUTOTERM", 5}, - {SetOptions::width, "WIDTH", 0}, - {SetOptions::transaction, "TRANSACTION", 5}, - {SetOptions::terminator, "TERMINATOR", 4}, - {SetOptions::names, "NAMES", 0}, - {SetOptions::time, "TIME", 0}, - {SetOptions::sqlda_display, "SQLDA_DISPLAY", 0}, - {SetOptions::exec_path_display, "EXEC_PATH_DISPLAY", 0}, - {SetOptions::sql, "SQL", 0}, - {SetOptions::warning, "WARNINGS", 7}, - {SetOptions::warning, "WNG", 0}, - {SetOptions::sqlCont, "GENERATOR", 0}, - {SetOptions::sqlCont, "STATISTICS", 0}, - {SetOptions::heading, "HEADING", 0}, - {SetOptions::bail, "BAIL", 0}, - {SetOptions::bulk_insert, "BULK_INSERT", 0}, - {SetOptions::maxrows, "ROWCOUNT", 0}, // legacy, compatible with FB2.5 - {SetOptions::maxrows, "MAXROWS", 0}, - {SetOptions::sqlCont, "ROLE", 0}, - {SetOptions::sqlCont, "TRUSTED", 0}, // TRUSTED ROLE, will get DSQL error other case - {SetOptions::stmtTimeout, "LOCAL_TIMEOUT", 0}, - {SetOptions::sqlCont, "DECFLOAT", 0}, - {SetOptions::keepTranParams, "KEEP_TRAN_PARAMS", 9}, - {SetOptions::perTableStats, "PER_TABLE_STATS", 7}, - {SetOptions::wireStats, "WIRE_STATS", 4} - }; - - // Display current set options - if (!*parms[1]) - return print_sets(); - - processing_state ret = SKIP; - const SetOptions setoptions(options, FB_NELEM(options), SetOptions::wrong); - switch (setoptions.getCommand(parms[1])) - { - case SetOptions::sqlCont: - ret = CONT; - break; - - case SetOptions::stat: - ret = do_set_command(parms[2], &setValues.Stats); - break; - - case SetOptions::count: - ret = do_set_command(parms[2], &setValues.Docount); - break; - - case SetOptions::list: - ret = do_set_command(parms[2], &setValues.List); - break; - - case SetOptions::plan: - ret = do_set_command(parms[2], &setValues.Plan); - if (setValues.Planonly && !setValues.Plan) - ret = do_set_command("OFF", &setValues.Planonly); - break; - - case SetOptions::planonly: - ret = do_set_command (parms[2], &setValues.Planonly); - if (setValues.Planonly && !setValues.Plan) - { - // turn on plan - ret = do_set_command ("ON", &setValues.Plan); - } - break; - - case SetOptions::explain: - ret = do_set_command(parms[2], &setValues.ExplainPlan); - if (setValues.ExplainPlan) - ret = do_set_command("ON", &setValues.Plan); - break; - - case SetOptions::blobdisplay: - // No arg means turn off blob display - if (!*parms[2] || !strcmp(parms[2], "OFF")) - setValues.Doblob = NO_BLOBS; - else if (!strcmp(parms[2], "ALL")) - setValues.Doblob = ALL_BLOBS; - else - setValues.Doblob = atoi(parms[2]); - break; - - case SetOptions::echo: - ret = do_set_command(parms[2], &setValues.Echo); - if (!setValues.Echo) - ISQL_prompt(""); - break; - - case SetOptions::autoddl: - ret = do_set_command(parms[2], &setValues.Autocommit); - break; - - case SetOptions::autoterm: - ret = do_set_command(parms[2], &setValues.AutoTerm); - if (setValues.AutoTerm) - { - isqlGlob.Termlen = 1; - strcpy(isqlGlob.global_Term, ";"); - } - break; - - case SetOptions::width: - ret = newsize(parms[2][0] == '"' ? lparms[2] : parms[2], parms[3]); - break; - - case SetOptions::transaction: - ret = newtrans(cmd); - break; - - case SetOptions::terminator: - { - const TEXT* a = (*lparms[2]) ? lparms[2] : DEFTERM; - for (size_t iter = 0; iter < sizeof(FORBIDDEN_TERM_CHARS); ++iter) - { - if (strchr(a, FORBIDDEN_TERM_CHARS[iter])) - { - TEXT msg_string[MSG_LENGTH]; - IUTILS_msg_get(INVALID_TERM_CHARS, msg_string, SafeArg() << FORBIDDEN_TERM_CHARS_DISPLAY); - isqlGlob.printf("%s\n", msg_string); - return ps_ERR; - } - } - - setValues.AutoTerm = false; - - isqlGlob.Termlen = strlen(a); - if (isqlGlob.Termlen < MAXTERM_SIZE) - { - strcpy(isqlGlob.global_Term, a); - } - else - { - isqlGlob.Termlen = MAXTERM_SIZE - 1; - fb_utils::copy_terminate(isqlGlob.global_Term, a, isqlGlob.Termlen + 1); - } - } - break; - - case SetOptions::names: - if (!*parms[2]) - { - const size_t lgth = strlen(DEFCHARSET); - if (lgth < MAXCHARSET_SIZE) - strcpy(setValues.ISQL_charset, DEFCHARSET); - else - fb_utils::copy_terminate(setValues.ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE); - } - else - { - const size_t lgth = strlen(parms[2]); - if (lgth < MAXCHARSET_SIZE) - strcpy(setValues.ISQL_charset, parms[2]); - else - fb_utils::copy_terminate(setValues.ISQL_charset, parms[2], MAXCHARSET_SIZE); - } - break; - - case SetOptions::time: - if (fb_utils::stricmp(parms[2], "ZONE") == 0) - ret = CONT; // pass SET TIME ZONE command to server - else - ret = do_set_command(parms[2], &setValues.Time_display); - break; - - case SetOptions::sqlda_display: - ret = do_set_command(parms[2], &setValues.Sqlda_display); - break; - - case SetOptions::exec_path_display: - ret = SKIP; - - if (!*parms[2]) - ret = ps_ERR; - else if (strcmp(parms[2], "OFF") == 0) - setValues.ExecPathDisplay[0] = 0; - else - { - Firebird::Array execPath; - - for (int parNum = 2; parNum < MAX_TERMS - 1 && *parms[parNum]; ++parNum) - { - const char* param = parms[parNum]; - UCHAR code; - - if (strcmp(param, "BLR") == 0) - code = isc_info_sql_exec_path_blr_text; - else - { - ret = ps_ERR; - break; - } - - if (execPath.exist(code)) - { - ret = ps_ERR; - break; - } - - execPath.push(code); - } - - if (ret != ps_ERR) - { - if (execPath.getCount() < sizeof(setValues.ExecPathDisplay)) - { - memcpy(setValues.ExecPathDisplay, execPath.begin(), execPath.getCount()); - setValues.ExecPathDisplay[execPath.getCount()] = 0; - } - else - ret = ps_ERR; - } - } - - if (ret != ps_ERR) - ret = execSetDebugCommand(); - - break; - - case SetOptions::sql: - if (!strcmp(parms[2], "DIALECT")) - ret = get_dialect(parms[3], bad_dialect_buf, bad_dialect); - else - ret = ps_ERR; - break; - - case SetOptions::warning: - ret = do_set_command (parms[2], &setValues.Warnings); - break; - - case SetOptions::heading: - ret = do_set_command(parms[2], &setValues.Heading); - break; - - case SetOptions::bail: - ret = do_set_command(parms[2], &setValues.BailOnError); - break; - - case SetOptions::bulk_insert: - if (*parms[2]) - ret = bulk_insert_hack(cmd); - else - ret = ps_ERR; - break; - - case SetOptions::maxrows: - ret = newMaxRows((*lparms[2]) ? lparms[2] : "0"); - break; - - case SetOptions::stmtTimeout: - { - int val = strtol(parms[2], NULL, 10); - if (val < 0) - ret = ps_ERR; - else - { - setValues.StmtTimeout = val; - ret = SKIP; - } - } - break; - - case SetOptions::keepTranParams: - { - const bool oldValue = setValues.KeepTranParams; - ret = do_set_command(parms[2], &setValues.KeepTranParams); - if ((ret != ps_ERR) && (oldValue != setValues.KeepTranParams)) - TranParams->assign(setValues.KeepTranParams ? DEFAULT_DML_TRANS_SQL : ""); - } - - break; - - case SetOptions::perTableStats: - ret = do_set_command(parms[2], &setValues.PerTableStats); - break; - - case SetOptions::wireStats: - ret = do_set_command(parms[2], &setValues.WireStats); - break; - - default: - //{ - // TEXT msg_string[MSG_LENGTH]; - // IUTILS_msg_get(VALID_OPTIONS, msg_string); - // isqlGlob.printf("%s\n", msg_string); - //} - //setoptions.showCommands(isqlGlob.Out); - //ret = ps_ERR; - ret = CONT; // pass unknown SET command to server as is - break; - } - - return ret; -} - - static processing_state do_set_command(const TEXT* parm, bool* global_flag) { /************************************** @@ -6721,7 +6297,7 @@ static bool printUser(const char* dbName) } -static processing_state newdb(TEXT* dbname, +static processing_state newdb(const TEXT* dbname, const TEXT* usr, const TEXT* psw, int numbufs, @@ -6755,14 +6331,16 @@ static processing_state newdb(TEXT* dbname, // out. We will restore it after the disconnect. The save_database buffer // will also be used to translate dbname to the proper character set. - const SLONG len = static_cast(chop_at(dbname, MAXPATHLEN)); + const SLONG len = strnlen(dbname, MAXPATHLEN - 1); SCHAR* save_database = (SCHAR*) ISQL_ALLOC(len + 1); if (!save_database) return ps_ERR; - strcpy(save_database, dbname); + strncpy(save_database, dbname, len); + save_database[len] = 0; ISQL_disconnect_database(false); - strcpy(dbname, save_database); + strcpy(isqlGlob.global_Db_name, save_database); + dbname = isqlGlob.global_Db_name; ISQL_FREE(save_database); TEXT local_psw[BUFFER_LENGTH256]; @@ -6775,12 +6353,10 @@ static processing_state newdb(TEXT* dbname, local_usr[0] = 0; local_sql_role[0] = 0; - // Strip quotes if well-intentioned - - strip_quotes(dbname, isqlGlob.global_Db_name); if (usr) strcpy(local_usr, usr); - strip_quotes(psw, local_psw); + if (psw) + strcpy(local_psw, psw); // if local user is not specified, see if global options are // specified - don't let a global role setting carry forward if a @@ -6964,20 +6540,17 @@ static processing_state newinput(const TEXT* infile) return ps_ERR; } - TEXT path[MAXPATHLEN]; - strip_quotes(infile, path); - PathName file; - if (PathUtils::isRelative(path)) + if (PathUtils::isRelative(infile)) { PathName newPath, temp; PathUtils::splitLastComponent(newPath, temp, Filelist->Ifp().fileName(false)); - PathUtils::concatPath(file, newPath, path); + PathUtils::concatPath(file, newPath, infile); } else - file = path; + file = infile; // filelist is a linked list of file pointers. We must add a node to // the linked list before discarding the current Ifp. @@ -6987,11 +6560,11 @@ static processing_state newinput(const TEXT* infile) if (fp) { Filelist->insertIfp(); - Filelist->Ifp().init(fp, file.c_str(), path); + Filelist->Ifp().init(fp, file.c_str(), infile); } else { - IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << path); + IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << infile); STDERROUT(errbuf); return FAIL; } @@ -7020,9 +6593,6 @@ static processing_state newoutput(const TEXT* outfile) if (*outfile) { - TEXT path[MAXPATHLEN]; - strip_quotes(outfile, path); - outfile = path; FILE* fp = os_utils::fopen(outfile, "a"); if (fp) { @@ -7071,18 +6641,7 @@ static processing_state newsize(const TEXT* colname, const TEXT* sizestr) * Add a column name and print width to collist * **************************************/ - if (!*colname || (strlen(colname) >= QUOTED_NAME_SIZE)) - return ps_ERR; - - char buf[QUOTED_NAME_SIZE]; - if (colname[0] == DBL_QUOTE) - { - strcpy(buf, colname); - IUTILS_remove_and_unescape_quotes(buf, DBL_QUOTE); - colname = buf; - } - - if (strlen(colname) > MAX_SQL_IDENTIFIER_LEN) + if (!*colname || strlen(colname) > MAX_SQL_IDENTIFIER_LEN) return ps_ERR; // If no size is given, remove the entry @@ -7601,25 +7160,6 @@ static processing_state parse_arg(int argc, SCHAR** argv, SCHAR* tabname) } -// ********************* -// p a s s t h r o u g h -// ********************* -// Execute a command directly on the server. No interpretation done in isql. -// Use with care. Only for debug builds. -#ifdef DEV_BUILD -static processing_state passthrough(const char* cmd) -{ - if (!DB) - return FAIL; - - M__trans = DB->execute(fbStatus, M__trans, 0, cmd, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL); - if (ISQL_errmsg(fbStatus)) - return ps_ERR; - - return SKIP; -} -#endif - static bool checkSpecial(TEXT* const p, const int length, const double value) { /************************************** diff --git a/src/isql/iutils.cpp b/src/isql/iutils.cpp index b5e3e276d1..09dc0a537f 100644 --- a/src/isql/iutils.cpp +++ b/src/isql/iutils.cpp @@ -37,6 +37,7 @@ #include "../common/utils_proto.h" #include +using namespace Firebird; using MsgFormat::SafeArg; @@ -148,6 +149,16 @@ void IUTILS_msg_get(USHORT number, USHORT size, TEXT* msg, const SafeArg& args) fb_msg_format(NULL, ISQL_MSG_FAC, number, size, msg, args); } + +string IUTILS_name_to_string(const MetaString& name) +{ + if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION) + return name.toQuotedString(); + else + return name.c_str(); +} + + void IUTILS_printf(FILE* fp, const char* buffer) { /************************************** diff --git a/src/isql/iutils_proto.h b/src/isql/iutils_proto.h index 4982e17c97..c61826081a 100644 --- a/src/isql/iutils_proto.h +++ b/src/isql/iutils_proto.h @@ -24,6 +24,7 @@ #ifndef ISQL_IUTILS_PROTO_H #define ISQL_IUTILS_PROTO_H +#include "../common/classes/MetaString.h" #include "../common/classes/SafeArg.h" #include @@ -33,6 +34,7 @@ void IUTILS_msg_get(USHORT number, TEXT* msg, const MsgFormat::SafeArg& args = MsgFormat::SafeArg()); void IUTILS_msg_get(USHORT number, USHORT size, TEXT* msg, const MsgFormat::SafeArg& args = MsgFormat::SafeArg()); +Firebird::string IUTILS_name_to_string(const Firebird::MetaString& name); void IUTILS_printf(FILE*, const char*); void IUTILS_printf2(FILE*, const char*, ...); void IUTILS_put_errmsg(USHORT number, const MsgFormat::SafeArg& args); diff --git a/src/isql/show.epp b/src/isql/show.epp index 83903e99a7..540f758a26 100644 --- a/src/isql/show.epp +++ b/src/isql/show.epp @@ -45,6 +45,7 @@ #include "../isql/isql.h" #include "../jrd/intl.h" #include "../common/intlobj_new.h" +#include "../common/StdHelper.h" #include "../common/classes/AlignedBuffer.h" #include "../common/classes/ClumpletReader.h" #include "../isql/isql_proto.h" @@ -68,8 +69,7 @@ #include #endif -using Firebird::string; -using Firebird::MetaString; +using namespace Firebird; using MsgFormat::SafeArg; @@ -83,7 +83,6 @@ DATABASE DB = EXTERN COMPILETIME "yachts.lnk" RUNTIME isqlGlob.global_Db_name; enum commentMode {cmmShow, cmmExtract}; -static void remove_delimited_double_quotes(TEXT*); static void make_priv_string(USHORT, char*, bool); static processing_state show_all_tables(SSHORT); static void show_charsets(SSHORT char_set_id, SSHORT collation); @@ -99,23 +98,23 @@ static processing_state show_dialect(); static processing_state show_domains(const SCHAR*); static processing_state show_exceptions(const SCHAR*); static processing_state show_filters(const SCHAR*); -static processing_state show_functions(const SCHAR* funcname, bool quoted, bool system, const SCHAR* msg = nullptr); +static processing_state show_functions( + const char* funcname, const char* packname, bool quoted, bool system, const char* msg = nullptr); static processing_state show_func_legacy(const MetaString&); static processing_state show_func(const MetaString&, const MetaString&); static processing_state show_generators(const SCHAR*); static void show_index(SCHAR*, SCHAR*, const SSHORT, const SSHORT, const SSHORT); -static processing_state show_indices(const SCHAR* const*); -static processing_state show_proc(const SCHAR*, bool, bool, const char* msg = nullptr); +static processing_state show_indices(const char*); +static processing_state show_proc(const char*, const char*, bool, bool, const char* msg = nullptr); static processing_state show_packages(const SCHAR* package_name, bool, const SCHAR* = NULL); static processing_state show_publications(const SCHAR* pub_name, bool, const SCHAR* = NULL); static void show_pub_table(const SCHAR* table_name); static processing_state show_role(const SCHAR*, bool, const char* msg = NULL); -static processing_state show_secclass(const char* object, const char* opt); +static processing_state show_secclass(const std::optional& object, bool detail); static processing_state show_table(const SCHAR*, bool); static processing_state show_trigger(const SCHAR*, bool, bool); static processing_state show_users(); static processing_state show_users12(); -static void parse_package(const char* procname, MetaString& package, MetaString& procedure); static processing_state show_wireStats(); const char* const spaces = " "; @@ -2067,7 +2066,7 @@ void SHOW_print_metadata_text_blob(FILE* fp, ISC_QUAD* blobid, bool escape_squot } -processing_state SHOW_metadata(const SCHAR* const* cmd, SCHAR** lcmd) +processing_state SHOW_metadata(const FrontendParser::AnyShowNode& node) { /************************************** * @@ -2078,752 +2077,720 @@ processing_state SHOW_metadata(const SCHAR* const* cmd, SCHAR** lcmd) * Functional description * If somebody presses the show ..., come here to * interpret the desired command. - * Paramters: - * cmd -- Array of words for the command * **************************************/ - class ShowOptions : public OptionsBase + static const OptionsBase::optionsMap options[] = { - public: - enum show_commands - { - role, table, view, system, index, domain, exception, - filter, function, generator, grant, procedure, trigger, - check, database, comment, dependency, collation, security_class, - users, package, publication, schema, map, wireStats, - wrong - }; - ShowOptions(const optionsMap* inmap, size_t insize, int wrongval) - : OptionsBase(inmap, insize, wrongval) - {} + {0, "ROLES", 4}, // ROLE + {0, "TABLES", 5}, // TABLE + {0, "VIEWS", 4}, // VIEW + {0, "SYSTEM", 3}, // SYS + {0, "INDEXES", 3}, // IND + {0, "INDICES", 0}, + {0, "DOMAINS", 6}, // DOMAIN + {0, "EXCEPTIONS", 5}, // EXCEPTION + {0, "FILTERS", 6}, // FILTER + {0, "FUNCTIONS", 4}, // FUNCTION + {0, "GENERATORS", 3}, // GEN + {0, "SEQUENCES", 3}, // SEQ + {0, "GRANTS", 5}, // GRANT + {0, "PROCEDURES", 4}, // PROC + {0, "TRIGGERS", 4}, // TRIG + {0, "CHECKS", 5}, // CHECK + {0, "DB", 0}, + {0, "DATABASE", 0}, + {0, "COMMENTS", 7}, // COMMENT + {0, "DEPENDENCY", 5}, // DEPEN + {0, "DEPENDENCIES", 5}, // DEPEN + {0, "COLLATES", 7}, // COLLATE + {0, "COLLATIONS", 9}, // COLLATION + {0, "SECCLASSES", 6}, // SECCLA + {0, "USERS", 0}, + {0, "PACKAGES", 4}, // PACK + ///{0, "SCHEMAS", 4}, // SCHE + {0, "MAPPINGS", 3}, // MAP + {0, "PUBLICATIONS", 3}, // PUB + {0, "WIRE_STATISTICS", 9}, // WIRE_STAT + {0, "WIRE_STATS", 10} }; - static const ShowOptions::optionsMap options[] = - { - //{role, "ROLE"}, - {ShowOptions::role, "ROLES", 4}, - //{table, "TABLE"}, - {ShowOptions::table, "TABLES", 5}, - //{view, "VIEW"}, - {ShowOptions::view, "VIEWS", 4}, - //{system, "SYS"}, - {ShowOptions::system, "SYSTEM", 3}, - //{index, "IND"}, - {ShowOptions::index, "INDEXES", 3}, - {ShowOptions::index, "INDICES", 0}, - //{domain, "DOMAIN"}, - {ShowOptions::domain, "DOMAINS", 6}, - //{exception, "EXCEPTION"}, - {ShowOptions::exception, "EXCEPTIONS", 5}, - //{filter, "FILTER"}, - {ShowOptions::filter, "FILTERS", 6}, - //{function, "FUNCTION"}, - {ShowOptions::function, "FUNCTIONS", 4}, - //{generator, "GEN"}, - //{generator, "GENERATOR"}, - {ShowOptions::generator, "GENERATORS", 3}, - //{generator, "SEQ"}, - //{generator, "SEQUENCE"}, - {ShowOptions::generator, "SEQUENCES", 3}, - //{grant, "GRANT"}, - {ShowOptions::grant, "GRANTS", 5}, - //{procedure, "PROC"}, - //{procedure, "PROCEDURE"}, - {ShowOptions::procedure, "PROCEDURES", 4}, - //{trigger, "TRIG"}, - //{trigger, "TRIGGER"}, - {ShowOptions::trigger, "TRIGGERS", 4}, - //{check, "CHECK"}, - {ShowOptions::check, "CHECKS", 5}, - {ShowOptions::database, "DB", 0}, - {ShowOptions::database, "DATABASE", 0}, - //{comment, "COMMENT"}, - {ShowOptions::comment, "COMMENTS", 7}, - {ShowOptions::dependency, "DEPENDENCY", 5}, - {ShowOptions::dependency, "DEPENDENCIES", 5}, - {ShowOptions::collation, "COLLATES", 7}, - {ShowOptions::collation, "COLLATIONS", 9}, - {ShowOptions::security_class, "SECURITY CLASSES", 12}, - {ShowOptions::security_class, "SECCLASSES", 6}, - {ShowOptions::users, "USERS", 0}, - {ShowOptions::package, "PACKAGES", 4}, - {ShowOptions::schema, "SCHEMAS", 4}, - {ShowOptions::map, "MAPPING", 3}, - {ShowOptions::publication, "PUBLICATIONS", 3}, - {ShowOptions::wireStats, "WIRE_STATISTICS", 9}, - {ShowOptions::wireStats, "WIRE_STATS", 10} - }; + const OptionsBase showoptions(options, FB_NELEM(options), 0); - const ShowOptions showoptions(options, FB_NELEM(options), ShowOptions::wrong); - - - // Can't show nothing, return an error - - if (!cmd[1] || !*cmd[1]) + if (!std::holds_alternative(node) && + !std::holds_alternative(node)) { - TEXT msg_string[MSG_LENGTH]; - IUTILS_msg_get(VALID_OPTIONS, msg_string); - isqlGlob.printf("%s\n", msg_string); - showoptions.showCommands(isqlGlob.Out); - return ps_ERR; - } - - processing_state ret = SKIP; - // Only show version and show sql dialect work if there is no db attached - bool handled = true; - if ((!strcmp(cmd[1], "VERSION")) || (!strcmp(cmd[1], "VER"))) - { - TEXT msg_string[MSG_LENGTH]; - IUTILS_msg_get(VERSION, msg_string, SafeArg() << FB_VERSION); - isqlGlob.printf("%s%s", msg_string, NEWLINE); - isqlGlob.printf("Server version:%s", NEWLINE); - VersionCallback callback; - Firebird::UtilInterfacePtr()->getFbVersion(fbStatus, DB, &callback); - if (fbStatus->getState() & Firebird::IStatus::STATE_ERRORS) - { - IUTILS_msg_get(CANNOT_GET_SRV_VER, msg_string); - STDERROUT(msg_string); - } - } - else if (!strcmp(cmd[1], "SQL")) - { - if (!strcmp(cmd[2], "DIALECT")) - ret = show_dialect(); - else - ret = ps_ERR; - } - else - { - handled = false; if (!ISQL_dbcheck()) - ret = ps_ERR; + return ps_ERR; } - if (ret == ps_ERR || handled) - return ret; - - TEXT SQL_id_for_grant[BUFFER_LENGTH256]; int key = 0; + MetaString notFoundName; - switch (showoptions.getCommand(cmd[1])) - { - case ShowOptions::role: - if (isqlGlob.major_ods >= ODS_VERSION9) + const auto ret = std::visit(StdVisitOverloads{ + [](const FrontendParser::InvalidNode&) { - if (*cmd[2]) + return CONT; + }, + + [&](const FrontendParser::ShowNode& node) + { + TEXT msg_string[MSG_LENGTH]; + IUTILS_msg_get(VALID_OPTIONS, msg_string); + isqlGlob.printf("%s\n", msg_string); + showoptions.showCommands(isqlGlob.Out); + return ps_ERR; + }, + + [&](const FrontendParser::ShowChecksNode& node) + { + const auto ret = show_check((node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) { - if (*cmd[2] == '"') + if (node.name) { - remove_delimited_double_quotes(lcmd[2]); - ret = show_role(lcmd[2], false); + FOR FIRST 1 R IN RDB$RELATIONS + WITH R.RDB$RELATION_NAME EQ node.name->c_str() + { + key = NO_CHECKS_ON_REL; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + + notFoundName = node.name.value(); + } + + if (!key) + key = NO_TABLE; + } + + return ret; + }, + + [&](const FrontendParser::ShowCollationsNode& node) + { + const auto ret = show_collations((node.name ? node.name->c_str() : ""), 0); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_COLLATION; + notFoundName = node.name.value(); + } + else + key = NO_COLLATIONS; + } + + return ret; + }, + + [&](const FrontendParser::ShowCommentsNode&) + { + const auto ret = show_comments(cmmShow, 0); + + if (ret == OBJECT_NOT_FOUND) + key = NO_COMMENTS; + + return ret; + }, + + [](const FrontendParser::ShowDatabaseNode&) + { + show_db(); + return SKIP; + }, + + [&](const FrontendParser::ShowDependenciesNode& node) + { + const auto ret = show_dependencies((node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) + key = NO_DEPENDENCIES; + + return ret; + }, + + [&](const FrontendParser::ShowDomainsNode& node) + { + const auto ret = show_domains((node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_DOMAIN; + notFoundName = node.name.value(); + } + else + key = NO_DOMAINS; + } + + return ret; + }, + + [&](const FrontendParser::ShowExceptionsNode& node) + { + const auto ret = show_exceptions((node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_EXCEPTION; + notFoundName = node.name.value(); + } + else + key = NO_EXCEPTIONS; + } + + return ret; + }, + + [&](const FrontendParser::ShowFiltersNode& node) + { + const auto ret = show_filters((node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_FILTER; + notFoundName = node.name.value(); + } + else + key = NO_FILTERS; + } + + return ret; + }, + + [&](const FrontendParser::ShowFunctionsNode& node) + { + const auto ret = show_functions( + (node.name ? node.name->c_str() : ""), + (node.package ? node.package->c_str() : ""), + false, false); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_FUNCTION; + notFoundName = node.name.value(); + } + else + key = NO_FUNCTIONS; + } + + return ret; + }, + + [&](const FrontendParser::ShowGeneratorsNode& node) + { + const auto ret = show_generators((node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_GEN; + notFoundName = node.name.value(); + } + else + key = NO_GENS; + } + + return ret; + }, + + [&](const FrontendParser::ShowGrantsNode& node) + { + processing_state ret; + + if (node.name) + ret = SHOW_grants(node.name->c_str(), "", obj_any); + else + ret = EXTRACT_list_grants(""); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + FOR FIRST 1 R IN RDB$RELATIONS + WITH R.RDB$RELATION_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_REL; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + + if (!key) + { + FOR FIRST 1 P IN RDB$PROCEDURES + WITH P.RDB$PROCEDURE_NAME EQ node.name->c_str() AND + P.RDB$PACKAGE_NAME MISSING + { + key = NO_GRANT_ON_PROC; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + { + FOR FIRST 1 R IN RDB$ROLES + WITH R.RDB$ROLE_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_ROL; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + { + FOR FIRST 1 F IN RDB$FUNCTIONS + WITH F.RDB$FUNCTION_NAME EQ node.name->c_str() AND + F.RDB$PACKAGE_NAME MISSING + { + key = NO_GRANT_ON_FUN; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + { + FOR FIRST 1 G IN RDB$GENERATORS + WITH G.RDB$GENERATOR_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_GEN; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + { + FOR FIRST 1 E IN RDB$EXCEPTIONS + WITH E.RDB$EXCEPTION_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_XCP; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + { + FOR FIRST 1 F IN RDB$FIELDS + WITH F.RDB$FIELD_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_FLD; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + { + FOR FIRST 1 CS IN RDB$CHARACTER_SETS + WITH CS.RDB$CHARACTER_SET_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_CS; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + { + FOR FIRST 1 C IN RDB$COLLATIONS + WITH C.RDB$COLLATION_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_COLL; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key && isqlGlob.major_ods >= ODS_VERSION12) + { + FOR FIRST 1 P IN RDB$PACKAGES + WITH P.RDB$PACKAGE_NAME EQ node.name->c_str() + { + key = NO_GRANT_ON_PKG; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + } + + if (!key) + key = NO_OBJECT; + + notFoundName = node.name.value(); + } + else + key = NO_GRANT_ON_ANY; + } + + return ret; + }, + + [&](const FrontendParser::ShowIndexesNode& node) + { + const auto ret = show_indices((node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + FOR FIRST 1 R IN RDB$RELATIONS + WITH R.RDB$RELATION_NAME EQ node.name->c_str() + { + key = NO_INDICES_ON_REL; + } + END_FOR + ON_ERROR + // Ignore any error + END_ERROR + + notFoundName = node.name.value(); + + if (!key) + key = NO_REL_OR_INDEX; + } + else + key = NO_INDICES; + } + + return ret; + }, + + [&](const FrontendParser::ShowMappingsNode& node) + { + const auto ret = SHOW_maps(false, (node.name ? node.name->c_str() : "")); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_MAP; + notFoundName = node.name.value(); + } + else + key = NO_MAPS; + } + + return ret; + }, + + [&](const FrontendParser::ShowPackagesNode& node) + { + const auto ret = show_packages((node.name ? node.name->c_str() : ""), false); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_PACKAGE; + notFoundName = node.name.value(); + } + else + key = NO_PACKAGES; + } + + return ret; + }, + + [&](const FrontendParser::ShowProceduresNode& node) + { + const auto ret = show_proc( + (node.name ? node.name->c_str() : ""), + (node.package ? node.package->c_str() : ""), + false, false); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_PROC; + notFoundName = node.name.value(); + } + else + key = NO_PROCS; + } + + return ret; + }, + + [&](const FrontendParser::ShowPublicationsNode& node) + { + const auto ret = show_publications((node.name ? node.name->c_str() : ""), false); + + if (ret == OBJECT_NOT_FOUND) + { + if (node.name) + { + key = NO_PUBLICATION; + notFoundName = node.name.value(); + } + else + key = NO_PUBLICATIONS; + } + + return ret; + }, + + [&](const FrontendParser::ShowRolesNode& node) + { + processing_state ret; + + if (isqlGlob.major_ods >= ODS_VERSION9) + { + if (node.name) + { + ret = show_role(node.name->c_str(), false); + + if (ret == OBJECT_NOT_FOUND) + { + key = NO_ROLE; + notFoundName = node.name.value(); + } } else { - ret = show_role(cmd[2], false); + ret = show_role(nullptr, false); + if (ret == OBJECT_NOT_FOUND) + key = NO_ROLES; } - - if (ret == OBJECT_NOT_FOUND) - key = NO_ROLE; } else { - ret = show_role(NULL, false); - if (ret == OBJECT_NOT_FOUND) - key = NO_ROLES; + ret = OBJECT_NOT_FOUND; + key = NO_ROLES; } - } - else - { - ret = OBJECT_NOT_FOUND; - key = NO_ROLES; - } - break; - case ShowOptions::table: - if (*cmd[2]) + return ret; + }, + + [&](const FrontendParser::ShowSecClassesNode& node) { - if (*cmd[2] == '"') + const auto ret = show_secclass(node.name, node.detail); + + if (ret == OBJECT_NOT_FOUND) { - remove_delimited_double_quotes(lcmd[2]); - ret = show_table(lcmd[2], false); + if (node.name) + { + key = NO_SECCLASS; + notFoundName = node.name.value(); + } + else + key = NO_DB_WIDE_SECCLASS; + } + + return ret; + }, + + [](const FrontendParser::ShowSqlDialectNode& node) + { + return show_dialect(); + }, + + [](const FrontendParser::ShowSystemNode& node) + { + if (node.objType) + { + switch (node.objType.value()) + { + case obj_collation: + show_collations("", 1); + break; + + case obj_udf: + show_functions(nullptr, nullptr, false, true); + break; + + case obj_relation: + show_all_tables(1); + break; + + case obj_sql_role: + show_role(nullptr, true); + break; + + case obj_procedure: + show_proc(nullptr, nullptr, false, true); + break; + + case obj_package_header: + show_packages(nullptr, true); + break; + + case obj_publication: + show_publications(nullptr, true); + break; + + default: + return ps_ERR; + } } else { - ret = show_table(cmd[2], false); - } - - if (ret == OBJECT_NOT_FOUND) - key = NO_TABLE; - } - else - { - ret = show_all_tables(0); - if (ret == OBJECT_NOT_FOUND) - key = NO_TABLES; - } - break; - - case ShowOptions::view: - if (*cmd[2]) - { - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_table(lcmd[2], true); - } - else - { - ret = show_table(cmd[2], true); - } - - if (ret == OBJECT_NOT_FOUND) - key = NO_VIEW; - } - else - { - ret = show_all_tables(-1); - if (ret == OBJECT_NOT_FOUND) - key = NO_VIEWS; - } - break; - - case ShowOptions::system: - if (*cmd[2]) - { - switch (showoptions.getCommand(cmd[2])) - { - case ShowOptions::collation: - show_collations("", 1); - break; - - case ShowOptions::function: - show_functions(nullptr, false, true); - break; - - case ShowOptions::table: + TEXT msg[MSG_LENGTH]; + IUTILS_msg_get(MSG_TABLES, msg); + isqlGlob.printf("%s%s", msg, NEWLINE); show_all_tables(1); - break; - - case ShowOptions::role: - show_role(NULL, true); - break; - - case ShowOptions::procedure: - show_proc(NULL, false, true); - break; - - case ShowOptions::package: - show_packages(NULL, true); - break; - - case ShowOptions::publication: - show_publications(NULL, true); - break; - - default: - return ps_ERR; + IUTILS_msg_get(MSG_FUNCTIONS, msg); + show_functions(nullptr, nullptr, false, true, msg); + IUTILS_msg_get(MSG_PROCEDURES, msg); + show_proc(nullptr, nullptr, false, true, msg); + IUTILS_msg_get(MSG_PACKAGES, msg); + show_packages(nullptr, true, msg); + IUTILS_msg_get(MSG_COLLATIONS, msg); + show_collations("", 1, msg, true); + IUTILS_msg_get(MSG_ROLES, msg); + show_role(nullptr, true, msg); + IUTILS_msg_get(MSG_PUBLICATIONS, msg); + show_publications(nullptr, true, msg); } - } - else - { - TEXT msg[MSG_LENGTH]; - IUTILS_msg_get(MSG_TABLES, msg); - isqlGlob.printf("%s%s", msg, NEWLINE); - show_all_tables(1); - IUTILS_msg_get(MSG_FUNCTIONS, msg); - show_functions(nullptr, false, true, msg); - IUTILS_msg_get(MSG_PROCEDURES, msg); - show_proc(NULL, false, true, msg); - IUTILS_msg_get(MSG_PACKAGES, msg); - show_packages(NULL, true, msg); - IUTILS_msg_get(MSG_COLLATIONS, msg); - show_collations("", 1, msg, true); - IUTILS_msg_get(MSG_ROLES, msg); - show_role(NULL, true, msg); - IUTILS_msg_get(MSG_PUBLICATIONS, msg); - show_publications(NULL, true, msg); - } - break; - case ShowOptions::index: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_indices(lcmd); - } - else - { - ret = show_indices(cmd); - } + return SKIP; + }, - if (ret == OBJECT_NOT_FOUND) + [&](const FrontendParser::ShowTablesNode& node) { - if (*cmd[2]) + processing_state ret; + + if (node.name) { - FOR FIRST 1 R IN RDB$RELATIONS - WITH R.RDB$RELATION_NAME EQ cmd[2] + ret = show_table(node.name->c_str(), false); - key = NO_INDICES_ON_REL; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - if (!key) - key = NO_REL_OR_INDEX; + if (ret == OBJECT_NOT_FOUND) + { + key = NO_TABLE; + notFoundName = node.name.value(); + } } else { - key = NO_INDICES; + ret = show_all_tables(0); + if (ret == OBJECT_NOT_FOUND) + key = NO_TABLES; } - } - break; - case ShowOptions::domain: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_domains(lcmd[2]); - } - else - ret = show_domains(cmd[2]); + return ret; + }, - if (ret == OBJECT_NOT_FOUND) + [&](const FrontendParser::ShowTriggersNode& node) { - if (*cmd[2]) - key = NO_DOMAIN; - else - key = NO_DOMAINS; - } - break; + const auto ret = show_trigger((node.name ? node.name->c_str() : ""), true, true); - case ShowOptions::exception: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_exceptions(lcmd[2]); - } - else - ret = show_exceptions(cmd[2]); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) - key = NO_EXCEPTION; - else - key = NO_EXCEPTIONS; - } - break; - - case ShowOptions::filter: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_filters(lcmd[2]); - } - else - ret = show_filters(cmd[2]); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) - key = NO_FILTER; - else - key = NO_FILTERS; - } - break; - - case ShowOptions::function: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_functions(lcmd[2], true, false); - } - else - ret = show_functions(cmd[2], false, false); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) - key = NO_FUNCTION; - else - key = NO_FUNCTIONS; - } - break; - - case ShowOptions::generator: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_generators(lcmd[2]); - } - else - ret = show_generators(cmd[2]); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) - key = NO_GEN; - else - key = NO_GENS; - } - break; - - case ShowOptions::grant: - if (*cmd[2]) - { - if (*cmd[2] == '"') + if (ret == OBJECT_NOT_FOUND) { - remove_delimited_double_quotes(lcmd[2]); - strcpy(SQL_id_for_grant, lcmd[2]); + if (node.name) + { + key = NO_TRIGGER; + notFoundName = node.name.value(); + } + else + key = NO_TRIGGERS; } - else - strcpy(SQL_id_for_grant, cmd[2]); - ret = SHOW_grants(SQL_id_for_grant, "", obj_any); - } - else - { - strcpy(SQL_id_for_grant, cmd[2]); - ret = EXTRACT_list_grants(""); - } - if (ret == OBJECT_NOT_FOUND) + return ret; + }, + + [&](const FrontendParser::ShowUsersNode& node) { - if (*cmd[2]) + const auto ret = show_users(); + + if (ret == OBJECT_NOT_FOUND) // It seems impossible, but... + key = NO_CONNECTED_USERS; + + return ret; + }, + + [](const FrontendParser::ShowVersionNode&) + { + TEXT msg_string[MSG_LENGTH]; + IUTILS_msg_get(VERSION, msg_string, SafeArg() << FB_VERSION); + isqlGlob.printf("%s%s", msg_string, NEWLINE); + isqlGlob.printf("Server version:%s", NEWLINE); + VersionCallback callback; + UtilInterfacePtr()->getFbVersion(fbStatus, DB, &callback); + if (fbStatus->getState() & IStatus::STATE_ERRORS) { - FOR FIRST 1 R IN RDB$RELATIONS - WITH R.RDB$RELATION_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_REL; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - if (!key) - { - FOR FIRST 1 P IN RDB$PROCEDURES - WITH P.RDB$PROCEDURE_NAME EQ SQL_id_for_grant AND - P.RDB$PACKAGE_NAME MISSING - - key = NO_GRANT_ON_PROC; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - { - FOR FIRST 1 R IN RDB$ROLES - WITH R.RDB$ROLE_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_ROL; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - { - FOR FIRST 1 F IN RDB$FUNCTIONS - WITH F.RDB$FUNCTION_NAME EQ SQL_id_for_grant AND - F.RDB$PACKAGE_NAME MISSING - - key = NO_GRANT_ON_FUN; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - { - FOR FIRST 1 G IN RDB$GENERATORS - WITH G.RDB$GENERATOR_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_GEN; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - { - FOR FIRST 1 E IN RDB$EXCEPTIONS - WITH E.RDB$EXCEPTION_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_XCP; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - { - FOR FIRST 1 F IN RDB$FIELDS - WITH F.RDB$FIELD_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_FLD; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - { - FOR FIRST 1 CS IN RDB$CHARACTER_SETS - WITH CS.RDB$CHARACTER_SET_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_CS; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - { - FOR FIRST 1 C IN RDB$COLLATIONS - WITH C.RDB$COLLATION_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_COLL; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key && isqlGlob.major_ods >= ODS_VERSION12) - { - FOR FIRST 1 P IN RDB$PACKAGES - WITH P.RDB$PACKAGE_NAME EQ SQL_id_for_grant - - key = NO_GRANT_ON_PKG; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - } - if (!key) - key = NO_OBJECT; + IUTILS_msg_get(CANNOT_GET_SRV_VER, msg_string); + STDERROUT(msg_string); } - else { - key = NO_GRANT_ON_ANY; - } - } - break; - case ShowOptions::procedure: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_proc(lcmd[2], true, false); - } - else - ret = show_proc(cmd[2], false, false); + return SKIP; + }, - if (ret == OBJECT_NOT_FOUND) + [&](const FrontendParser::ShowViewsNode& node) { - if (*cmd[2]) - key = NO_PROC; - else - key = NO_PROCS; - } - break; + processing_state ret; - case ShowOptions::trigger: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_trigger(lcmd[2], true, true); - } - else - ret = show_trigger(cmd[2], true, true); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) + if (node.name) { - /* - FOR FIRST 1 R IN RDB$RELATIONS - WITH R.RDB$RELATION_NAME EQ cmd[2] + ret = show_table(node.name->c_str(), true); - key = NO_TRIGGERS_ON_REL; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; - if (!key) - key = NO_REL_OR_TRIGGER; - */ - key = NO_TRIGGER; + if (ret == OBJECT_NOT_FOUND) + { + key = NO_VIEW; + notFoundName = node.name.value(); + } } else - key = NO_TRIGGERS; - } - break; - - case ShowOptions::check: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_check(lcmd[2]); - } - else - ret = show_check(cmd[2]); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) { - FOR FIRST 1 R IN RDB$RELATIONS - WITH R.RDB$RELATION_NAME EQ cmd[2] - - key = NO_CHECKS_ON_REL; - END_FOR - ON_ERROR - // Ignore any error - END_ERROR; + ret = show_all_tables(-1); + if (ret == OBJECT_NOT_FOUND) + key = NO_VIEWS; } - if (!key) - key = NO_TABLE; - } - break; - case ShowOptions::database: - show_db(); - break; + return ret; + }, - case ShowOptions::comment: - ret = show_comments(cmmShow, 0); - if (ret == OBJECT_NOT_FOUND) - key = NO_COMMENTS; - break; - - case ShowOptions::collation: - if (*cmd[2] == '"') + [](const FrontendParser::ShowWireStatsNode&) { - remove_delimited_double_quotes(lcmd[2]); - ret = show_collations(lcmd[2], -1); - } - else - ret = show_collations(cmd[2], 0); + return show_wireStats(); + }, - if (ret == OBJECT_NOT_FOUND) + [](auto& arg) { - if (*cmd[2]) - key = NO_COLLATION; - else - key = NO_COLLATIONS; + static_assert(FrontendParser::AlwaysFalseV, + "Add visitor method for that show node type"); } - break; - - case ShowOptions::dependency: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_dependencies(lcmd[2]); - } - else - ret = show_dependencies(cmd[2]); - - if (ret == OBJECT_NOT_FOUND) - key = NO_DEPENDENCIES; - break; - - case ShowOptions::security_class: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_secclass(lcmd[2], cmd[3]); - } - else - ret = show_secclass(cmd[2], cmd[3]); - - if (ret == OBJECT_NOT_FOUND) - { - if (!strcmp(cmd[2], "*")) - key = NO_DB_WIDE_SECCLASS; - else - key = NO_SECCLASS; - } - break; - - case ShowOptions::users: - ret = show_users(); - if (ret == OBJECT_NOT_FOUND) // It seems impossible, but... - key = NO_CONNECTED_USERS; - break; - - case ShowOptions::package: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_packages(lcmd[2], false); - } - else - ret = show_packages(cmd[2], false); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) - key = NO_PACKAGE; - else - key = NO_PACKAGES; - } - break; - - case ShowOptions::map: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = SHOW_maps(false, lcmd[2]); - } - else - ret = SHOW_maps(false, cmd[2]); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) - key = NO_MAP; - else - key = NO_MAPS; - } - break; - - case ShowOptions::publication: - if (*cmd[2] == '"') - { - remove_delimited_double_quotes(lcmd[2]); - ret = show_publications(lcmd[2], false); - } - else - ret = show_publications(cmd[2], false); - - if (ret == OBJECT_NOT_FOUND) - { - if (*cmd[2]) - key = NO_PUBLICATION; - else - key = NO_PUBLICATIONS; - } - break; - - case ShowOptions::schema: - return ps_ERR; - break; - - case ShowOptions::wireStats: - ret = show_wireStats(); - break; - - default: - return ps_ERR; - } // switch + }, node); if (ret == OBJECT_NOT_FOUND) { TEXT key_string[MSG_LENGTH]; - if (*cmd[2] == '"') - IUTILS_msg_get(key, key_string, SafeArg() << lcmd[2]); - else - IUTILS_msg_get(key, key_string, SafeArg() << cmd[2]); + IUTILS_msg_get(key, key_string, SafeArg() << notFoundName.c_str()); STDERROUT(key_string); } @@ -2831,23 +2798,6 @@ processing_state SHOW_metadata(const SCHAR* const* cmd, SCHAR** lcmd) } -static void remove_delimited_double_quotes(TEXT* string) -{ -/************************************** - * - * r e m o v e _ d e l i m i t e d _ d o u b l e _ q u o t e s - * - ************************************** - * - * Functional description - * Remove the delimited double quotes. Blanks could be part of - * delimited SQL identifier. Unescape embedded double quotes. - * - **************************************/ - IUTILS_remove_and_unescape_quotes(string, DBL_QUOTE); -} - - static void make_priv_string(USHORT flags, char* string, bool useAny) { /************************************** @@ -4267,7 +4217,8 @@ static processing_state show_filters(const SCHAR* object) } -static processing_state show_functions(const SCHAR* funcname, bool quoted, bool system, const SCHAR* msg) +static processing_state show_functions( + const char* funcname, const char* packname, bool quoted, bool system, const char* msg) { /************************************** * @@ -4352,14 +4303,11 @@ static processing_state show_functions(const SCHAR* funcname, bool quoted, bool processing_state return_state = OBJECT_NOT_FOUND; - MetaString package, function; - if (quoted) - function = funcname; - else - parse_package(funcname, package, function); + const MetaString function(funcname); + const MetaString package(packname); FOR FUN IN RDB$FUNCTIONS - WITH FUN.RDB$FUNCTION_NAME EQ function.c_str() + WITH FUN.RDB$FUNCTION_NAME EQ funcname AND FUN.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') if (!FUN.RDB$MODULE_NAME.NULL) @@ -4838,7 +4786,7 @@ static void show_index(SCHAR* relation_name, } -static processing_state show_indices(const SCHAR* const* cmd) +static processing_state show_indices(const char* name) { /************************************** * @@ -4858,8 +4806,6 @@ static processing_state show_indices(const SCHAR* const* cmd) // The names stored in the database are all upper case - const SCHAR* name = cmd[2]; - if (*name) { FOR IDX IN RDB$INDICES WITH @@ -5187,7 +5133,7 @@ processing_state SHOW_maps(bool extract, const SCHAR* map_name) } -static processing_state show_proc(const SCHAR* procname, bool quoted, bool sys, const char* msg) +static processing_state show_proc(const char* procname, const char* packname, bool quoted, bool sys, const char* msg) { /************************************** * @@ -5282,11 +5228,9 @@ static processing_state show_proc(const SCHAR* procname, bool quoted, bool sys, // A procedure was named, so print all the info on that procedure bool first = true; - MetaString package, procedure; - if (quoted) - procedure = procname; - else - parse_package(procname, package, procedure); + + const MetaString procedure(procname); + const MetaString package(packname); FOR PRC IN RDB$PROCEDURES WITH PRC.RDB$PROCEDURE_NAME EQ procedure.c_str() AND @@ -5463,23 +5407,6 @@ static processing_state show_proc(const SCHAR* procname, bool quoted, bool sys, } -static void parse_package(const char* procname, MetaString& package, MetaString& procedure) -{ - for (const char* point = procname; *point; ++point) - { - if (*point == '.') - { - package.assign(procname, point - procname); - procedure = ++point; - return; - } - } - - package = ""; - procedure = procname; -} - - static processing_state show_publications(const SCHAR* pub_name, bool sys, const SCHAR* msg) { /************************************** @@ -5783,21 +5710,15 @@ static processing_state show_role(const SCHAR* object, bool system, const char* // Show low-level, GDML security for an object. It may be table/view or procedure. // Using SHOW SECCLASS DET[AIL] will print the contents of the sec blob. // Using SHOW SECCLASS * DET[AIL] will print the db-wide sec class in rdb$database. -static processing_state show_secclass(const char* object, const char* opt) +static processing_state show_secclass(const std::optional& object, bool detail) { - if (!object || !*object) - return ps_ERR; - - const bool detail = opt && - (fb_utils::stricmp(opt, "DETAIL") == 0 || - fb_utils::stricmp(opt, "DET") == 0); IsqlVar var; memset(&var, 0, sizeof(var)); var.subType = isc_blob_acl; int count = 0; - if (strcmp(object, "*") == 0) + if (!object) { FOR D IN RDB$DATABASE CROSS SC IN RDB$SECURITY_CLASSES @@ -5822,7 +5743,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR REL IN RDB$RELATIONS CROSS SC IN RDB$SECURITY_CLASSES OVER RDB$SECURITY_CLASS - WITH REL.RDB$RELATION_NAME EQ object + WITH REL.RDB$RELATION_NAME EQ object->c_str() ++count; isqlGlob.printf("%s's main sec class %s%s", REL.RDB$VIEW_BLR.NULL ? "Table" : "View", @@ -5840,7 +5761,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR REL2 IN RDB$RELATIONS CROSS SC IN RDB$SECURITY_CLASSES - WITH REL2.RDB$RELATION_NAME EQ object + WITH REL2.RDB$RELATION_NAME EQ object->c_str() AND REL2.RDB$DEFAULT_CLASS EQ SC.RDB$SECURITY_CLASS ++count; isqlGlob.printf("%s's default sec class %s%s", @@ -5860,7 +5781,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR RF IN RDB$RELATION_FIELDS CROSS SC IN RDB$SECURITY_CLASSES OVER RDB$SECURITY_CLASS - WITH RF.RDB$RELATION_NAME EQ object + WITH RF.RDB$RELATION_NAME EQ object->c_str() SORTED BY RF.RDB$FIELD_POSITION ++count; isqlGlob.printf(" Field %s - sec class %s%s", fb_utils::exact_name(RF.RDB$FIELD_NAME), @@ -5879,7 +5800,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR PR IN RDB$PROCEDURES CROSS SC IN RDB$SECURITY_CLASSES OVER RDB$SECURITY_CLASS - WITH PR.RDB$PROCEDURE_NAME EQ object AND + WITH PR.RDB$PROCEDURE_NAME EQ object->c_str() AND PR.RDB$PACKAGE_NAME MISSING ++count; isqlGlob.printf("Procedure's sec class %s%s", @@ -5900,7 +5821,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR FUN IN RDB$FUNCTIONS CROSS SC IN RDB$SECURITY_CLASSES OVER RDB$SECURITY_CLASS - WITH FUN.RDB$FUNCTION_NAME EQ object AND + WITH FUN.RDB$FUNCTION_NAME EQ object->c_str() AND FUN.RDB$PACKAGE_NAME MISSING ++count; isqlGlob.printf("Function's sec class %s%s", @@ -5919,7 +5840,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR PKG IN RDB$PACKAGES CROSS SC IN RDB$SECURITY_CLASSES OVER RDB$SECURITY_CLASS - WITH PKG.RDB$PACKAGE_NAME EQ object + WITH PKG.RDB$PACKAGE_NAME EQ object->c_str() ++count; isqlGlob.printf("Package's sec class %s%s", fb_utils::exact_name(SC.RDB$SECURITY_CLASS), NEWLINE); @@ -5937,7 +5858,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR GEN IN RDB$GENERATORS CROSS SC IN RDB$SECURITY_CLASSES OVER RDB$SECURITY_CLASS - WITH GEN.RDB$GENERATOR_NAME EQ object + WITH GEN.RDB$GENERATOR_NAME EQ object->c_str() ++count; isqlGlob.printf("Sequence's sec class %s%s", fb_utils::exact_name(SC.RDB$SECURITY_CLASS), NEWLINE); @@ -5955,7 +5876,7 @@ static processing_state show_secclass(const char* object, const char* opt) FOR XCP IN RDB$EXCEPTIONS CROSS SC IN RDB$SECURITY_CLASSES OVER RDB$SECURITY_CLASS - WITH XCP.RDB$EXCEPTION_NAME EQ object + WITH XCP.RDB$EXCEPTION_NAME EQ object->c_str() ++count; isqlGlob.printf("Exception's sec class %s%s", fb_utils::exact_name(SC.RDB$SECURITY_CLASS), NEWLINE); diff --git a/src/isql/show_proto.h b/src/isql/show_proto.h index 169131e863..bbbe704e8e 100644 --- a/src/isql/show_proto.h +++ b/src/isql/show_proto.h @@ -26,6 +26,7 @@ #include "../common/classes/fb_string.h" #include +#include "../isql/FrontendParser.h" #include "../jrd/obj.h" void SHOW_comments(bool force); @@ -36,7 +37,7 @@ void SHOW_grant_roles (const SCHAR*, bool*); void SHOW_grant_roles2 (const SCHAR*, bool*, const TEXT*, bool); void SHOW_print_metadata_text_blob(FILE*, ISC_QUAD*, bool escape_squote = false, bool avoid_end_in_single_line_comment = false); -processing_state SHOW_metadata(const SCHAR* const*, SCHAR**); +processing_state SHOW_metadata(const FrontendParser::AnyShowNode& node); void SHOW_read_owner(); const Firebird::string SHOW_trigger_action(SINT64); processing_state SHOW_maps(bool extract, const SCHAR* map_name); diff --git a/src/isql/tests/FrontendParserTest.cpp b/src/isql/tests/FrontendParserTest.cpp new file mode 100644 index 0000000000..61a571dfdb --- /dev/null +++ b/src/isql/tests/FrontendParserTest.cpp @@ -0,0 +1,621 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2024 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + */ + +#include "firebird.h" +#include "boost/test/unit_test.hpp" +#include "../FrontendParser.h" +#include + +using namespace Firebird; + +BOOST_AUTO_TEST_SUITE(ISqlSuite) +BOOST_AUTO_TEST_SUITE(FrontendParserSuite) +BOOST_AUTO_TEST_SUITE(FrontendParserTests) + + +BOOST_AUTO_TEST_CASE(ParseCommandTest) +{ + const FrontendParser::Options parserOptions; + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "add", parserOptions))); + BOOST_TEST((std::get(FrontendParser::parse( + "add table1", parserOptions)).tableName == "TABLE1")); + BOOST_TEST((std::get(FrontendParser::parse( + "add \"table2\"", parserOptions)).tableName == "table2")); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "blobdump", parserOptions))); + + { + const auto blobDump1 = std::get(FrontendParser::parse( + "blobdump 1:2 /tmp/blob.txt", parserOptions)); + BOOST_TEST(blobDump1.blobId.gds_quad_high == 1); + BOOST_TEST(blobDump1.blobId.gds_quad_low == 2u); + BOOST_TEST(blobDump1.file.value() == "/tmp/blob.txt"); + + const auto blobDump2 = std::get(FrontendParser::parse( + "blobdump 1:2 'C:\\A dir\\blob.txt'", parserOptions)); + BOOST_TEST((blobDump2.file.value() == "C:\\A dir\\blob.txt")); + } + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "blobview", parserOptions))); + + { + const auto blobView1 = std::get(FrontendParser::parse( + "blobview 1:2", parserOptions)); + BOOST_TEST(blobView1.blobId.gds_quad_high == 1); + BOOST_TEST(blobView1.blobId.gds_quad_low == 2u); + BOOST_TEST(!blobView1.file); + } + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "connect", parserOptions))); + + { + const auto connect1 = std::get(FrontendParser::parse( + "connect 'test.fdb'", parserOptions)); + BOOST_TEST(connect1.args[0].getProcessedString() == "test.fdb"); + + const auto connect2 = std::get(FrontendParser::parse( + "connect 'test.fdb' user user", parserOptions)); + BOOST_TEST(connect2.args.size() == 3u); + } + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "copy", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "copy source destination", parserOptions))); + + { + const auto copy1 = std::get(FrontendParser::parse( + "copy source \"destination\" localhost:/tmp/database.fdb", parserOptions)); + BOOST_TEST((copy1.source == "SOURCE")); + BOOST_TEST((copy1.destination == "destination")); + BOOST_TEST(copy1.database == "localhost:/tmp/database.fdb"); + } + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "create", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "create database", parserOptions))); + + { + const auto createDatabase1 = std::get(FrontendParser::parse( + "create database 'test.fdb'", parserOptions)); + BOOST_TEST(createDatabase1.args[0].getProcessedString() == "test.fdb"); + + const auto createDatabase2 = std::get(FrontendParser::parse( + "create database 'test.fdb' user user", parserOptions)); + BOOST_TEST(createDatabase2.args.size() == 3u); + } + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "drop database x", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "drop database", parserOptions))); + + BOOST_TEST(!std::get(FrontendParser::parse( + "edit", parserOptions)).file); + BOOST_TEST(std::get(FrontendParser::parse( + "edit /tmp/file.sql", parserOptions)).file.value() == "/tmp/file.sql"); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "exit x", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "exit", parserOptions))); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "explain", parserOptions))); + BOOST_TEST(std::get(FrontendParser::parse( + "explain select 1 from rdb$database", parserOptions)).query == "select 1 from rdb$database"); + + BOOST_TEST(!std::get(FrontendParser::parse( + "help", parserOptions)).command.has_value()); + BOOST_TEST(std::get(FrontendParser::parse( + "help set", parserOptions)).command.value() == "SET"); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "help set x", parserOptions))); + + BOOST_TEST(!std::get(FrontendParser::parse( + "?", parserOptions)).command.has_value()); + BOOST_TEST(std::get(FrontendParser::parse( + "? set", parserOptions)).command.value() == "SET"); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "? set x", parserOptions))); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "input", parserOptions))); + BOOST_TEST(std::get(FrontendParser::parse( + "input /tmp/file.sql", parserOptions)).file == "/tmp/file.sql"); + + BOOST_TEST(!std::get(FrontendParser::parse( + "output", parserOptions)).file); + BOOST_TEST(std::get(FrontendParser::parse( + "output /tmp/file.txt", parserOptions)).file.value() == "/tmp/file.txt"); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "quit", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "quit x", parserOptions))); + + BOOST_TEST(!std::get(FrontendParser::parse( + "shell", parserOptions)).command); + BOOST_TEST(std::get(FrontendParser::parse( + "shell ls -l /tmp", parserOptions)).command.value() == "ls -l /tmp"); +} + +BOOST_AUTO_TEST_CASE(ParseSetTest) +{ + const FrontendParser::Options parserOptions; + + const auto parseSet = [&](const std::string_view text) { + return std::get(FrontendParser::parse(text, parserOptions)); + }; + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "\"set\"", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set x", parserOptions))); + + BOOST_TEST(std::holds_alternative(parseSet("set"))); + + BOOST_TEST(std::get(parseSet( + "set auto")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set auto on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set auto off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set autoddl")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set autoddl on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set autoddl off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set autoterm")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set autoterm on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set autoterm off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set bail")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set bail on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set bail off x", parserOptions))); + + BOOST_TEST((std::get(parseSet( + "set bulk_insert insert into mytable (a, b) values (1, ?)")).statement == + "insert into mytable (a, b) values (1, ?)")); + + BOOST_TEST(std::get(parseSet( + "set blob")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set blob on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set blob off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set blobdisplay")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set blobdisplay on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set blobdisplay off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set count")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set count on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set count off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set echo")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set echo on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set echo off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set exec_path_display")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set exec_path_display blr")).arg == "BLR")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set exec_path_display off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set explain")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set explain on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set explain off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set heading")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set heading on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set heading off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set keep_tran")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set keep_tran on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set keep_tran off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set keep_tran_params")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set keep_tran_params on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set keep_tran_params off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set list")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set list on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set list off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set local_timeout")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set local_timeout 80")).arg == "80")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set local_timeout 90 x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set maxrows")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set maxrows 80")).arg == "80")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set maxrows 90 x", parserOptions))); + + BOOST_TEST(!std::get(parseSet( + "set names")).name.has_value()); + BOOST_TEST((std::get(parseSet( + "set names utf8")).name == "UTF8")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set names utf8 x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set per_tab")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set per_tab on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set per_tab off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set per_table_stats")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set per_table_stats on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set per_table_stats off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set plan")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set plan on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set plan off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set planonly")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set planonly on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set planonly off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set rowcount")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set rowcount 80")).arg == "80")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set rowcount 90 x", parserOptions))); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set sql", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set sql dialect", parserOptions))); + BOOST_TEST((std::get(parseSet( + "set sql dialect 3")).arg == "3")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set sql dialect 3 x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set sqlda_display")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set sqlda_display on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set sqlda_display off x", parserOptions))); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set sta", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set sta on", parserOptions))); + BOOST_TEST(std::get(parseSet( + "set stat")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set stat on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set stat off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set stats")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set stats on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set stats off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set term")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set term !")).arg == "!")); + BOOST_TEST((std::get(parseSet( + "set term Go")).arg == "Go")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set term a b", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set terminator")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set terminator !")).arg == "!")); + BOOST_TEST((std::get(parseSet( + "set terminator Go")).arg == "Go")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set terminator a b", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set time")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set time on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set time off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set transaction")).statement == "set transaction"); + BOOST_TEST(std::get(parseSet( + "set transaction read committed")).statement == "set transaction read committed"); + + BOOST_TEST(std::get(parseSet( + "set warning")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set warning on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set warning off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set warnings")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set warnings on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set warnings off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set wng")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set wng on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set wng off x", parserOptions))); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set width", parserOptions))); + BOOST_TEST((std::get(parseSet( + "set width x")).column == "X")); + BOOST_TEST(std::get(parseSet( + "set width x")).width.empty()); + BOOST_TEST((std::get(parseSet( + "set width x 80")).column == "X")); + BOOST_TEST((std::get(parseSet( + "set width x 90")).width == "90")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set width x 90 y", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set wire")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set wire on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set wire off x", parserOptions))); + + BOOST_TEST(std::get(parseSet( + "set wire_stats")).arg.empty()); + BOOST_TEST((std::get(parseSet( + "set wire_stats on")).arg == "ON")); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set wire_stats off x", parserOptions))); + + // Engine commands. + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set decfloat", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set decfloat x", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set generator", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set generator x", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set role", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set role x", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set statistics", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set statistics x", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set time zone", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set trusted", parserOptions))); + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "set trusted x", parserOptions))); +} + +BOOST_AUTO_TEST_CASE(ParseShowTest) +{ + const FrontendParser::Options parserOptions; + + const auto parseShow = [&](const std::string_view text) { + return std::get(FrontendParser::parse(text, parserOptions)); + }; + + BOOST_TEST((std::holds_alternative(parseShow("show")))); + + BOOST_TEST(!std::get(parseShow( + "show check")).name); + BOOST_TEST((std::get(parseShow( + "show check name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show collate")).name); + BOOST_TEST((std::get(parseShow( + "show collate name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show collation")).name); + BOOST_TEST((std::get(parseShow( + "show collation name")).name == "NAME")); + + BOOST_TEST(std::holds_alternative(parseShow( + "show comments"))); + + BOOST_TEST(!std::get(parseShow( + "show depen")).name); + BOOST_TEST((std::get(parseShow( + "show depen name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show domain")).name); + BOOST_TEST((std::get(parseShow( + "show domain name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show excep")).name); + BOOST_TEST((std::get(parseShow( + "show excep name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show filter")).name); + BOOST_TEST((std::get(parseShow( + "show filter name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show func")).name); + BOOST_TEST((std::get(parseShow( + "show func name")).name == "NAME")); + BOOST_TEST((std::get(parseShow( + "show func package.name")).package == "PACKAGE")); + BOOST_TEST((std::get(parseShow( + "show func package.name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show ind")).name); + BOOST_TEST((std::get(parseShow( + "show index name")).name == "NAME")); + BOOST_TEST((std::get(parseShow( + "show indices name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show gen")).name); + BOOST_TEST((std::get(parseShow( + "show generator name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show map")).name); + BOOST_TEST((std::get(parseShow( + "show mapping name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show pack")).name); + BOOST_TEST((std::get(parseShow( + "show package name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show proc")).name); + BOOST_TEST((std::get(parseShow( + "show proc name")).name == "NAME")); + BOOST_TEST((std::get(parseShow( + "show proc package.name")).package == "PACKAGE")); + BOOST_TEST((std::get(parseShow( + "show proc package.name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show pub")).name); + BOOST_TEST((std::get(parseShow( + "show publication name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show role")).name); + BOOST_TEST((std::get(parseShow( + "show roles name")).name == "NAME")); + + BOOST_TEST(std::holds_alternative(FrontendParser::parse( + "show seccla", parserOptions))); + BOOST_TEST(!std::get(parseShow( + "show seccla *")).detail); + BOOST_TEST(std::get(parseShow( + "show seccla * detail")).detail); + BOOST_TEST(!std::get(parseShow( + "show seccla * detail")).name); + BOOST_TEST((std::get(parseShow( + "show secclasses name")).name == "NAME")); + + BOOST_TEST((!std::get(parseShow( + "show system")).objType.has_value())); + BOOST_TEST((!std::get(parseShow( + "show system table")).objType == obj_relation)); + BOOST_TEST((std::get(parseShow( + "show table \"test\"")).name == "test")); + BOOST_TEST((std::get(parseShow( + "show table \"te\"\"st\"")).name == "te\"st")); + + BOOST_TEST(!std::get(parseShow( + "show table")).name); + BOOST_TEST((std::get(parseShow( + "show tables name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show trig")).name); + BOOST_TEST((std::get(parseShow( + "show triggers name")).name == "NAME")); + + BOOST_TEST(!std::get(parseShow( + "show view")).name); + BOOST_TEST((std::get(parseShow( + "show views name")).name == "NAME")); + + BOOST_TEST(std::holds_alternative(parseShow( + "show wire_stat"))); + BOOST_TEST(std::holds_alternative(parseShow( + "show wire_statistics"))); +} + + +BOOST_AUTO_TEST_SUITE_END() // FrontendParserTests +BOOST_AUTO_TEST_SUITE_END() // FrontendParserSuite +BOOST_AUTO_TEST_SUITE_END() // ISqlSuite diff --git a/src/isql/tests/ISqlTest.cpp b/src/isql/tests/ISqlTest.cpp index 2531df796c..dddf8ff03e 100644 --- a/src/isql/tests/ISqlTest.cpp +++ b/src/isql/tests/ISqlTest.cpp @@ -4,16 +4,3 @@ #define BOOST_TEST_MODULE ISqlTest #include "boost/test/included/unit_test.hpp" - - -// TODO: Remove. - -BOOST_AUTO_TEST_SUITE(IsqlSuite) -BOOST_AUTO_TEST_SUITE(DummySuite) - -BOOST_AUTO_TEST_CASE(DummyTest) -{ -} - -BOOST_AUTO_TEST_SUITE_END() // DummySuite -BOOST_AUTO_TEST_SUITE_END() // IsqlSuite diff --git a/src/jrd/obj.h b/src/jrd/obj.h index 0bfd7bd55d..77e8faa7cb 100644 --- a/src/jrd/obj.h +++ b/src/jrd/obj.h @@ -24,6 +24,8 @@ #ifndef JRD_OBJ_H #define JRD_OBJ_H +#include "../common/gdsassert.h" + // Object types used in RDB$DEPENDENCIES and RDB$USER_PRIVILEGES and stored in backup. // Note: some values are hard coded in grant.gdl // Keep existing constants unchanged. @@ -76,10 +78,11 @@ const ObjectType obj_index_condition = 37; const ObjectType obj_type_MAX = 38; -// used in the parser only / no relation with obj_type_MAX (should be greater) +// not used in metadata / no relation with obj_type_MAX (should be greater) const ObjectType obj_user_or_role= 100; const ObjectType obj_parameter = 101; const ObjectType obj_column = 102; +const ObjectType obj_publication = 103; const ObjectType obj_any = 255;