/*
 * Copyright (C) 2023 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.
 */

#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_CONNECTION_H_
#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_CONNECTION_H_

#include <sqlite3.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>

#include "perfetto/base/status.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"

namespace perfetto::trace_processor {

class SqliteDatabase;

// Wrapper class around a single |sqlite3*| connection.
//
// The goal of this class is to provide a one-stop-shop mechanism to use SQLite.
// Benefits of this include:
// 1) It allows us to add code which intercepts registration of functions
//    and tables and keeps track of this for later lookup.
// 2) Allows easily auditing the SQLite APIs we use making it easy to determine
//    what functionality we rely on.
class SqliteConnection {
 public:
  using Fn = void(sqlite3_context* ctx, int argc, sqlite3_value** argv);
  using AggregateFnStep = void(sqlite3_context* ctx,
                               int argc,
                               sqlite3_value** argv);
  using AggregateFnFinal = void(sqlite3_context* ctx);
  using WindowFnStep = void(sqlite3_context* ctx,
                            int argc,
                            sqlite3_value** argv);
  using WindowFnInverse = void(sqlite3_context* ctx,
                               int argc,
                               sqlite3_value** argv);
  using WindowFnValue = void(sqlite3_context* ctx);
  using WindowFnFinal = void(sqlite3_context* ctx);
  using FnCtxDestructor = void(void*);

  // Wrapper class for SQLite's |sqlite3_stmt| struct and associated functions.
  struct PreparedStatement {
   public:
    bool Step();
    bool IsDone() const;

    const char* original_sql() const;

    // Computed and cached on first call.
    const char* sql();

    const base::Status& status() const { return status_; }
    sqlite3_stmt* sqlite_stmt() const { return stmt_.get(); }

   private:
    friend class SqliteConnection;

    explicit PreparedStatement(ScopedStmt, SqlSource);

    ScopedStmt stmt_;
    ScopedSqliteString expanded_sql_;
    SqlSource sql_source_;
    base::Status status_ = base::OkStatus();
  };

  // Creates a fresh |SqliteDatabase| and returns a connection attached to it.
  // The database lives only as long as some connection has it open.
  static std::unique_ptr<SqliteConnection> CreateConnectionToNewDatabase();

  // Constructs a connection attached to an existing |SqliteDatabase|. Most
  // callers should prefer |CreateConnectionToNewDatabase| or |Fork|; this is
  // the "raw" entry point used by higher-level wrappers (e.g.
  // |PerfettoSqlConnection|) that already own a shared database handle.
  explicit SqliteConnection(std::shared_ptr<SqliteDatabase> database);

  ~SqliteConnection();

  SqliteConnection(SqliteConnection&&) noexcept = delete;
  SqliteConnection& operator=(SqliteConnection&&) = delete;

  // Returns a new connection attached to the same |SqliteDatabase| as this
  // one. The new connection has its own |sqlite3*| handle and its own
  // per-connection state (registered functions, vtab modules etc.); only the
  // underlying in-memory store is shared.
  std::unique_ptr<SqliteConnection> Fork();

  // Prepares a SQLite statement for the given SQL.
  PreparedStatement PrepareStatement(SqlSource);

  // Registers a C++ function to be runnable from SQL.
  base::Status RegisterFunction(const char* name,
                                int argc,
                                Fn* fn,
                                void* ctx,
                                FnCtxDestructor* ctx_destructor,
                                bool deterministic);

  // Registers a C++ aggregate function to be runnable from SQL.
  base::Status RegisterAggregateFunction(const char* name,
                                         int argc,
                                         AggregateFnStep* step,
                                         AggregateFnFinal* final,
                                         void* ctx,
                                         FnCtxDestructor* ctx_destructor,
                                         bool deterministic);

  // Registers a C++ window function to be runnable from SQL.
  base::Status RegisterWindowFunction(const char* name,
                                      int argc,
                                      WindowFnStep* step,
                                      WindowFnInverse* inverse,
                                      WindowFnValue* value,
                                      WindowFnFinal* final,
                                      void* ctx,
                                      FnCtxDestructor* ctx_destructor,
                                      bool deterministic);

  // Unregisters a C++ function from SQL.
  base::Status UnregisterFunction(const char* name, int argc);

  // Registers a SQLite virtual table module with the given name.
  using ModuleContextDestructor = void(void*);
  void RegisterVirtualTableModule(const std::string& module_name,
                                  const sqlite3_module* module,
                                  void* ctx,
                                  ModuleContextDestructor destructor);

  // Sets a callback to be called when a transaction is committed.
  //
  // Returns the prior context object passed to a previous invocation of this
  // function.
  //
  // See https://www.sqlite.org/c3ref/commit_hook.html for more details.
  using CommitCallback = int(void*);
  void* SetCommitCallback(CommitCallback callback, void* ctx);

  // Sets a callback to be called when a transaction is rolled back.
  //
  // Returns the prior context object passed to a previous invocation of this
  // function.
  //
  // See https://www.sqlite.org/c3ref/commit_hook.html for more details.
  using RollbackCallback = void(void*);
  void* SetRollbackCallback(RollbackCallback callback, void* ctx);

  sqlite3* db() const { return db_.get(); }

 private:
  std::optional<uint32_t> GetErrorOffset() const;

  std::shared_ptr<SqliteDatabase> database_;
  ScopedDb db_;
};

}  // namespace perfetto::trace_processor

#endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_CONNECTION_H_
