in backend/wbprivate/sqlide/wb_sql_editor_help.cpp [700:1204]
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 "";
}