// file      : odb/sqlite/database.hxx
// license   : GNU GPL v2; see accompanying LICENSE file

#ifndef ODB_SQLITE_DATABASE_HXX
#define ODB_SQLITE_DATABASE_HXX

#include <odb/pre.hxx>

#include <sqlite3.h> // SQLITE_OPEN_*

#include <memory>     // std::unique_ptr
#include <string>
#include <iosfwd>     // std::ostream
#include <functional> // std::function

#include <odb/database.hxx>

#include <odb/sqlite/version.hxx>
#include <odb/sqlite/forward.hxx>
#include <odb/sqlite/query.hxx>
#include <odb/sqlite/tracer.hxx>
#include <odb/sqlite/connection.hxx>
#include <odb/sqlite/connection-factory.hxx>
#include <odb/sqlite/transaction-impl.hxx>

#include <odb/sqlite/details/export.hxx>

namespace odb
{
  namespace sqlite
  {
    class transaction_impl;

    class LIBODB_SQLITE_EXPORT database: public odb::database
    {
    public:
      using connection_type = sqlite::connection;

      explicit
      database (std::string name,
                int flags = SQLITE_OPEN_READWRITE,
                /*bool*/ int foreign_keys = true,
                std::string vfs = "",
                std::unique_ptr<connection_factory> = nullptr);

      // Note that the connection configurator is called during the connection
      // construction and the only valid operations it can perform on the
      // connection is call the connection::execute() to execute generic
      // statements (typically PRAGMA) or to call SQLite API functions on the
      // underlying connection handle. In particular, it may not start a
      // transaction via the connection::begin() API (but may start one
      // manually via execute("BEGIN")).
      //
      database (std::string name,
                int flags,
                std::function<void (connection_type&)> connection_configurator,
                std::string vfs = "",
                std::unique_ptr<connection_factory> = nullptr);

#ifdef _WIN32
      explicit
      database (const std::wstring& name,
                int flags = SQLITE_OPEN_READWRITE,
                /*bool*/ int foreign_keys = true,
                std::string vfs = "",
                std::unique_ptr<connection_factory> = nullptr);

      database (const std::wstring& name,
                int flags,
                std::function<void (connection_type&)> connection_configurator,
                std::string vfs = "",
                std::unique_ptr<connection_factory> = nullptr);
#endif

      // Extract the database parameters from the command line. The
      // following options are recognized:
      //
      // --database
      // --create
      // --read-only
      // --options-file
      //
      // For more information, see the output of the print_usage() function
      // below. If erase is true, the above options are removed from the argv
      // array and the argc count is updated accordingly. The command line
      // options override the flags passed as an argument. This constructor
      // may throw the cli_exception exception.
      //
      database (int& argc, char* argv[], bool erase = false,
                int flags = SQLITE_OPEN_READWRITE,
                /*bool*/ int foreign_keys = true,
                std::string vfs = "",
                std::unique_ptr<connection_factory> = nullptr);

      database (int& argc, char* argv[], bool erase,
                int flags,
                std::function<void (connection_type&)> connection_configurator,
                std::string vfs = "",
                std::unique_ptr<connection_factory> = nullptr);

      // Attach to the specified connection a database with the specified name
      // as the specified schema. Good understanding of the SQLite
      // ATTACH/DETACH DATABASE statements semantics and ODB connection
      // management is strongly recommended when using this mechanism.
      //
      // The resulting database instance is referred to as an "attached
      // database" and the connection it returns as an "attached connection"
      // (which is just a proxy for the main connection). Database operations
      // executed on the attached database or attached connection are
      // automatically translated to refer to the specified schema rather than
      // "main". For uniformity attached databases can also be created for the
      // pre-attached "main" and "temp" schemas (in this case name can be
      // anything).
      //
      // The automatic translation of the statements relies on their text
      // having references to top-level database entities (tables, indexes,
      // etc) qualified with the "main" schema. To achieve this, compile your
      // headers with `--schema main` and, if using schema migration, with
      // `--schema-version-table main.schema_version`. You must also not use
      // "main" as an object/table alias in views of native statements. For
      // optimal translation performance use 4-character schema names.
      //
      // The main connection and attached to it databases and connections are
      // all meant to be used within the same thread. In particular, the
      // attached database holds a counted reference to the main connection
      // which means the connection will not be released until all the
      // attached to this connection databases are destroyed.
      //
      // Note that in this model the attached databases are attached to the
      // main connection, not to the (main) database, which mimics the
      // underlying semantics of SQLite. An alternative model would have been
      // to notionally attach the databases to the main database and under the
      // hood automatically attach them to each returned connection. While
      // this may seem like a more convenient model in some cases, it is also
      // less flexible: the current model allows attaching a different set of
      // databases to different connections, attaching them on demand as the
      // transaction progresses, etc. Also, the more convenient model can be
      // implemented on top of this model by deriving an application-specific
      // database class and/or providing custom connection factories.
      //
      // Note that unless the name is a URI with appropriate mode, it is
      // opened with the SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE flags. So if
      // you want just SQLITE_OPEN_READWRITE, then you will need to verify its
      // existence manually prior to calling this constructor.
      //
      // Note that attaching/detaching databases within a transaction is only
      // supported since SQLite 3.21.0.
      //
      database (const connection_ptr&,
                const std::string& name,
                const std::string& schema,
                std::unique_ptr<attached_connection_factory> = nullptr);

