Completion_list complete()

in mysqlshdk/shellcore/provider_sql.cc [217:483]


  Completion_list complete(mysqlshdk::Sql_completion_result &&result) const {
    Completion_list list;
    const auto add_from_set = [&list](Names *s) {
      while (!s->empty()) {
        list.emplace_back(std::move(s->extract(s->begin()).value()));
      }
    };
    const auto quote = [&prefix = std::as_const(result.context.prefix)](
                           const std::string &s, bool as_string) {
      if (prefix.quoted_as_identifier) {
        return shcore::quote_identifier(s, prefix.quote);
      } else if (as_string && prefix.quoted_as_string) {
        return shcore::quote_string(s, prefix.quote);
      } else {
        // we quote here with '`' characters, they are still valid even if
        // ANSI_QUOTES is enabled
        return shcore::quote_identifier_if_needed(s);
      }
    };
    const auto quote_identifier = [&quote](const std::string &s) {
      return quote(s, false);
    };
    const auto quote_string_or_identifier = [&quote](const std::string &s) {
      return quote(s, true);
    };
    const auto add_identifiers_and_quote_if_needed =
        [&list, &quote_identifier](const auto &container) {
          for (const auto &item : container) {
            list.emplace_back(quote_identifier(item));
          }
        };
    const auto add_identifiers =
        [&list, &quote_identifier,
         &prefix = std::as_const(result.context.prefix.as_identifier)](
            const auto &container, bool as_function_call = false) {
          for (const auto &item : find_prefix_ci(container, prefix)) {
            auto name = quote_identifier(item->name());

            if (as_function_call) {
              name += '(';
              name += ')';
            }

            list.emplace_back(std::move(name));
          }
        };
    const auto add_strings_or_identifiers =
        [&list, &quote_string_or_identifier,
         &prefix =
             std::as_const(result.context.prefix.as_string_or_identifier)](
            const auto &container) {
          for (const auto &item : find_prefix_ci(container, prefix)) {
            list.emplace_back(quote_string_or_identifier(item->name()));
          }
        };
    const auto add_identifiers_from = [&add_identifiers, this](
                                          const Names &schemas, auto source,
                                          bool as_function_call = false) {
      for (const auto &schema : schemas) {
        if (const auto &s = find(m_instance.schemas, schema)) {
          add_identifiers(s->*source, as_function_call);
        }
      }
    };
    const auto add_columns = [&add_identifiers, this](const Columns &columns) {
      for (const auto &schema : columns) {
        if (const auto &s = find(m_instance.schemas, schema.first)) {
          for (const auto &table : schema.second) {
            if (const auto &t = find(s->tables, table)) {
              add_identifiers(t->columns);
            }

            if (const auto &v = find(s->views, table)) {
              add_identifiers(v->columns);
            }
          }
        }
      }
    };

    // these are already filtered
    add_from_set(&result.keywords);
    add_from_set(&result.system_functions);
    add_identifiers_and_quote_if_needed(result.tables);

    for (const auto &candidate : result.candidates) {
      using Candidate = mysqlshdk::Sql_completion_result::Candidate;

      switch (candidate) {
        case Candidate::SCHEMA:
          add_identifiers(m_instance.schemas);
          break;

        case Candidate::TABLE:
          add_identifiers_from(result.tables_from, &Instance::Schema::tables);
          break;

        case Candidate::VIEW:
          add_identifiers_from(result.views_from, &Instance::Schema::views);
          break;

        case Candidate::COLUMN:
          add_columns(result.columns);
          break;

        case Candidate::INTERNAL_COLUMN:
          add_columns(result.internal_columns);
          break;

        case Candidate::PROCEDURE:
          add_identifiers_from(result.procedures_from,
                               &Instance::Schema::procedures, true);
          break;

        case Candidate::FUNCTION:
          add_identifiers_from(result.functions_from,
                               &Instance::Schema::functions, true);
          break;

        case Candidate::TRIGGER:
          add_identifiers_from(result.triggers_from,
                               &Instance::Schema::triggers);
          break;

        case Candidate::EVENT:
          add_identifiers_from(result.events_from, &Instance::Schema::events);
          break;

        case Candidate::ENGINE:
          add_identifiers(m_instance.engines);
          break;

        case Candidate::UDF:
          add_identifiers(m_instance.udfs, true);
          break;

        case Candidate::RUNTIME_FUNCTION:
          // if prefix was quoted then we cannot match any functions
          if (!result.context.prefix.quoted) {
            for (const auto &item :
                 find_prefix_ci(*m_instance.system_functions,
                                result.context.prefix.full_wide)) {
              list.emplace_back(item->name());
            }
          }
          break;

        case Candidate::LOGFILE_GROUP:
          add_identifiers(m_instance.logfile_groups);
          break;

        case Candidate::USER_VAR:
          // user variables can be quoted as strings or identifiers
          add_strings_or_identifiers(m_instance.user_variables);
          break;

        case Candidate::SYSTEM_VAR:
          // system variables can be quoted as identifiers, but if ANSI_QUOTES
          // is active, only "sys_var" (and not i.e. @@"sys_var") form is
          // recognized; since we don't know the context, we simply disallow
          // double quotes
          if (!result.context.prefix.quoted ||
              '`' == result.context.prefix.quote) {
            for (const auto &item :
                 find_prefix_ci(m_instance.system_variables,
                                result.context.prefix.as_identifier)) {
              auto name = item->name();

              if (result.context.prefix.quote) {
                name = shcore::quote_identifier(name);
              }

              list.emplace_back(std::move(name));
            }
          }
          break;

        case Candidate::TABLESPACE:
          add_identifiers(m_instance.tablespaces);
          break;

        case Candidate::USER: {
          // construct the prefix which matches the format of user accounts
          // stored in cache: 'user'@'host'
          std::string prefix;
          const auto &as_account = result.context.as_account;

          using Account_part = parsers::Sql_completion_result::Account_part;

          if (!as_account.user.full.empty()) {
            const auto add_part = [&prefix](const Account_part &part) {
              prefix += shcore::quote_string(part.unquoted, '\'');
            };

            add_part(as_account.user);

            if (as_account.has_at_sign) {
              prefix += '@';

              if (!as_account.host.full.empty()) {
                add_part(as_account.host);
              }
            }

            // remove the last quote to allow for partial matches
            if (prefix.back() == '\'') {
              prefix.pop_back();
            }
          }

          // recreate candidate based on quotes used by the user
          const auto quote_user = [&as_account](const std::string &name) {
            const auto quote_like_part = [](const std::string &s,
                                            const Account_part &part) {
              if (part.full.empty()) {
                return shcore::quote_string(s, '\'');
              } else if (!part.quoted) {
                return s;
              } else {
                if (part.quote == '`') {
                  return shcore::quote_identifier(s);
                } else {
                  return shcore::quote_string(s, part.quote);
                }
              }
            };

            const auto account = shcore::split_account(name);
            std::string quoted = quote_like_part(account.user, as_account.user);
            quoted += '@';
            // if host part was not provided, quote it like the user, to keep
            // the same style
            quoted += quote_like_part(account.host, as_account.host.full.empty()
                                                        ? as_account.user
                                                        : as_account.host);

            return quoted;
          };

          for (const auto &item : find_prefix_ci(m_instance.users, prefix)) {
            list.emplace_back(quote_user(item->name()));
          }
        } break;

        case Candidate::CHARSET:
          // character sets can be quoted as strings or identifiers
          add_strings_or_identifiers(m_instance.charsets);
          break;

        case Candidate::COLLATION:
          // collations can be quoted as strings or identifiers
          add_strings_or_identifiers(m_instance.collations);
          break;

        case Candidate::PLUGIN:
          add_identifiers(m_instance.plugins);
          break;

        case Candidate::LABEL:
          // already filtered
          add_identifiers_and_quote_if_needed(result.context.labels);
          break;
      }
    }

    return list;
  }