/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CQuery.h  -  description
                             -------------------
    begin                : Wed Dec 15 1999
    copyright            : (C) 1999-2006 by Alexey Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at
   your option) any later version.

   This library is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#ifndef __CQUERY_H__
#define __CQUERY_H__

#if defined (_WIN32) || defined (__CYGWIN__)
#include <windows.h>
#endif

#include <sptk3/CDataSource.h>

#include <sptk3/CDatabase.h>
#include <sptk3/CParams.h>
#include <sptk3/CFieldList.h>

namespace sptk {

   /// @addtogroup Database Database Support
   /// @{

   /// @brief Query lock.
   ///
   /// Used internally to provide the thread-safe
   /// query execution. Blocks the database operation if another
   /// thread is using the same database. The block will be released as
   /// soon as the thread using the database exits the database operation
	class SP_EXPORT CQueryLock {
      /// The protected database
		CDatabase *m_db;
	public:

      /// Constructor
      /// @param db CDatabase *, the database to protect
		CQueryLock(CDatabase *db) { m_db = db; if (m_db) m_db->lock(); }

      /// Destructor
		~CQueryLock()             { if (m_db) m_db->unlock(); }
	};

   /// @brief Database query
   ///
   /// A CDataset connected to the database to
   /// execute a database queries. The type of the database
   /// depends on the CDatabase object query is connected to.
	class SP_EXPORT CQuery : public CDataSource {
		friend class CDatabase;

	protected:
		bool              m_autoPrepare;     ///< Prepare the query automatically, on the first call
		void *            m_statement;       ///< ODBC statement handle
		void *            m_connection;      ///< Database server connection

		bool              m_prepared;        ///< True if the statement is prepared
		bool              m_active;          ///< True if query is active (opened)
		bool              m_eof;             ///< True if there is no more records to fetch
		CParamList        m_params;          ///< List of query parameters
		CFieldList        m_fields;          ///< List of query fields - makes sense after fetch
		double            m_duration;        ///< The duration of last execution, in seconds

		CDatabase        *m_db;              ///< Database connection
		std::string       m_sql;             ///< SQL statement string

		CStrings          m_messages;        ///< Optional diag messages populated after exec() or open()

		int  countCols();                    ///< Counts columns of the dataset (if any) returned by query
		void allocStmt();                    ///< Allocates a statement
		void freeStmt();                     ///< Deallocates a statement. All the resources allocated by statement are released.
		void closeStmt();                    ///< Closes a statement. Prepared statement stay prepared but closed.
		void execute();                      ///< Executes a statement
		void colAttributes(short column,short descType,long& value); ///< In a dataset returned by a query, retrieves the column attributes
		void colAttributes(short column,short descType,char *buff,int len); ///< In a dataset returned by a query, retrieves the column attributes
		std::string getError() const;        ///< Retrieves an error (if any) after executing an ODBC statement

		int  m_objectIndex;                  ///< Unique index for query object
		static int nextObjectIndex;          ///< Internal number to implement unique query index. That is pretty useful for logs

		void notImplemented(std::string functionName) const; ///< Internal function to throw 'Not implemented' exception

	protected:
      /// Closes query by closing the statement. If the statement isn't released it may be re-used later.
      /// @param releaseStatement bool, true if we need to release the query's ODBC statement
		void closeQuery(bool releaseStatement=false);

      /// In CDataset it should load data into the dataset.
      /// Since the query loads all the data by open() or fetch(),
      /// in CQuery this method does exactly nothing
		virtual bool          loadData() { return false; }

      /// In CDataset it should save data into the dataset.
      /// Since the query saves all the data by execute,
      /// in CQuery this method does exactly nothing
		virtual bool          saveData() { return false; }

	public:
      /// Constructor
      /// @param db CDatabase, the database to connect to, optional
      /// @param sql std::string, the SQL query text to use, optional
		CQuery(CDatabase *db=0L,std::string sql = "");

      /// Copy constructor
		CQuery(const CQuery& );

      /// Destructor
		~CQuery();
	public:

      /// Finds a field by the field name
      /// @param fname const char *, field name
      /// @returns CField pointer, or 0L if not found
		CField *              fieldByName(const char * fname) const       { return m_fields.fieldByName(fname); }

      /// Field access by number, const version. Field index should be inside 0..fieldCount()-1
		virtual const CField& operator [] (int fieldIndex) const          { return m_fields[fieldIndex]; }

      /// Field access by number. Field index should be inside 0..fieldCount()-1
		virtual CField&       operator [] (int fieldIndex)                { return m_fields[fieldIndex]; }

      /// Field access by field name, const version
		virtual const CField& operator [] (const char *fieldName) const   { return m_fields[fieldName]; }

      /// Field access by field name.
		virtual CField&       operator [] (const char *fieldName)         { return m_fields[fieldName]; }