      // The database is automatically detached on destruction but a failure
      // to detach is ignored. To detect such a failure perform explicit
      // detach. For uniformity detaching a main database is a no-op.
      //
      void
      detach ();

      // Return the main database of an attached database. If this database
      // is main, return itself.
      //
      database&
      main_database ();

      // Move-constructible but not move-assignable.
      //
      // Note: noexcept is not specified since odb::database(odb::database&&)
      // can throw.
      //
      database (database&&);

      static void
      print_usage (std::ostream&);

    public:
      const std::string&
      name () const
      {
        return name_;
      }

      // Schema name under which this database was attached or empty for the
      // main database.
      //
      const std::string&
      schema () const
      {
        return schema_;
      }

      int
      flags () const
      {
        return flags_;
      }

      const std::string&
      vfs () const
      {
        return vfs_;
      }

      const std::function<void (connection_type&)>&
      connection_configurator () const
      {
        return connection_configurator_;
      }

      // Object persistence API.
      //
    public:

      // Make the object persistent.
      //
      template <typename T>
      typename object_traits<T>::id_type
      persist (T& object);

      template <typename T>
      typename object_traits<T>::id_type
      persist (const T& object);

      template <typename T>
      typename object_traits<T>::id_type
      persist (T* obj_ptr);

      template <typename T, template <typename> class P>
      typename object_traits<T>::id_type
      persist (const P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      typename object_traits<T>::id_type
      persist (const P<T, A1>& obj_ptr);

      template <typename T, template <typename> class P>
      typename object_traits<T>::id_type
      persist (P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      typename object_traits<T>::id_type
      persist (P<T, A1>& obj_ptr);

      template <typename T>
      typename object_traits<T>::id_type
      persist (const typename object_traits<T>::pointer_type& obj_ptr);

      // Load an object. Throw object_not_persistent if not found.
      //
      template <typename T>
      typename object_traits<T>::pointer_type
      load (const typename object_traits<T>::id_type& id);

      template <typename T>
      void
      load (const typename object_traits<T>::id_type& id, T& object);

      // Load (or reload, if it is already loaded) a section of an object.
      //
      template <typename T>
      void
      load (T& object, section&);

      // Reload an object.
      //
      template <typename T>
      void
      reload (T& object);

      template <typename T>
      void
      reload (T* obj_ptr);

      template <typename T, template <typename> class P>
      void
      reload (const P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      void
      reload (const P<T, A1>& obj_ptr);

      template <typename T, template <typename> class P>
      void
      reload (P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      void
      reload (P<T, A1>& obj_ptr);

      template <typename T>
      void
      reload (const typename object_traits<T>::pointer_type& obj_ptr);

      // Loan an object if found. Return NULL/false if not found.
      //
      template <typename T>
      typename object_traits<T>::pointer_type
      find (const typename object_traits<T>::id_type& id);

      template <typename T>
      bool
      find (const typename object_traits<T>::id_type& id, T& object);

      // Update the state of a modified objects.
      //
      template <typename T>
      void
      update (T& object);

      template <typename T>
      void
      update (T* obj_ptr);

      template <typename T, template <typename> class P>
      void
      update (const P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      void
      update (const P<T, A1>& obj_ptr);

      template <typename T, template <typename> class P>
      void
      update (P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      void
      update (P<T, A1>& obj_ptr);

      template <typename T>
      void
      update (const typename object_traits<T>::pointer_type& obj_ptr);

      // Update a section of an object. Throws the section_not_loaded
      // exception if the section is not loaded. Note also that this
      // function does not clear the changed flag if it is set.
      //
      template <typename T>
      void
      update (const T& object, const section&);

      // Make the object transient. Throw object_not_persistent if not
      // found.
      //
      template <typename T>
      void
      erase (const typename object_traits<T>::id_type& id);

      template <typename T>
      void
      erase (T& object);

      template <typename T>
      void
      erase (T* obj_ptr);

      template <typename T, template <typename> class P>
      void
      erase (const P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      void
      erase (const P<T, A1>& obj_ptr);

      template <typename T, template <typename> class P>
      void
      erase (P<T>& obj_ptr);

      template <typename T, typename A1, template <typename, typename> class P>
      void
      erase (P<T, A1>& obj_ptr);

      template <typename T>
      void
      erase (const typename object_traits<T>::pointer_type& obj_ptr);

      // Erase multiple objects matching a query predicate.
      //
      template <typename T>
      unsigned long long
      erase_query ();

      template <typename T>
      unsigned long long
      erase_query (const char*);

      template <typename T>
      unsigned long long
      erase_query (const std::string&);

      template <typename T>
      unsigned long long
      erase_query (const sqlite::query_base&);

      template <typename T>
      unsigned long long
      erase_query (const odb::query_base&);

      // Query API.
      //
      template <typename T>
      result<T>
      query ();

      template <typename T>
      result<T>
      query (const char*);

      template <typename T>
      result<T>
      query (const std::string&);

      template <typename T>
      result<T>
      query (const sqlite::query_base&);

      template <typename T>
      result<T>
      query (const odb::query_base&);

      // Query one API.
      //
      template <typename T>
      typename result<T>::pointer_type
      query_one ();

      template <typename T>
      bool
      query_one (T& object);

      template <typename T>
      T
      query_value ();

      template <typename T>
      typename result<T>::pointer_type
      query_one (const char*);

      template <typename T>
      bool
      query_one (const char*, T& object);

      template <typename T>
      T
      query_value (const char*);

      template <typename T>
      typename result<T>::pointer_type
      query_one (const std::string&);

      template <typename T>
      bool
      query_one (const std::string&, T& object);

      template <typename T>
      T
      query_value (const std::string&);

      template <typename T>
      typename result<T>::pointer_type
      query_one (const sqlite::query_base&);

      template <typename T>
      bool
      query_one (const sqlite::query_base&, T& object);

      template <typename T>
      T
      query_value (const sqlite::query_base&);

      template <typename T>
      typename result<T>::pointer_type
      query_one (const odb::query_base&);

      template <typename T>
      bool
      query_one (const odb::query_base&, T& object);

      template <typename T>
      T
      query_value (const odb::query_base&);

      // Query preparation.
      //
      template <typename T>
      prepared_query<T>
      prepare_query (const char* name, const char*);

      template <typename T>
      prepared_query<T>
      prepare_query (const char* name, const std::string&);

      template <typename T>
      prepared_query<T>
      prepare_query (const char* name, const sqlite::query_base&);

      template <typename T>
      prepared_query<T>
      prepare_query (const char* name, const odb::query_base&);

      // Transactions.
      //
    public:
      std::unique_ptr<transaction_impl>
      begin ();

      std::unique_ptr<transaction_impl>
      begin_immediate ();

      std::unique_ptr<transaction_impl>
      begin_exclusive ();

      // Connections.
      //
    public:
      connection_ptr
      connection ();

      // SQL statement tracing.
      //
    public:
      typedef sqlite::tracer tracer_type;

      void
      tracer (tracer_type& t)
      {
        odb::database::tracer (t);
      }

      void
      tracer (tracer_type* t)
      {
        odb::database::tracer (t);
      }

      using odb::database::tracer;

      // Database schema version.
      //
    protected:
      virtual const schema_version_info&
      load_schema_version (const std::string& schema_name) const override;

      // Database id constant (useful for meta-programming).
      //
    public:
      static const odb::database_id database_id = id_sqlite;

    public:
      virtual
      ~database ();

    protected:
      virtual std::unique_ptr<odb::transaction_impl>
      begin_ () override;

      virtual odb::connection_ptr
      connection_ () override;

    private:
      friend class transaction_impl; // factory_

      // Note: remember to update move ctor if adding any new members.
      //
      std::string name_;
      std::string schema_;
      int flags_;
      std::string vfs_;

      std::function<void (connection_type&)> connection_configurator_;

      // Note: keep last so that all other database members are still valid
      // during factory's destruction.
      //
      std::unique_ptr<connection_factory> factory_;
    };
  }
}

#include <odb/sqlite/database.ixx>

#include <odb/post.hxx>

#endif // ODB_SQLITE_DATABASE_HXX
