/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/trace_processor/sqlite/create_view_function.h"

#include <numeric>

#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/sqlite/create_function_internal.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
#include "src/trace_processor/tp_metatrace.h"
#include "src/trace_processor/util/status_macros.h"

namespace perfetto {
namespace trace_processor {

namespace {

class CreatedViewFunction : public SqliteTable {
 public:
  class Cursor : public SqliteTable::Cursor {
   public:
    explicit Cursor(CreatedViewFunction* table);
    ~Cursor() override;

    int Filter(const QueryConstraints& qc,
               sqlite3_value**,
               FilterHistory) override;
    int Next() override;
    int Eof() override;
    int Column(sqlite3_context* context, int N) override;

   private:
    ScopedStmt scoped_stmt_;
    sqlite3_stmt* stmt_ = nullptr;
    CreatedViewFunction* table_ = nullptr;
    bool is_eof_ = false;
  };

  CreatedViewFunction(sqlite3*, void*);
  ~CreatedViewFunction() override;

  base::Status Init(int argc, const char* const* argv, Schema*) override;
  std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
  int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) override;

  static void Register(sqlite3* db) {
    SqliteTable::Register<CreatedViewFunction>(
        db, nullptr, "internal_view_function_impl", false, true);
  }

 private:
  Schema CreateSchema();

  sqlite3* db_ = nullptr;

  Prototype prototype_;
  std::vector<Prototype::Argument> return_values_;

