/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CQuery.h  -  description
                             -------------------
    begin                : Wed Dec 15 1999
    copyright            : (C) 1999-2005 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>

class CDatabase;

/// @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
   int               m_retcode;         ///< Last RETCODE returned from ODBC function
   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
   float             m_duration;        ///< The duration of last execution, in seconds

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

   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  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);
};

/// @}

#define FETCH_BUFFER_SIZE 1024

#endif
