backend/wbprivate/sqlide/wb_sql_editor_help.cpp (942 lines of code) (raw):
/*
* Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms, as
* designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <regex>
#include <thread>
#include <rapidjson/istreamwrapper.h>
#include <fstream>
#include "base/log.h"
#include "base/string_utilities.h"
#include "base/file_utilities.h"
#include "mforms/app.h"
#include "mforms/code_editor.h"
#include "grtdb/db_helpers.h"
#include "workbench/wb_backend_public_interface.h"
#include "wb_sql_editor_help.h"
#include "mysql/mysql-recognition-types.h"
#include "mysql/MySQLRecognizerCommon.h"
#include "mysql/MySQLLexer.h"
#include "mysql/MySQLParser.h"
DEFAULT_LOG_DOMAIN("Context help")
using namespace parsers;
using namespace antlr4;
using namespace help;
using namespace rapidjson;
class HelpContext::Private {
public:
Private() : lexer(&input), tokens(&lexer), parser(&tokens) {
}
ANTLRInputStream input;
MySQLLexer lexer;
CommonTokenStream tokens;
MySQLParser parser;
ParserRuleContext *parse(const std::string &query) {
input.load(query);
lexer.reset();
lexer.setInputStream(&input);
tokens.setTokenSource(&lexer);
parser.reset();
return parser.query();
}
};
//----------------- HelpContext ----------------------------------------------------------------------------------------
HelpContext::HelpContext(GrtCharacterSetsRef charsets, const std::string &sqlMode, long serverVersion) {
_d = new Private();
std::set<std::string> filteredCharsets;
for (size_t i = 0; i < charsets->count(); i++)
filteredCharsets.insert("_" + base::tolower(*charsets[i]->name()));
if (_d->lexer.serverVersion < 50503) {
filteredCharsets.erase("_utf8mb4");
filteredCharsets.erase("_utf16");
filteredCharsets.erase("_utf32");
} else {
filteredCharsets.insert("_utf8mb4");
filteredCharsets.insert("_utf16");
filteredCharsets.insert("_utf32");
}
_d->lexer.charsets = filteredCharsets;
_d->lexer.serverVersion = serverVersion;
_d->lexer.sqlModeFromString(sqlMode);
_d->parser.sqlMode = _d->lexer.sqlMode;
_d->parser.serverVersion = serverVersion;
_d->parser.removeParseListeners();
_d->parser.removeErrorListeners();
}
//----------------------------------------------------------------------------------------------------------------------
HelpContext::~HelpContext() {
delete _d;
}
//----------------------------------------------------------------------------------------------------------------------
long HelpContext::serverVersion() const {
return _d->lexer.serverVersion;
}
//----------------- DbSqlEditorContextHelp -----------------------------------------------------------------------------
DbSqlEditorContextHelp *DbSqlEditorContextHelp::get() {
static DbSqlEditorContextHelp instance;
return &instance;
}
//----------------------------------------------------------------------------------------------------------------------
static std::string helpStyleSheetTemplate = "<style>\n"
"body { color: »textColor«; background-color: »mainBackground«; spacing: 5px; }\n"
"emphasis {font-style: italic; font-size: 100%; font-weight: 400;}\n"
"literal { font-family: monospace; background-color: »literalBackground«; color: »textColor«; }\n"
"literal[role='stmt'] { font-weight: 600; }\n"
"literal[role='func'] { font-weight: 600; }\n"
"literal[role='cfunc'] { color: #ba0099; }\n"
"replaceable { font-style: italic; font-weight: 600; color: »textColor«; }\n"
"indexterm { display: none; }\n"
"userinput { color: »userInput«; font-weight: 600; }\n"
"pre { margin-top: 0px; margin-bottom: 0px; margin-left: 6px; padding: 3px 8px; line-height: 1.5; }\n"
"pre.programlisting {margin: 6px; padding: 10px; color: »textColor«; display: block; font-size: 95%;"
" background-color: »userInputBackground«; }\n"
"</style>";
//----------------------------------------------------------------------------------------------------------------------
std::string convertXRef(long version, std::string const &source) {
if (source.find("<xref") == std::string::npos)
return source;
std::regex pattern("<xref linkend=\"([^\"]+)\" />", std::regex::ECMAScript | std::regex ::icase);
std::string result =
std::regex_replace(source, pattern, "<a href='http://dev.mysql.com/doc/refman/{0}.{1}/en/\\1.html'>\\1</a>");
result = base::replaceString(result, "{0}", std::to_string(version / 100));
result = base::replaceString(result, "{1}", std::to_string(version % 10));
return result;
}
//----------------------------------------------------------------------------------------------------------------------
std::string convertExternalLinks(long version, std::string const &source) {
if (source.find("<link") == std::string::npos)
return source;
std::regex pattern("<link linkend=\"([^\"]+)\">([^<]+)<\\/link>", std::regex::ECMAScript | std::regex ::icase);
std::string result =
std::regex_replace(source, pattern, "<a href='http://dev.mysql.com/doc/refman/{0}.{1}/en/glossary.html#\\1'>\\2</a>");
result = base::replaceString(result, "{0}", std::to_string(version / 100));
result = base::replaceString(result, "{1}", std::to_string(version % 10));
return result;
}
//----------------------------------------------------------------------------------------------------------------------
std::string convertInternalLinks(std::string const &source) {
if (source.find("role=\"stmt\"") == std::string::npos)
return source;
std::regex pattern("<literal role=\"stmt\">([^<]+)</literal>", std::regex::ECMAScript | std::regex ::icase);
std::string result = std::regex_replace(
source, pattern, "<a href='local:\\1'>\\1</a>");
return result;
}
//----------------------------------------------------------------------------------------------------------------------
std::string convertList(long version, Value const &list) {
std::string result;
for (auto const &entry: list.GetArray()) {
auto iterator = entry.FindMember("para");
if (iterator != entry.MemberEnd()) {
std::string text = "<p>" + convertInternalLinks(iterator->value.GetString()) + "</p>";
result += convertXRef(version, convertExternalLinks(version, text));
} else {
auto iterator = entry.FindMember("programlisting");
if (iterator != entry.MemberEnd()) {
std::string text = convertInternalLinks(iterator->value.GetString());
result += "<pre>" + text + "</pre>";
} else {
auto iterator = entry.FindMember("itemizedlist"); // Convert to bullet list.
if (iterator != entry.MemberEnd()) {
result = "<ul>";
auto const &itemizedList = iterator->value.GetArray();
for (auto const &listentry: itemizedList) {
result += "<li>" + convertList(version, listentry) + "</li>";
}
result += "</ul>";
}
}
}
}
return result;
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Creates the HTML formatted help text from the object that's passed in.
*/
std::string DbSqlEditorContextHelp::createHelpTextFromJson(long version, Value const &json) {
std::string result = "<body>";
std::string id = json.HasMember("id") ? json["id"].GetString() : "";
result += "<h3>" + id + " Syntax:</h3>";
// Syntax (the summary), often in a code block.
if (json.HasMember("syntax")) {
auto &syntax = json["syntax"];
if (syntax.IsObject()) {
for (auto it = syntax.MemberBegin(); it != syntax.MemberEnd(); ++it) {
// There are different variants for syntax descriptions. Usually it's encapsulated in a program listing,
// but e.g. for functions in a list the syntax is a paragraph.
auto entry = it->value.FindMember("programlisting");
if (entry != it->value.MemberEnd()) {
std::string text = convertInternalLinks(entry->value.GetString());
result += "<pre class='programlisting line-numbers language-sql'>" + text + "</pre><br/>";
} else {
auto para = it->value.FindMember("para");
if (para != it->value.MemberEnd()) {
result += "<p>" + convertInternalLinks(para->value.GetString()) + "</p>";
}
}
}
}
}
// The full description, plain text with code examples, lists and more.
if (json.HasMember("description")) {
auto const &description = json["description"].GetArray();
for (auto const &entry : description) {
auto iterator = entry.FindMember("para");
if (iterator != entry.MemberEnd()) {
std::string text = "<p>" + convertInternalLinks(iterator->value.GetString()) + "</p>";
result += convertXRef(version, convertExternalLinks(version, text));
} else {
auto iterator = entry.FindMember("programlisting");
if (iterator != entry.MemberEnd()) {
std::string text = convertInternalLinks(iterator->value.GetString());
result += "<pre class='programlisting line-numbers language-sql'>" + text + "</pre><br/>";
} else {
auto iterator = entry.FindMember("itemizedlist"); // Convert to bullet list.
if (iterator != entry.MemberEnd()) {
result += "<ul>";
auto const &itemizedList = iterator->value.GetArray();
for (auto const &listentry : itemizedList) {
result += "<li>" + convertList(version, listentry) + "</li>";
}
result += "</ul>";
}
}
}
}
}
std::string page = base::replaceString(base::tolower(id), " ", "-");
auto iterator = pageMap.find(page);
if (iterator != pageMap.end())
page = iterator->second;
std::string url = base::strfmt("http://dev.mysql.com/doc/refman/%ld.%ld/en/%s.html", version / 100, version % 10, page.c_str());
result += "<b>See also: </>: <a href='" + url + "'>Online help " + page + "</a><br /><br /></body>";
return result;
}
//----------------- DbSqlEditorContextHelp -----------------------------------------------------------------------------
DbSqlEditorContextHelp::DbSqlEditorContextHelp() {
pageMap = {
{ "now", "date-and-time-functions" },
{ "like", "string-comparison-functions" },
{ "auto_increment", "example-auto-increment" },
};
loaderThread = std::thread([this]() {
std::string dataDir = base::makePath(mforms::App::get()->baseDir(), "modules/data/sqlide");
for (long version : { 800, 507, 506 }) {
std::string fileName = "help-" + std::to_string(version / 100) + "." + std::to_string(version % 10) + ".json";
std::string path = base::makePath(dataDir, fileName);
if (!base::file_exists(path)) {
logError("Help file not found (%s)\n", path.c_str());
continue;
}
try {
rapidjson::Value document;
std::ifstream ifs(path);
IStreamWrapper isw(ifs);
Document d;
d.ParseStream(isw);
if (d.HasParseError()) {
logError("Could not read help text file (%s)\nError code: %d\n", fileName.c_str(), d.GetParseError());
continue;
}
std::set<std::string> topics;
Value topicRoot;
topicRoot.CopyFrom(d, d.GetAllocator());
if (topicRoot.HasMember("topics")) {
auto const &topicList = topicRoot["topics"].GetArray();
for (auto &topic : topicList) {
std::string id = base::toupper(topic["id"].GetString());
topics.insert(id);
helpContent[version][id] = createHelpTextFromJson(version, topic);
}
}
helpTopics[version] = topics;
} catch (std::bad_cast &e) {
logError("Unexpected file format (%s)\nError message: %s\n", fileName.c_str(), e.what());
}
}
});
}
//----------------------------------------------------------------------------------------------------------------------
DbSqlEditorContextHelp::~DbSqlEditorContextHelp() {
waitForLoading();
}
//----------------------------------------------------------------------------------------------------------------------
void DbSqlEditorContextHelp::waitForLoading() {
if (loaderThread.joinable())
loaderThread.join();
};
//----------------------------------------------------------------------------------------------------------------------
/**
* A quick lookup if the help topic exists actually, without retrieving help text.
*/
bool DbSqlEditorContextHelp::topicExists(long serverVersion, const std::string &topic) {
waitForLoading();
auto iterator = helpTopics.find(serverVersion / 100);
if (iterator == helpTopics.end())
return false;
return iterator->second.count(topic) > 0;
};
//----------------------------------------------------------------------------------------------------------------------
bool DbSqlEditorContextHelp::helpTextForTopic(HelpContext *context, const std::string &topic, std::string &text) {
logDebug2("Looking up help topic: %s\n", topic.c_str());
// If help text is requested so quickly that loading the help content hasn't finished yet, wait here.
// Usually however, the content is loaded way before offline help is queried the first time.
waitForLoading();
if (!topic.empty()) {
auto iterator = helpContent.find(context->serverVersion() / 100);
if (iterator == helpContent.end())
return false;
// Prepare style sheet depending on the OS theme.
std::string styleSheet;
#ifndef __linux__
styleSheet = helpStyleSheetTemplate;
base::Color color;
if (mforms::App::get()->isDarkModeActive())
color = { 0x60 / 255.0, 0x60 / 255.0, 0x60 / 255.0 };
else
color = { 0xd0 / 255.0, 0xd0 / 255.0, 0xd0 / 255.0 };
base::replaceStringInplace(styleSheet, "»literalBackground«", color.to_html());
color = base::Color::getSystemColor(base::TextColor);
base::replaceStringInplace(styleSheet, "»textColor«", color.to_html());
color = base::Color::getSystemColor(base::SecondaryBackgroundColor);
base::replaceStringInplace(styleSheet, "»mainBackground«", color.to_html());
if (mforms::App::get()->isDarkModeActive())
color = base::Color::parse("#404040");
else
color = base::Color::parse("#ebebeb");
base::replaceStringInplace(styleSheet, "»userInputBackground«", color.to_html());
color = base::Color::parse("#004480");
base::replaceStringInplace(styleSheet, "»userInput«", color.to_html());
#endif
text = "<html><head>" + styleSheet + "</head>" + iterator->second[topic] + "</html>";
return true;
}
return false;
}
//----------------------------------------------------------------------------------------------------------------------
// Determines if the given tree is a terminal node and if so, if it is of the given type.
bool isToken(tree::ParseTree *tree, size_t type) {
auto terminal = dynamic_cast<tree::TerminalNode *>(tree);
if (terminal != nullptr)
return terminal->getSymbol()->getType() == type;
auto token = dynamic_cast<ParserRuleContext *>(tree)->start;
if (token == nullptr)
return false;
return token->getType() == type;
}
//----------------------------------------------------------------------------------------------------------------------
// Determines if the given is of the given type.
bool isToken(Token *token, size_t type) {
return token->getType() == type;
}
//----------------------------------------------------------------------------------------------------------------------
// Determines if the parent of the given tree is a specific context.
bool isParentContext(tree::ParseTree *tree, size_t type) {
auto parent = dynamic_cast<ParserRuleContext *>(tree->parent);
return parent->getRuleIndex() == type;
}
//----------------------------------------------------------------------------------------------------------------------
static std::map<std::string, std::string> functionSynonyms = {
{ "ST_ASWKB", "ST_ASBINARY" },
{ "ASWKB", "ASBINARY" },
{ "ST_ASWKT", "ST_ASTEXT" },
{ "ASWKT", "ASTEXT" },
{ "ST_CROSSES", "CROSSES" },
{ "GEOMETRYFROMTEXT", "GEOMFROMTEXT" },
{ "GEOMETRYFROMWKB", "GEOMFROMWKB" },
{ "ST_GEOMETRYFROMTEXT", "ST_GEOMFROMTEXT" },
{ "ST_GEOMETRYFROMWKB", "ST_GEOMFROMWKB" },
{ "ST_GLENGTH", "GLENGTH" },
};
std::string functionTopicForContext(ParserRuleContext *context) {
std::string topic;
Token *nameToken = nullptr;
size_t rule = context->getRuleIndex();
switch (rule) {
case MySQLParser::RuleFunctionCall: {
auto functionContext = dynamic_cast<MySQLParser::FunctionCallContext *>(context);
// We only consider global functions here, hence there should not be any qualifier.
if (functionContext->pureIdentifier() != nullptr)
nameToken = functionContext->pureIdentifier()->start;
else if (functionContext->qualifiedIdentifier() != nullptr)
nameToken = functionContext->qualifiedIdentifier()->start;
break;
}
case MySQLParser::RuleRuntimeFunctionCall: {
auto functionContext = dynamic_cast<MySQLParser::RuntimeFunctionCallContext *>(context);
if (functionContext->name != nullptr) {
switch (functionContext->name->getType()) {
// Function names that are also keywords.
case MySQLLexer::IF_SYMBOL:
case MySQLLexer::REPEAT_SYMBOL:
case MySQLLexer::REPLACE_SYMBOL:
case MySQLLexer::TIME_SYMBOL:
case MySQLLexer::TIMESTAMP_SYMBOL:
case MySQLLexer::CHAR_SYMBOL:
case MySQLLexer::DATE_SYMBOL:
case MySQLLexer::INSERT_SYMBOL:
return base::toupper(functionContext->name->getText()) + " FUNCTION";
case MySQLLexer::COLLATION_SYMBOL:
return "COLLATION";
default:
nameToken = functionContext->name;
}
}
break;
}
case MySQLParser::RuleSumExpr: {
auto exprContext = dynamic_cast<MySQLParser::SumExprContext *>(context);
if (exprContext->COUNT_SYMBOL() != nullptr && exprContext->DISTINCT_SYMBOL() != nullptr)
return "COUNT DISTINCT";
nameToken = exprContext->name;
break;
}
case MySQLParser::RuleGeometryFunction: {
auto functionContext = dynamic_cast<MySQLParser::GeometryFunctionContext *>(context);
nameToken = functionContext->name;
break;
}
}
if (nameToken != nullptr)
topic = base::toupper(nameToken->getText());
if (functionSynonyms.count(topic) > 0)
topic = functionSynonyms[topic];
return topic;
}
//----------------------------------------------------------------------------------------------------------------------
static std::unordered_map<size_t, std::string> supportedOperatorsAndKeywords = {
{ MySQLLexer::EQUAL_OPERATOR, "ASSIGN-EQUAL" },
{ MySQLLexer::ASSIGN_OPERATOR, "ASSIGN-VALUE" },
{ MySQLLexer::LOGICAL_AND_OPERATOR, "AND" },
{ MySQLLexer::LOGICAL_OR_OPERATOR, "||" },
{ MySQLLexer::BIT_AND_SYMBOL, "BIT_AND" },
{ MySQLLexer::BIT_OR_SYMBOL, "BIT_OR" },
{ MySQLLexer::BIT_XOR_SYMBOL, "BIT_XOR" },
{ MySQLLexer::LOGICAL_NOT_OPERATOR, "!" },
{ MySQLLexer::NOT_EQUAL_OPERATOR, "!=" },
{ MySQLLexer::MOD_OPERATOR, "%" },
{ MySQLLexer::BITWISE_AND_OPERATOR, "&" },
{ MySQLLexer::MULT_OPERATOR, "*" },
{ MySQLLexer::PLUS_OPERATOR, "+" },
{ MySQLLexer::JSON_SEPARATOR_SYMBOL, "->" },
{ MySQLLexer::JSON_UNQUOTED_SEPARATOR_SYMBOL, "->>" },
{ MySQLLexer::DIV_OPERATOR, "/" },
{ MySQLLexer::LESS_THAN_OPERATOR, "<" },
{ MySQLLexer::SHIFT_LEFT_OPERATOR, "<<" },
{ MySQLLexer::NULL_SAFE_EQUAL_OPERATOR, "<=>" },
{ MySQLLexer::GREATER_THAN_OPERATOR, ">" },
{ MySQLLexer::GREATER_OR_EQUAL_OPERATOR, ">=" },
{ MySQLLexer::LESS_OR_EQUAL_OPERATOR, "<=" },
{ MySQLLexer::SHIFT_RIGHT_OPERATOR, ">>" },
{ MySQLLexer::BITWISE_XOR_OPERATOR, "^" },
{ MySQLLexer::BITWISE_OR_OPERATOR, "|" },
{ MySQLLexer::BITWISE_NOT_OPERATOR, "~" },
{ MySQLLexer::AUTO_INCREMENT_SYMBOL, "AUTO_INCREMENT" },
{ MySQLLexer::CALL_SYMBOL, "CALL" },
{ MySQLLexer::CAST_SYMBOL, "CAST" },
{ MySQLLexer::DIV_SYMBOL, "DIV" },
{ MySQLLexer::MOD_SYMBOL, "MOD" },
{ MySQLLexer::OR_SYMBOL, "OR" },
{ MySQLLexer::SPATIAL_SYMBOL, "SPATIAL" },
{ MySQLLexer::UNION_SYMBOL, "UNION" },
{ MySQLLexer::XOR_SYMBOL, "XOR" },
};
// Simple token -> topic matches, only used in certain contexts and only if there is no trivial token -> topic
// translation.
static std::unordered_map<size_t, std::string> tokenToTopic = {
{ MySQLLexer::AUTHORS_SYMBOL, "SHOW AUTHORS" },
{ MySQLLexer::BINLOG_SYMBOL, "SHOW BINLOG EVENTS" },
{ MySQLLexer::COLLATION_SYMBOL, "SHOW COLLATION" },
{ MySQLLexer::COLUMNS_SYMBOL, "SHOW COLUMNS" },
{ MySQLLexer::CONTRIBUTORS_SYMBOL, "SHOW CONTRIBUTORS" },
{ MySQLLexer::DATABASES_SYMBOL, "SHOW databases" },
{ MySQLLexer::ENGINE_SYMBOL, "SHOW ENGINE" },
{ MySQLLexer::ENGINES_SYMBOL, "SHOW ENGINES" },
{ MySQLLexer::ERRORS_SYMBOL, "SHOW ERRORS" },
{ MySQLLexer::EVENTS_SYMBOL, "SHOW EVENTS" },
{ MySQLLexer::GRANTS_SYMBOL, "SHOW GRANTS" },
{ MySQLLexer::INDEX_SYMBOL, "SHOW INDEX" },
{ MySQLLexer::INDEXES_SYMBOL, "SHOW INDEX" },
{ MySQLLexer::INSTALL_SYMBOL, "INSTALL PLUGIN" },
{ MySQLLexer::KEYS_SYMBOL, "SHOW INDEX" },
{ MySQLLexer::LOGS_SYMBOL, "SHOW BINARY LOGS" },
{ MySQLLexer::MASTER_SYMBOL, "SHOW MASTER STATUS" },
{ MySQLLexer::OPEN_SYMBOL, "SHOW OPEN TABLES" },
{ MySQLLexer::PLUGIN_SYMBOL, "SHOW PLUGIN" },
{ MySQLLexer::PLUGINS_SYMBOL, "SHOW PLUGINS" },
{ MySQLLexer::PRIVILEGES_SYMBOL, "SHOW PRIVILEGES" },
{ MySQLLexer::PROCESSLIST_SYMBOL, "SHOW PROCESSLIST" },
{ MySQLLexer::PROFILE_SYMBOL, "SHOW PROFILE" },
{ MySQLLexer::PROFILES_SYMBOL, "SHOW PROFILES" },
{ MySQLLexer::RELAYLOG_SYMBOL, "SHOW RELAYLOG EVENTS" },
{ MySQLLexer::STATUS_SYMBOL, "SHOW STATUS" },
{ MySQLLexer::TABLES_SYMBOL, "SHOW TABLES" },
{ MySQLLexer::TRIGGERS_SYMBOL, "SHOW TRIGGERS" },
{ MySQLLexer::VARIABLES_SYMBOL, "SHOW VARIABLES" },
{ MySQLLexer::WARNINGS_SYMBOL, "SHOW WARNINGS" },
{ MySQLLexer::ANALYZE_SYMBOL, "ANALYZE TABLE" },
{ MySQLLexer::CHECKSUM_SYMBOL, "CHECKSUM TABLE" },
{ MySQLLexer::CACHE_SYMBOL, "CACHE INDEX" },
{ MySQLLexer::CHECK_SYMBOL, "CHECK TABLE" },
{ MySQLLexer::FLUSH_SYMBOL, "FLUSH" },
{ MySQLLexer::KILL_SYMBOL, "KILL" },
{ MySQLLexer::LOAD_SYMBOL, "LOAD INDEX" },
{ MySQLLexer::OPTIMIZE_SYMBOL, "OPTIMIZE TABLE" },
{ MySQLLexer::REPAIR_SYMBOL, "REPAIR TABLE" },
{ MySQLLexer::SHUTDOWN_SYMBOL, "SHUTDOWN" },
{ MySQLLexer::UNINSTALL_SYMBOL, "UNINSTALL PLUGIN" },
};
static std::unordered_map<size_t, std::string> contextToTopic = {
{ MySQLParser::RuleCallStatement, "CALL" },
{ MySQLParser::RuleCreateDatabase, "CREATE DATABASE" },
{ MySQLParser::RuleCreateEvent, "CREATE EVENT" },
{ MySQLParser::RuleCreateFunction, "CREATE FUNCTION" },
{ MySQLParser::RuleCreateUdf, "CREATE FUNCTION UDF" },
{ MySQLParser::RuleCreateIndex, "CREATE INDEX" },
{ MySQLParser::RuleCreateProcedure, "CREATE PROCEDURE" },
{ MySQLParser::RuleCreateServer, "CREATE SERVER" },
{ MySQLParser::RuleCreateTable, "CREATE TABLE" },
{ MySQLParser::RuleCreateTablespace, "CREATE TABLESPACE" },
{ MySQLParser::RuleCreateTrigger, "CREATE TRIGGER" },
{ MySQLParser::RuleCreateUser, "CREATE USER" },
{ MySQLParser::RuleCreateView, "CREATE VIEW" },
{ MySQLParser::RuleDeleteStatement, "DELETE" },
{ MySQLParser::RuleDoStatement, "DO" },
{ MySQLParser::RuleDropUser, "DROP USER" },
{ MySQLParser::RuleExecuteStatement, "EXECUTE STATEMENT" },
{ MySQLParser::RuleDescribeStatement, "EXPLAIN" },
{ MySQLParser::RuleExplainStatement, "EXPLAIN" },
{ MySQLParser::RuleGrant, "GRANT" },
{ MySQLParser::RuleHandlerStatement, "HANDLER" },
{ MySQLParser::RuleHandlerDeclaration, "DECLARE HANDLER" },
{ MySQLParser::RuleHelpCommand, "HELP COMMAND" },
{ MySQLParser::RuleIfStatement, "IF STATEMENT" },
{ MySQLParser::RuleIterateStatement, "ITERATE" },
{ MySQLParser::RuleJoinedTable, "JOIN" },
{ MySQLParser::RuleLabel, "LABELS" },
{ MySQLParser::RuleLeaveStatement, "LEAVE" },
{ MySQLParser::RuleLockStatement, "LOCK" },
{ MySQLParser::RuleLoopBlock, "LOOP" },
{ MySQLParser::RuleCursorOpen, "OPEN" },
{ MySQLParser::RuleQuerySpecification, "SELECT" },
{ MySQLParser::RuleCursorClose, "CLOSE" },
{ MySQLParser::RuleCursorFetch, "FETCH" },
{ MySQLParser::RuleProcedureAnalyseClause, "PROCEDURE ANALYSE" },
{ MySQLParser::RuleRenameTableStatement, "RENAME TABLE" },
{ MySQLParser::RuleRenameUser, "RENAME USER" },
{ MySQLParser::RuleRepeatUntilBlock, "REPEAT LOOP" },
{ MySQLParser::RuleReplaceStatement, "REPLACE" },
{ MySQLParser::RuleResignalStatement, "RESIGNAL" },
{ MySQLParser::RuleReturnStatement, "RETURN" },
{ MySQLParser::RuleRevoke, "REVOKE" },
{ MySQLParser::RuleSavepointStatement, "SAVEPOINT" },
{ MySQLParser::RuleSelectStatement, "SELECT" },
{ MySQLParser::RuleTransactionStatement, "START TRANSACTION" },
{ MySQLParser::RuleTruncateTableStatement, "TRUNCATE TABLE" },
{ MySQLParser::RuleUpdateStatement, "UPDATE" },
{ MySQLParser::RuleUseCommand, "USE" },
{ MySQLParser::RuleWhileDoBlock, "WHILE" },
{ MySQLParser::RuleXaStatement, "XA" },
{ MySQLParser::RuleVariableDeclaration, "DECLARE VARIABLE" },
{ MySQLParser::RuleConditionDeclaration, "DECLARE CONDITION" },
{ MySQLParser::RuleHandlerDeclaration, "DECLARE HANDLER" },
{ MySQLParser::RuleCursorDeclaration, "DECLARE CURSOR" },
{ MySQLParser::RuleGetDiagnostics, "GET DIAGNOSTICS" },
{ MySQLParser::RuleSignalStatement, "SIGNAL" },
{ MySQLParser::RuleAlterUser, "ALTER USER" },
{ MySQLParser::RuleCaseStatement, "CASE STATEMENT" },
{ MySQLParser::RuleChangeMaster, "CHANGE MASTER TO" },
{ MySQLParser::RuleDropDatabase, "DROP DATABASE" },
{ MySQLParser::RuleDropEvent, "DROP EVENT" },
{ MySQLParser::RuleDropFunction, "DROP FUNCTION" },
{ MySQLParser::RuleDropProcedure, "DROP PROCEDURE" },
{ MySQLParser::RuleDropIndex, "DROP INDEX" },
{ MySQLParser::RuleDropLogfileGroup, "DROP LOGFILEGROUP" },
{ MySQLParser::RuleDropServer, "DROP SERVER" },
{ MySQLParser::RuleDropTable, "DROP TABLE" },
{ MySQLParser::RuleDropTableSpace, "DROP TABLESPACE" },
{ MySQLParser::RuleDropTrigger, "DROP TRIGGER" },
{ MySQLParser::RuleDropView, "DROP VIEW" },
};
// Words which are part of a multi word topic or can produce wrong topics if used alone, and hence need further
// examination.
static std::unordered_set<std::string> specialWords = {
"CHAR", "COUNT", "DATE", "DOUBLE", "REPLACE", "TIME", "TIMESTAMP", "YEAR", "DATABASE",
"USER", "INSERT", "PREPARE", "HANDLER", "FLUSH", "IS", "IN", "LIKE", "REGEXP",
"SET", "PASSWORD", "SHOW", "COLLATION", "OPEN", "UPDATE", "DELETE"
};
//----------------------------------------------------------------------------------------------------------------------
/**
* Determines a help topic from the given query at the given position (given as column/row pair).
*/
std::string DbSqlEditorContextHelp::helpTopicFromPosition(HelpContext *helpContext, const std::string &query,
size_t caret) {
logDebug2("Finding help topic\n");
// We are not interested in validity here. We simply parse in default mode (LL) and examine the returned parse tree.
// This usually will give us a good result, except in cases where the query has an error before the caret such that
// we cannot predict the path through the rules.
ParserRuleContext *parseTree = helpContext->_d->parse(query);
//std::cout << "Parse tree: " << parseTree->toStringTree(&helpContext->_d->parser) << std::endl;
tree::ParseTree *tree = MySQLRecognizerCommon::contextFromPosition(parseTree, caret);
if (tree == nullptr)
return "";
if (antlrcpp::is<tree::TerminalNode *>(tree)) {
tree::TerminalNode *node = dynamic_cast<tree::TerminalNode *>(tree);
size_t token = node->getSymbol()->getType();
if (token == MySQLLexer::SEMICOLON_SYMBOL) {
tree = MySQLRecognizerCommon::getPrevious(tree);
node = dynamic_cast<tree::TerminalNode *>(tree);
token = node->getSymbol()->getType();
}
// First check if we can get a topic for this single token, either from our topic table or by lookup.
// This is a double-edged sword. It will help in incomplete statements where we do not get a good parse tree
// but might also show help topics for unrelated stuff (e.g. returns "SAVEPOINT" for "select a := savepoint(c);").
if (supportedOperatorsAndKeywords.count(token) > 0)
return supportedOperatorsAndKeywords[token];
switch (token) {
case MySQLLexer::MINUS_OPERATOR:
if (isParentContext(tree, MySQLParser::RuleSimpleExpr))
return "- UNARY";
return "- BINARY";
case MySQLLexer::BINARY_SYMBOL:
if (isParentContext(tree, MySQLParser::RuleDataType))
return "BINARY";
if (isParentContext(tree, MySQLParser::RuleSimpleExpr) || isParentContext(tree, MySQLParser::RuleCastType))
return "BINARY OPERATOR";
tree = tree->parent;
break;
case MySQLLexer::CHANGE_SYMBOL:
if (isParentContext(tree, MySQLParser::RuleChangeMaster))
return "CHANGE MASTER TO";
if (isParentContext(tree, MySQLParser::RuleChangeReplication))
return "CHANGE REPLICATION FILTER";
tree = tree->parent;
break;
// Other keywords connected to topics.
case MySQLLexer::BEGIN_SYMBOL:
case MySQLLexer::END_SYMBOL:
return "BEGIN END";
case MySQLLexer::TRUE_SYMBOL:
case MySQLLexer::FALSE_SYMBOL:
return "TRUE FALSE";
case MySQLLexer::BINLOG_SYMBOL:
if (isParentContext(tree, MySQLParser::RuleOtherAdministrativeStatement))
return "BINLOG";
if (isParentContext(tree, MySQLParser::RuleShowStatement))
return "SHOW BINLOG EVENTS";
tree = tree->parent;
break;
default:
std::string s = base::toupper(node->getText());
if (specialWords.count(s) == 0 && topicExists(helpContext->serverVersion(), s))
return s;
// No specific help topic for the given terminal. Jump to the token's parent and start the
// context search then.
tree = tree->parent;
break;
}
}
// See if we have a help topic for the given tree. If not walk up the parent chain until we find something.
while (true) {
if (tree == nullptr)
return "";
// We deliberately don't check if the given tree is actually a parse rule context - there is no other possibility.
ParserRuleContext *context = dynamic_cast<ParserRuleContext *>(tree);
size_t ruleIndex = context->getRuleIndex();
if (contextToTopic.count(ruleIndex) > 0)
return contextToTopic[ruleIndex];
// Topics from function names
std::string functionTopic = functionTopicForContext(context);
if (!functionTopic.empty() && topicExists(helpContext->serverVersion(), functionTopic))
return functionTopic;
switch (ruleIndex) {
case MySQLParser::RulePredicateOperations: {
if (!context->children.empty()) {
// Some keyword topics have variants with a leading NOT.
auto parent = dynamic_cast<MySQLParser::PredicateContext *>(context->parent);
bool isNot = parent->notRule() != nullptr;
// IN, BETWEEN (with special help topic name), LIKE, REGEXP
auto predicateContext = dynamic_cast<MySQLParser::PredicateOperationsContext *>(context);
if (isToken(predicateContext->children[0], MySQLLexer::BETWEEN_SYMBOL)) {
if (isNot)
return "NOT BETWEEN";
return "BETWEEN AND";
}
std::string topic = isNot ? "NOT " : "";
return topic + base::toupper(predicateContext->children[0]->getText());
}
break;
}
case MySQLParser::RuleOtherAdministrativeStatement: {
// See if we only have a single flush command.
auto adminContext = dynamic_cast<MySQLParser::OtherAdministrativeStatementContext *>(context);
if (adminContext->type->getType() == MySQLLexer::FLUSH_SYMBOL && adminContext->flushOption().size() == 1 &&
adminContext->flushOption(0)->option->getType() == MySQLLexer::QUERY_SYMBOL)
return "FLUSH QUERY CACHE";
if (adminContext->type != nullptr)
return tokenToTopic[adminContext->type->getType()];
break;
}
case MySQLParser::RuleInsertStatement: {
auto insertContext = dynamic_cast<MySQLParser::InsertStatementContext *>(context);
if (insertContext->insertQueryExpression() != nullptr)
return "INSERT SELECT";
if (insertContext->insertLockOption() != nullptr &&
insertContext->insertLockOption()->DELAYED_SYMBOL() != nullptr)
return "INSERT DELAYED";
return "INSERT";
}
case MySQLParser::RuleInstallUninstallStatment: {
auto pluginContext = dynamic_cast<MySQLParser::InstallUninstallStatmentContext *>(context);
if (pluginContext->action != nullptr)
return tokenToTopic[pluginContext->action->getType()];
break;
}
case MySQLParser::RuleExpr: {
auto exprContext = dynamic_cast<MySQLParser::ExprContext *>(context);
if (exprContext->children.size() > 2 && isToken(exprContext->children[1], MySQLLexer::IS_SYMBOL)) {
if (isToken(exprContext->children[2], MySQLLexer::NOT_SYMBOL) ||
isToken(exprContext->children[2], MySQLLexer::NOT2_SYMBOL))
return "IS NOT";
return "IS";
}
break;
}
case MySQLParser::RuleBoolPri: {
if (antlrcpp::is<MySQLParser::PrimaryExprIsNullContext *>(context)) {
auto primaryExprIsNullContext = dynamic_cast<MySQLParser::PrimaryExprIsNullContext *>(context);
if (primaryExprIsNullContext->notRule() == nullptr)
return "IS NULL";
return "IS NOT NULL";
}
break;
}
case MySQLParser::RuleSetStatement: {
auto setContext = dynamic_cast<MySQLParser::SetStatementContext *>(context);
auto setValueListContext = setContext->startOptionValueList();
if (setValueListContext->TRANSACTION_SYMBOL() != nullptr)
return "ISOLATION";
if (setValueListContext->PASSWORD_SYMBOL().size() > 0)
return "SET PASSWORD";
// TODO: handle the assignment at the caret instead of looking for specific names in the first assignment.
ParserRuleContext *variableName = nullptr;
if (setValueListContext->startOptionValueListFollowingOptionType() != nullptr) {
auto subContext = setValueListContext->startOptionValueListFollowingOptionType();
variableName = subContext->optionValueFollowingOptionType()->internalVariableName();
} else if (setValueListContext->optionValueNoOptionType() != nullptr)
variableName = setValueListContext->optionValueNoOptionType()->internalVariableName();
if (variableName != nullptr) {
std::string option = base::toupper(variableName->getText());
if (option == "SQL_SLAVE_SKIP_COUNTER")
return "SET GLOBAL SQL_SLAVE_SKIP_COUNTER";
if (option == "SQL_LOG_BIN")
return "SET SQL_LOG_BIN";
}
return "SET";
}
case MySQLParser::RuleLoadStatement: {
auto loadStatementContext = dynamic_cast<MySQLParser::LoadStatementContext *>(context);
if (loadStatementContext->dataOrXml()->DATA_SYMBOL() != nullptr)
return "LOAD DATA";
return "LOAD XML";
}
case MySQLParser::RulePredicate:
if (context->children.size() > 2) {
if (isToken(context->children[1], MySQLLexer::NOT_SYMBOL) ||
isToken(context->children[1], MySQLLexer::NOT2_SYMBOL)) {
// For NOT BETWEEN, NOT LIKE, NOT IN, NOT REGEXP.
auto predicateContext = dynamic_cast<MySQLParser::PredicateOperationsContext *>(context->children[2]);
return "NOT " + base::toupper(predicateContext->children[0]->getText());
}
if (isToken(context->children[1], MySQLLexer::SOUNDS_SYMBOL))
return "SOUNDS LIKE";
}
break;
case MySQLParser::RuleTableAdministrationStatement: {
auto adminStatementContext = dynamic_cast<MySQLParser::TableAdministrationStatementContext *>(context);
if (adminStatementContext->type != nullptr)
return tokenToTopic[adminStatementContext->type->getType()];
break;
}
case MySQLParser::RulePreparedStatement: {
auto preparedContext = dynamic_cast<MySQLParser::PreparedStatementContext *>(context);
size_t type = 0;
if (preparedContext->type != nullptr)
type = preparedContext->type->getType();
if (type == MySQLLexer::PREPARE_SYMBOL)
return "PREPARE";
if (type == MySQLLexer::DEALLOCATE_SYMBOL || type == MySQLLexer::DROP_SYMBOL)
return "DEALLOCATE PREPARE";
break;
}
case MySQLParser::RuleReplicationStatement: {
auto replicationContext = dynamic_cast<MySQLParser::ReplicationStatementContext *>(context);
if (replicationContext->PURGE_SYMBOL() != nullptr)
return "PURGE BINARY LOGS";
if (replicationContext->RESET_SYMBOL() != nullptr &&
(replicationContext->resetOption().empty() || replicationContext->resetOption()[0]->option == nullptr))
return "RESET";
break;
}
case MySQLParser::RuleResetOption: {
auto optionContext = dynamic_cast<MySQLParser::ResetOptionContext *>(context);
if (isToken(optionContext->option, MySQLLexer::MASTER_SYMBOL))
return "RESET MASTER";
if (isToken(optionContext->option, MySQLLexer::SLAVE_SYMBOL))
return "RESET SLAVE";
return "RESET";
}
case MySQLParser::RuleShowStatement: {
auto showContext = dynamic_cast<MySQLParser::ShowStatementContext *>(context);
if (showContext->value == nullptr) {
if (showContext->charset() != nullptr)
return "SHOW CHARACTER SET";
return "SHOW";
}
switch (showContext->value->getType()) {
case MySQLLexer::TABLE_SYMBOL:
return "SHOW TABLE STATUS";
case MySQLLexer::SLAVE_SYMBOL:
if (showContext->HOSTS_SYMBOL() != nullptr)
return "SHOW SLAVE HOSTS";
if (showContext->STATUS_SYMBOL() != nullptr)
return "SHOW SLAVE STATUS";
break;
case MySQLLexer::CREATE_SYMBOL: {
if (showContext->object != nullptr)
return "SHOW CREATE " + base::toupper(showContext->object->getText());
break;
}
case MySQLLexer::PROCEDURE_SYMBOL:
if (showContext->STATUS_SYMBOL() != nullptr)
return "SHOW PROCEDURE STATUS";
if (showContext->CODE_SYMBOL() != nullptr)
return "SHOW PROCEDURE CODE";
break;
case MySQLLexer::FUNCTION_SYMBOL:
if (showContext->STATUS_SYMBOL() != nullptr)
return "SHOW FUNCTION STATUS";
if (showContext->CODE_SYMBOL() != nullptr)
return "SHOW FUNCTION CODE";
break;
default:
return tokenToTopic[showContext->value->getType()];
}
break;
}
case MySQLParser::RuleTableConstraintDef: {
auto definitionContext = dynamic_cast<MySQLParser::TableConstraintDefContext *>(context);
if (definitionContext->type != nullptr && definitionContext->type->getType() == MySQLLexer::FOREIGN_SYMBOL)
return "CONSTRAINT";
if (definitionContext->checkConstraint() != nullptr)
return "CONSTRAINT"; // TODO: There's no own topic for check constraints so far. Update when that is available.
break;
}
case MySQLParser::RuleHelpCommand:
return "HELP COMMAND";
case MySQLParser::RuleSimpleExpr:
if (!context->children.empty() && antlrcpp::is<tree::TerminalNode *>(context->children[0])) {
size_t type = dynamic_cast<tree::TerminalNode *>(context->children[0])->getSymbol()->getType();
switch (type) {
case MySQLLexer::MATCH_SYMBOL:
return "MATCH AGAINST";
case MySQLLexer::CONVERT_SYMBOL:
return "CONVERT";
case MySQLLexer::DEFAULT_SYMBOL:
return "DEFAULT";
case MySQLLexer::CASE_SYMBOL:
return "CASE OPERATOR";
}
}
break;
case MySQLParser::RuleEngineRef: {
std::string engine = base::tolower(context->getText());
if (engine == "merge" || engine == "mrg_myisam")
return "MERGE";
break;
}
case MySQLParser::RuleSlave:
if (!context->children.empty())
return base::toupper(context->children[0]->getText()) + " SLAVE";
break;
case MySQLParser::RuleDataType: {
auto typeContext = dynamic_cast<MySQLParser::DataTypeContext *>(context);
if (typeContext->nchar() != nullptr)
return "CHAR";
std::string topic;
switch (typeContext->type->getType()) {
case MySQLLexer::DOUBLE_SYMBOL:
if (typeContext->PRECISION_SYMBOL() != nullptr)
return "DOUBLE PRECISION";
return "DOUBLE";
case MySQLLexer::SET_SYMBOL:
return "SET DATA TYPE";
case MySQLLexer::YEAR_SYMBOL:
return "YEAR DATA TYPE";
case MySQLLexer::BOOL_SYMBOL:
return "BOOLEAN";
case MySQLLexer::CHAR_SYMBOL:
case MySQLLexer::NCHAR_SYMBOL:
case MySQLLexer::NATIONAL_SYMBOL:
case MySQLLexer::VARCHAR_SYMBOL:
case MySQLLexer::NVARCHAR_SYMBOL:
case MySQLLexer::VARYING_SYMBOL:
if (typeContext->VARYING_SYMBOL() != nullptr || typeContext->VARCHAR_SYMBOL() != nullptr)
return "VARCHAR";
if (typeContext->charsetWithOptBinary() != nullptr &&
typeContext->charsetWithOptBinary()->BYTE_SYMBOL() != nullptr)
return "CHAR BYTE";
return "CHAR";
default:
topic = base::toupper(typeContext->type->getText());
break;
}
if (topicExists(helpContext->serverVersion(), topic))
return topic;
break; // Not all data types have an own topic.
}
case MySQLParser::RuleFromClause: {
auto keylistContext = dynamic_cast<MySQLParser::FromClauseContext *>(context);
if (keylistContext->DUAL_SYMBOL() != nullptr)
return "DUAL";
break;
}
case MySQLParser::RuleTransactionCharacteristics: {
auto characteristicsContext = dynamic_cast<MySQLParser::TransactionCharacteristicsContext *>(context);
if (characteristicsContext->isolationLevel() != nullptr)
return "ISOLATION";
break;
}
case MySQLParser::RuleSubstringFunction: {
// A case where we might have a synonym, so we need to check the text actually.
auto substringContext = dynamic_cast<MySQLParser::SubstringFunctionContext *>(context);
return base::toupper(substringContext->SUBSTRING_SYMBOL()->getText());
break;
}
case MySQLParser::RuleAlterStatement: {
auto alterContext = dynamic_cast<MySQLParser::AlterStatementContext *>(context);
if (alterContext->alterTable() != nullptr)
return "ALTER TABLE";
if (alterContext->alterDatabase() != nullptr)
return "ALTER DATABASE";
if (alterContext->PROCEDURE_SYMBOL() != nullptr)
return "ALTER PROCEDURE";
if (alterContext->FUNCTION_SYMBOL() != nullptr)
return "ALTER FUNCTION";
if (alterContext->alterView() != nullptr)
return "ALTER VIEW";
if (alterContext->alterEvent() != nullptr)
return "ALTER EVENT";
if (alterContext->alterTablespace() != nullptr || alterContext->alterUndoTablespace() != nullptr)
return "ALTER TABLESPACE";
if (alterContext->alterLogfileGroup() != nullptr)
return "ALTER LOGFILE GROUP";
if (alterContext->alterServer() != nullptr)
return "ALTER SERVER";
if (alterContext->INSTANCE_SYMBOL() != nullptr)
return "ALTER INSTANCE";
break;
}
case MySQLParser::RuleCreateStatement: {
auto createContext = dynamic_cast<MySQLParser::CreateStatementContext *>(context);
if (createContext->createDatabase() != nullptr)
return "CREATE DATABASE";
if (createContext->createTable() != nullptr)
return "CREATE TABLE";
if (createContext->createFunction() != nullptr)
return "CREATE FUNCTION";
if (createContext->createProcedure() != nullptr)
return "CREATE PROCEDURE";
if (createContext->createUdf() != nullptr)
return "CREATE FUNCTION UDF";
if (createContext->createLogfileGroup() != nullptr)
return "CREATE LOGFILE GROUP";
if (createContext->createView() != nullptr)
return "CREATE VIEW";
if (createContext->createTrigger() != nullptr)
return "CREATE TRIGGER";
if (createContext->createIndex() != nullptr)
return "CREATE INDEX";
if (createContext->createServer() != nullptr)
return "CREATE SERVER";
if (createContext->createTablespace() != nullptr)
return "CREATE TABLESPACE";
if (createContext->createEvent() != nullptr)
return "CREATE EVENT";
if (createContext->createRole() != nullptr)
return "CREATE ROLE";
if (createContext->createSpatialReference() != nullptr)
return "CREATE SPATIALREFERENCE SYSTEM";
if (createContext->createUndoTablespace() != nullptr)
return "CREATE TABLESPACE";
break;
}
case MySQLParser::RuleDropStatement: {
auto dropContext = dynamic_cast<MySQLParser::DropStatementContext *>(context);
if (dropContext->dropDatabase() != nullptr)
return "DROP DATABASE";
if (dropContext->dropEvent() != nullptr)
return "DROP EVENT";
if (dropContext->dropFunction() != nullptr) // We cannot make a distinction between FUNCTION and FUNCTION UDF.
return "DROP FUNCTION";
if (dropContext->dropProcedure() != nullptr)
return "DROP PROCEDURE";
if (dropContext->dropIndex() != nullptr)
return "DROP INDEX";
if (dropContext->dropLogfileGroup() != nullptr)
return "DROP LOGFILE GROUP";
if (dropContext->dropServer() != nullptr)
return "DROP SERVER";
if (dropContext->dropTable() != nullptr)
return "DROP TABLE";
if (dropContext->dropTableSpace() != nullptr)
return "DROP TABLESPACE";
if (dropContext->dropTrigger() != nullptr)
return "DROP TRIGGER";
if (dropContext->dropView() != nullptr)
return "DROP VIEW";
if (dropContext->dropRole() != nullptr)
return "DROP ROLE";
if (dropContext->dropSpatialReference() != nullptr)
return "DROP SPATIALREFERENCE SYSTEM";
if (dropContext->dropUndoTablespace() != nullptr)
return "DROP TABLESPACE";
break;
}
}
tree = tree->parent;
}
return "";
}
//----------------------------------------------------------------------------------------------------------------------