      /// Field access by field name, const version
		virtual const CField& operator [] (const std::string& fieldName) const{ return m_fields[fieldName.c_str()]; }

      /// Field access by field name.
		virtual CField&       operator [] (const std::string& fieldName)      { return m_fields[fieldName.c_str()]; }

      /// Reports a number of columns in the recordset for the active query.
      /// Typically is used after the open() method is called, but before query is closed.
		virtual unsigned      fieldCount() const                          { return m_fields.size(); }

	public:
      /// Reports the record count for the recordset, returned by the open() method.
      /// Currently is NOT implemented.
		virtual unsigned      recordCount() const { notImplemented("recordCount"); return 0; }

      /// Returns the text of current SQL query as std::string
		std::string sql() { return m_sql; }

      /// Returns the query fields list
		CFieldList& fields() { return m_fields; }

      /// Returns the query parameters list
		CParamList& params() { return m_params; }

	public:

      /// Field read access by the field name, for the universal data connection
		virtual bool          readField(const char *fname,CVariant& value);

      /// Field write access by the field name, for the universal data connection
		virtual bool          writeField(const char *fname,const CVariant& fvalue);

	public:
      /// Opens the query and fetches the first row. It is necessary for the select queries and some stored procedures
      /// that may return the dataset. First-time call for open() also prepares the query.
		virtual bool          open();

      /// Closes the query but doesn't release the ODBC statement, so the query may be called again, and faster than for the first time.
		virtual bool          close()      { closeQuery(); return true; }

      /// Fetches the next row from the recordset, same as fetch()
		virtual bool          next()       { fetch(); return true; }

      /// Returns true if there is no more rows in the recordset
		virtual bool          eof() const  { return m_eof; }

	public:
      /// Executes the query and closes the statement.
		void exec();

      /// Fetches the next row from the recordset, same as next()
		void fetch();

      /// Connects a query to a database. If the query was connected
      /// to another database, releases all the allocated resources in it.
		void connect(CDatabase *db);

      /// Disconnects query from the database and releases all the allocated resourses.
		void disconnect();

	public:
      /// Reports the number of unique parameters in the query.
      /// Makes sense after the SQL query text is set.
		unsigned   paramCount() const      { return m_params.size(); }

      /// Returns the parameter by the name. If the parameter name
      /// isn't found, throws an exception
      /// @param paramName const char *, parameter name
      /// @returns parameter
      /// @see CParamList
		CParam&    param(const char *paramName) const;

      /// Returns the parameter by the name. If the parameter name
      /// isn't found, throws an exception
      /// @param paramName std::string, parameter name
      /// @returns parameter
      /// @see CParamList
		CParam&    param(const std::string& paramName) const;

      /// Returns the parameter by the index.
      /// @returns parameter
      /// @see CParamList
		CParam&    param(unsigned paramIndex)   const;

      /// Returns the parameter by the index.
      /// @returns parameter
      /// @see CParamList
		CParam&    param(int paramIndex)   const;

	public:

      /// Returns query statement handle
		void *statement() const { return m_statement; }

      /// Returns SQL Query text
		std::string    sql() const { return m_sql; }

      /// Sets SQL Query text. If the Qury text is not the same and the ODBC statement was prepared earlier
      /// the ODBC statement is released and new one is created.
		void       sql(std::string _sql);

      /// Returns the database the query is connected to
		CDatabase *database() const { return m_db; }

      /// Connects the query to the database different database.
		void       database(CDatabase *db) { connect(db); }

		bool       active() const { return m_active; }        ///< Reports if the query is opened
		bool       prepared() const { return m_prepared; }    ///< True if the statement is prepared

      /// Returns the value for auto-prepare flag. If the flag is set
      /// the query would automatically call prepare() when needed.
		bool autoPrepare() const { return m_autoPrepare; }

      /// Sets the value for auto-prepare flag. If the flag is set
      /// the query would automatically call prepare() when needed.
		void autoPrepare(bool ap) { m_autoPrepare = ap; }

      /// Prepares query for the fast execution
		void prepare();

      /// Unprepares query releasing previously prepared statement
		void unprepare();

      /// @brief Adds the text to log file
      ///
      /// The log file should be set to active, otherwise no text is added to the log file.
      /// Every successful call of this method adds a new line to the log file.
      /// @param text std::string, log text
		void logText(std::string text);

      /// @brief Throws an exception
      ///
      /// Before exception is thrown, it is logged into the logfile (if the logfile is defined)
      /// @param method std::string, method name where error has occured
      /// @param error std::string, error text
		void logAndThrow(std::string method,std::string error) throw(CException);

      /// @brief Access to diag messages
      ///
      /// Some of the database drivers (ODBC, for example) may return diag messages
      /// after the execution of the query. Usually, such messages may be generated
      /// by a stored procedure call.
		CStrings& messages() { return m_messages; }
	};
   /// @}
}
#define FETCH_BUFFER_SIZE 1024

#endif