  std::string prototype_str_;
  std::string sql_defn_str_;
};

CreatedViewFunction::CreatedViewFunction(sqlite3* db, void*) : db_(db) {}
CreatedViewFunction::~CreatedViewFunction() = default;

base::Status CreatedViewFunction::Init(int argc,
                                       const char* const* argv,
                                       Schema* schema) {
  // The first three args are SQLite ones which we ignore.
  PERFETTO_CHECK(argc == 6);

  prototype_str_ = argv[3];
  std::string return_prototype_str = argv[4];
  sql_defn_str_ = argv[5];

  // SQLite gives us strings with quotes included (i.e. 'string'). Strip these
  // from the front and back.
  prototype_str_ = prototype_str_.substr(1, prototype_str_.size() - 2);
  return_prototype_str =
      return_prototype_str.substr(1, return_prototype_str.size() - 2);
  sql_defn_str_ = sql_defn_str_.substr(1, sql_defn_str_.size() - 2);

  // Parse all the arguments into a more friendly form.
  base::Status status =
      ParsePrototype(base::StringView(prototype_str_), prototype_);
  if (!status.ok()) {
    return base::ErrStatus("CREATE_VIEW_FUNCTION[prototype=%s]: %s",
                           prototype_str_.c_str(), status.c_message());
  }

  // Parse the return type into a enum format.
  status = ParseArgs(return_prototype_str, return_values_);
  if (!status.ok()) {
    return base::ErrStatus(
        "CREATE_VIEW_FUNCTION[prototype=%s, return=%s]: unknown return type "
        "specified",
        prototype_str_.c_str(), return_prototype_str.c_str());
  }

  // Verify that the provided SQL prepares to a statement correctly.
  ScopedStmt stmt;
  sqlite3_stmt* raw_stmt = nullptr;
  int ret = sqlite3_prepare_v2(db_, sql_defn_str_.data(),
                               static_cast<int>(sql_defn_str_.size()),
                               &raw_stmt, nullptr);
  stmt.reset(raw_stmt);
  if (ret != SQLITE_OK) {
    return base::ErrStatus(
        "%s: Failed to prepare SQL statement for function. "
        "Check the SQL defintion this function for syntax errors. "
        "(SQLite error: %s).",
        prototype_.function_name.c_str(), sqlite3_errmsg(db_));
  }

  // Verify that every argument name in the function appears in the
  // argument list.
  //
  // We intentionally loop from 1 to |used_param_count| because SQL
  // parameters are 1-indexed *not* 0-indexed.
  int used_param_count = sqlite3_bind_parameter_count(stmt.get());
  for (int i = 1; i <= used_param_count; ++i) {
    const char* name = sqlite3_bind_parameter_name(stmt.get(), i);

    if (!name) {
      return base::ErrStatus(
          "%s: \"Nameless\" SQL parameters cannot be used in the SQL "
          "statements of view functions.",
          prototype_.function_name.c_str());
    }

    if (!base::StringView(name).StartsWith("$")) {
      return base::ErrStatus(
          "%s: invalid parameter name %s used in the SQL definition of "
          "the view function: all parameters must be prefixed with '$' not ':' "
          "or '@'.",
          prototype_.function_name.c_str(), name);
    }

    auto it =
        std::find_if(prototype_.arguments.begin(), prototype_.arguments.end(),
                     [name](const Prototype::Argument& arg) {
                       return arg.dollar_name == name;
                     });
    if (it == prototype_.arguments.end()) {
      return base::ErrStatus(
          "%s: parameter %s does not appear in the list of arguments in the "
          "prototype of the view function.",
          prototype_.function_name.c_str(), name);
    }
  }

  // Verify that the prepared statement column count matches the return
  // count.
  uint32_t col_count = static_cast<uint32_t>(sqlite3_column_count(stmt.get()));
  if (col_count != return_values_.size()) {
    return base::ErrStatus(
        "%s: number of return values %u does not match SQL statement column "
        "count %zu.",
        prototype_.function_name.c_str(), col_count, return_values_.size());
  }

  // Verify that the return names matches the prepared statment column names.
  for (uint32_t i = 0; i < col_count; ++i) {
    const char* name = sqlite3_column_name(stmt.get(), static_cast<int>(i));
    if (name != return_values_[i].name) {
      return base::ErrStatus(
          "%s: column %s at index %u does not match return value name %s.",
          prototype_.function_name.c_str(), name, i,
          return_values_[i].name.c_str());
    }
  }

  // Now we've parsed prototype and return values, create the schema.
  *schema = CreateSchema();

  return base::OkStatus();
}

SqliteTable::Schema CreatedViewFunction::CreateSchema() {
  std::vector<Column> columns;
  for (size_t i = 0; i < return_values_.size(); ++i) {
    const auto& ret = return_values_[i];
    columns.push_back(Column(columns.size(), ret.name, ret.type));
  }
  for (size_t i = 0; i < prototype_.arguments.size(); ++i) {
    const auto& arg = prototype_.arguments[i];

    // Add the "in_" prefix to every argument param to avoid clashes between the
    // output and input parameters.
    columns.push_back(Column(columns.size(), "in_" + arg.name, arg.type, true));
  }

  std::vector<size_t> primary_keys(return_values_.size());
  std::iota(primary_keys.begin(), primary_keys.end(), 0);

  return SqliteTable::Schema(std::move(columns), std::move(primary_keys));
}

std::unique_ptr<SqliteTable::Cursor> CreatedViewFunction::CreateCursor() {
  return std::unique_ptr<Cursor>(new Cursor(this));
}

int CreatedViewFunction::BestIndex(const QueryConstraints& qc,
                                   BestIndexInfo* info) {
  // Only accept constraint sets where every input parameter has a value.
  size_t seen_hidden_constraints = 0;
  for (size_t i = 0; i < qc.constraints().size(); ++i) {
    const auto& cs = qc.constraints()[i];
    if (!schema().columns()[static_cast<size_t>(cs.column)].hidden())
      continue;
    seen_hidden_constraints++;
  }
  if (seen_hidden_constraints < prototype_.arguments.size())
    return SQLITE_CONSTRAINT;

  for (size_t i = 0; i < info->sqlite_omit_constraint.size(); ++i) {
    size_t col = static_cast<size_t>(qc.constraints()[i].column);
    if (schema().columns()[col].hidden()) {
      info->sqlite_omit_constraint[i] = true;
    }
  }
  return SQLITE_OK;
}

CreatedViewFunction::Cursor::Cursor(CreatedViewFunction* table)
    : SqliteTable::Cursor(table), table_(table) {}

CreatedViewFunction::Cursor::~Cursor() = default;

int CreatedViewFunction::Cursor::Filter(const QueryConstraints& qc,
                                        sqlite3_value** argv,
                                        FilterHistory) {
  PERFETTO_TP_TRACE("CREATE_VIEW_FUNCTION", [this](metatrace::Record* r) {
    r->AddArg("Function", table_->prototype_.function_name.c_str());
  });

  auto col_to_arg_idx = [this](int col) {
    return static_cast<uint32_t>(col) -
           static_cast<uint32_t>(table_->return_values_.size());
  };

  size_t seen_hidden_constraints = 0;
  for (size_t i = 0; i < qc.constraints().size(); ++i) {
    const auto& cs = qc.constraints()[i];

    // Only consider hidden columns (i.e. input parameters) as we're delegating
    // the rest to SQLite.
    if (!table_->schema().columns()[static_cast<size_t>(cs.column)].hidden())
      continue;

    // We only support equality constraints as we're expecting "input arguments"
    // to our "function".
    if (!sqlite_utils::IsOpEq(cs.op)) {
      table_->SetErrorMessage(
          sqlite3_mprintf("%s: non-equality constraint passed",
                          table_->prototype_.function_name.c_str()));
      return SQLITE_ERROR;
    }

    const auto& arg = table_->prototype_.arguments[col_to_arg_idx(cs.column)];
    SqlValue::Type expected_type = arg.type;
    base::Status status = TypeCheckSqliteValue(argv[i], expected_type);
    if (!status.ok()) {
      table_->SetErrorMessage(
          sqlite3_mprintf("%s: argument %s (index %u) %s",
                          table_->prototype_.function_name.c_str(),
                          arg.name.c_str(), i, status.c_message()));
      return SQLITE_ERROR;
    }

    seen_hidden_constraints++;
  }

  // Verify that we saw one valid constriant for every input argument.
  if (seen_hidden_constraints < table_->prototype_.arguments.size()) {
    table_->SetErrorMessage(sqlite3_mprintf(
        "%s: missing value for input argument. Saw %u arguments but expected "
        "%u",
        table_->prototype_.function_name.c_str(), seen_hidden_constraints,
        table_->prototype_.arguments.size()));
    return SQLITE_ERROR;
  }

  // Prepare the SQL definition as a statement using SQLite.
  // TODO(lalitm): see if we can reuse this prepared statement rather than
  // creating it very time.
  // TODO(lalitm): measure and implement whether it would be a good idea to
  // forward constraints here when we build the nested query.
  int ret = sqlite3_prepare_v2(table_->db_, table_->sql_defn_str_.data(),
                               static_cast<int>(table_->sql_defn_str_.size()),
                               &stmt_, nullptr);
  scoped_stmt_.reset(stmt_);
  PERFETTO_CHECK(ret == SQLITE_OK);

  // Bind all the arguments to the appropriate places in the function.
  for (size_t i = 0; i < qc.constraints().size(); ++i) {
    const auto& cs = qc.constraints()[i];

    // Don't deal with any constraints on the output parameters for simplicty.
    // TODO(lalitm): reconsider this decision to allow more efficient queries:
    // we would need to wrap the query in a SELECT * FROM (...) WHERE constraint
    // like we do for SPAN JOIN.
    if (!table_->schema().columns()[static_cast<size_t>(cs.column)].hidden())
      continue;

    uint32_t index = col_to_arg_idx(cs.column);
    PERFETTO_DCHECK(index < table_->prototype_.arguments.size());

    const auto& arg = table_->prototype_.arguments[index];
    auto status = MaybeBindArgument(stmt_, table_->prototype_.function_name,
                                    arg, argv[i]);
    if (!status.ok()) {
      table_->SetErrorMessage(sqlite3_mprintf("%s", status.c_message()));
      return SQLITE_ERROR;
    }
  }

  ret = Next();
  if (ret != SQLITE_OK)
    return ret;

  return SQLITE_OK;
}

int CreatedViewFunction::Cursor::Next() {
  int ret = sqlite3_step(stmt_);
  is_eof_ = ret == SQLITE_DONE;
  if (ret != SQLITE_ROW && ret != SQLITE_DONE) {
    table_->SetErrorMessage(sqlite3_mprintf(
        "%s: SQLite error while stepping statement: %s",
        table_->prototype_.function_name.c_str(), sqlite3_errmsg(table_->db_)));
    return ret;
  }
  return SQLITE_OK;
}

int CreatedViewFunction::Cursor::Eof() {
  return is_eof_;
}

int CreatedViewFunction::Cursor::Column(sqlite3_context* ctx, int i) {
  size_t idx = static_cast<size_t>(i);
  if (idx < table_->return_values_.size()) {
    sqlite3_result_value(ctx, sqlite3_column_value(stmt_, i));
  } else {
    sqlite3_result_null(ctx);
  }
  return SQLITE_OK;
}

}  // namespace

base::Status CreateViewFunction::Run(CreateViewFunction::Context* ctx,
                                     size_t argc,
                                     sqlite3_value** argv,
                                     SqlValue&,
                                     Destructors&) {
  if (argc != 3) {
    return base::ErrStatus(
        "CREATE_VIEW_FUNCTION: invalid number of args; expected %u, received "
        "%zu",
        3u, argc);
  }

  sqlite3_value* prototype_value = argv[0];
  sqlite3_value* return_prototype_value = argv[1];
  sqlite3_value* sql_defn_value = argv[2];

  // Type check all the arguments.
  {
    auto type_check = [prototype_value](sqlite3_value* value,
                                        SqlValue::Type type, const char* desc) {
      base::Status status = TypeCheckSqliteValue(value, type);
      if (!status.ok()) {
        return base::ErrStatus("CREATE_VIEW_FUNCTION[prototype=%s]: %s %s",
                               sqlite3_value_text(prototype_value), desc,
                               status.c_message());
      }
      return base::OkStatus();
    };

    RETURN_IF_ERROR(type_check(prototype_value, SqlValue::Type::kString,
                               "function prototype (first argument)"));
    RETURN_IF_ERROR(type_check(return_prototype_value, SqlValue::Type::kString,
                               "return prototype (second argument)"));
    RETURN_IF_ERROR(type_check(sql_defn_value, SqlValue::Type::kString,
                               "SQL definition (third argument)"));
  }

  // Extract the arguments from the value wrappers.
  auto extract_string = [](sqlite3_value* value) -> const char* {
    return reinterpret_cast<const char*>(sqlite3_value_text(value));
  };
  const char* prototype_str = extract_string(prototype_value);
  const char* return_prototype_str = extract_string(return_prototype_value);
  const char* sql_defn_str = extract_string(sql_defn_value);

  base::StringView function_name;
  RETURN_IF_ERROR(ParseFunctionName(prototype_str, function_name));

  static constexpr char kSqlTemplate[] = R"""(
    DROP TABLE IF EXISTS %s;

    CREATE VIRTUAL TABLE %s
    USING INTERNAL_VIEW_FUNCTION_IMPL('%s', '%s', '%s');
  )""";
  std::string function_name_str = function_name.ToStdString();
  base::StackString<1024> sql(kSqlTemplate, function_name_str.c_str(),
                              function_name_str.c_str(), prototype_str,
                              return_prototype_str, sql_defn_str);

  ScopedSqliteString errmsg;
  char* errmsg_raw = nullptr;
  int ret = sqlite3_exec(ctx->db, sql.c_str(), nullptr, nullptr, &errmsg_raw);
  errmsg.reset(errmsg_raw);
  if (ret != SQLITE_OK)
    return base::ErrStatus("%s", errmsg.get());

  // CREATE_VIEW_FUNCTION doesn't have a return value so just don't sent |out|.
  return base::OkStatus();
}

void CreateViewFunction::RegisterTable(sqlite3* db) {
  CreatedViewFunction::Register(db);
}

}  // namespace trace_processor
}  // namespace perfetto
