/****************************************************************************
**
** https://www.qxorm.com/
** Copyright (C) 2013 XDL Team (ic-east.com)
**
** This file is part of the QxOrm library
**
** This software is provided 'as-is', without any express or implied
** warranty. In no event will the authors be held liable for any
** damages arising from the use of this software
**
** Commercial Usage
** Licensees holding valid commercial QxOrm licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and XDL Team
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file 'license.gpl3.txt' included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met : http://www.gnu.org/copyleft/gpl.html
**
** If you are unsure which license is appropriate for your use, or
** if you have questions regarding the use of this file, please contact :
** ic-east.com
**
****************************************************************************/

#include <QxPrecompiled.h>

#include <QtCore/qelapsedtimer.h>

#include <QtSql/qsqlrecord.h>

#include <QxDao/QxSqlQuery.h>
#include <QxDao/QxSqlDatabase.h>
#include <QxDao/QxMongoDB/QxMongoDB_Helper.h>

#include <QxCollection/QxCollectionIterator.h>

#include <QxSerialize/std/QxSerialize_std_tuple.h>

#include <QxSerialize/Qt/QxSerialize_QString.h>
#include <QxSerialize/Qt/QxSerialize_QVariant.h>
#include <QxSerialize/Qt/QxSerialize_QHash.h>
#include <QxSerialize/Qt/QxSerialize_QVector.h>
#include <QxSerialize/Qt/QxSerialize_QFlags.h>

#include <QxSerialize/Qx/QxSerialize_QxCollection.h>

#include <QxSerialize/QDataStream/QxSerializeQDataStream_all_include.h>

#include <QxMemLeak/mem_leak.h>

namespace qx
{

   QxSqlQuery::QxSqlQuery() : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false) { m_sQuery << QString(); }

   QxSqlQuery::QxSqlQuery(const char *query, const QVariantList &values /* = QVariantList() */) : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false)
   {
      if (values.count() <= 0)
      {
         m_sQuery << QString(query);
      }
      else
      {
         addFreeText(query, values);
      }
   }

   QxSqlQuery::QxSqlQuery(const QString &query, const QVariantList &values /* = QVariantList() */) : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false)
   {
      if (values.count() <= 0)
      {
         m_sQuery << query;
      }
      else
      {
         addFreeText(query, values);
      }
   }

   QxSqlQuery::QxSqlQuery(const QStringList &query) : m_sQuery(query), m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false) { ; }

   QxSqlQuery::QxSqlQuery(const QString &type, const QString &query) : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false), m_sType(type) { m_sQuery << query; }

   QxSqlQuery::QxSqlQuery(const QString &type, const QStringList &query) : m_sQuery(query), m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false), m_sType(type) { ; }

#ifndef _QX_NO_JSON
#ifdef Q_COMPILER_INITIALIZER_LISTS

   QxSqlQuery::QxSqlQuery(std::initializer_list<QPair<QString, QJsonValue>> json) : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false)
   {
      QJsonObject obj;
      for (std::initializer_list<QPair<QString, QJsonValue>>::const_iterator itr = json.begin(); itr != json.end(); ++itr)
      {
         obj.insert(itr->first, itr->second);
      }
      QJsonDocument doc(obj);
      m_sQuery << QString::fromUtf8(doc.toJson());
   }

   QxSqlQuery::QxSqlQuery(std::initializer_list<QPair<QString, QJsonValue>> json, std::initializer_list<QPair<QString, QJsonValue>> opts) : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false)
   {
      QJsonObject obj1;
      for (std::initializer_list<QPair<QString, QJsonValue>>::const_iterator itr = json.begin(); itr != json.end(); ++itr)
      {
         obj1.insert(itr->first, itr->second);
      }
      QJsonObject obj2;
      for (std::initializer_list<QPair<QString, QJsonValue>>::const_iterator itr = opts.begin(); itr != opts.end(); ++itr)
      {
         obj2.insert(itr->first, itr->second);
      }
      QJsonDocument doc1(obj1);
      QJsonDocument doc2(obj2);
      m_sQuery << QString::fromUtf8(doc1.toJson()) << QString::fromUtf8(doc2.toJson());
   }

   QxSqlQuery::QxSqlQuery(const QString &type, std::initializer_list<QPair<QString, QJsonValue>> json) : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false), m_sType(type)
   {
      QJsonObject obj;
      for (std::initializer_list<QPair<QString, QJsonValue>>::const_iterator itr = json.begin(); itr != json.end(); ++itr)
      {
         obj.insert(itr->first, itr->second);
      }
      QJsonDocument doc(obj);
      m_sQuery << QString::fromUtf8(doc.toJson());
   }

   QxSqlQuery::QxSqlQuery(const QString &type, std::initializer_list<QPair<QString, QJsonValue>> json, std::initializer_list<QPair<QString, QJsonValue>> opts) : m_iSqlElementIndex(0), m_iParenthesisCount(0), m_bDistinct(false), m_sType(type)
   {
      QJsonObject obj1;
      for (std::initializer_list<QPair<QString, QJsonValue>>::const_iterator itr = json.begin(); itr != json.end(); ++itr)
      {
         obj1.insert(itr->first, itr->second);
      }
      QJsonObject obj2;
      for (std::initializer_list<QPair<QString, QJsonValue>>::const_iterator itr = opts.begin(); itr != opts.end(); ++itr)
      {
         obj2.insert(itr->first, itr->second);
      }
      QJsonDocument doc1(obj1);
      QJsonDocument doc2(obj2);
      m_sQuery << QString::fromUtf8(doc1.toJson()) << QString::fromUtf8(doc2.toJson());
   }

#endif // Q_COMPILER_INITIALIZER_LISTS
#endif // _QX_NO_JSON

   QxSqlQuery::~QxSqlQuery() { ; }

   void QxSqlQuery::verifyQuery() const
   {
#ifdef _QX_MODE_DEBUG
      if (queryAt(0).isEmpty() || (m_lstSqlElement.count() <= 0))
      {
         return;
      }
      qDebug("[QxOrm] qx::QxSqlQuery::verifyQuery() : '%s'", "invalid SQL query, you cannot mix classic SQL and C++ syntax");
      qAssert(false);
#endif // _QX_MODE_DEBUG
   }

   QString QxSqlQuery::query()
   {
      verifyQuery();
      if (m_lstSqlElement.count() <= 0)
      {
         return queryAt(0);
      }
      while (m_iParenthesisCount > 0)
      {
         closeParenthesis();
      }

      QString sQuery;
      for (int i = 0; i < m_lstSqlElement.count(); i++)
      {
         sQuery += m_lstSqlElement.at(i)->toString() + " ";
      }
      return sQuery;
   }

   QString QxSqlQuery::queryAt(int idx) const
   {
      if ((idx < 0) || (idx >= m_sQuery.count()))
      {
         return QString();
      }
      return m_sQuery.at(idx);
   }

   void QxSqlQuery::queryAt(int idx, const QString &query)
   {
      while ((m_sQuery.count() - 1) < idx)
      {
         m_sQuery << QString();
      }
      m_sQuery.replace(idx, query);
      qAssert(m_sQuery.at(idx) == query);
   }

   QVariant QxSqlQuery::response() const
   {
      return m_vResponse;
   }

   void QxSqlQuery::setResponse(const QVariant &v)
   {
      m_vResponse = v;
   }

   QString QxSqlQuery::type() const
   {
      return m_sType;
   }

   void QxSqlQuery::setType(const QString &s)
   {
      m_sType = s;
   }

   bool QxSqlQuery::isEmpty() const
   {
      return (queryAt(0).isEmpty() && (m_lstSqlElement.count() <= 0) && (m_lstJoinQueryUser.count() <= 0));
   }

   bool QxSqlQuery::isDistinct() const
   {
      return m_bDistinct;
   }

   void QxSqlQuery::clear()
   {
      m_sQuery.clear();
      m_lstValue.clear();
      m_pSqlElementTemp.reset();
      m_lstSqlElement.clear();
      m_iSqlElementIndex = 0;
      m_iParenthesisCount = 0;
      m_vResponse = QVariant();
      m_sType = "";
      m_lstJoinQueryUser.clear();
      m_lstJoinQueryToResolve.clear();
   }

   QxSqlQuery &QxSqlQuery::query(const QString &sQuery)
   {
      if (!sQuery.isEmpty())
      {
         qAssert(m_lstSqlElement.count() <= 0);
      }

      clear();
      m_sQuery << sQuery;
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::bind(const QVariant &vValue, QSql::ParamType paramType /* = QSql::In */)
   {
      verifyQuery();
      qAssert(m_lstSqlElement.count() <= 0);
      qAssert(!queryAt(0).isEmpty() && (qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle() == qx::QxSqlDatabase::ph_style_question_mark));

      QString sKey = QString::number(m_lstValue.count() + 1);
      m_lstValue.insert(sKey, type_bind_value(vValue, paramType));
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::bind(const QString &sKey, const QVariant &vValue, QSql::ParamType paramType /* = QSql::In */)
   {
      verifyQuery();
      qAssert(m_lstSqlElement.count() <= 0);
      qAssert(!queryAt(0).isEmpty() && (qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle() != qx::QxSqlDatabase::ph_style_question_mark));

      if (sKey.isEmpty() || m_lstValue.exist(sKey))
      {
         qAssert(false);
         return (*this);
      }
      if (!queryAt(0).contains(sKey))
      {
         qAssert(false);
         return (*this);
      }
      m_lstValue.insert(sKey, type_bind_value(vValue, paramType));
      return (*this);
   }

   QVariant QxSqlQuery::boundValue(const QString &sKey) const
   {
      if (sKey.isEmpty() || !m_lstValue.exist(sKey))
      {
         qAssert(false);
         return QVariant();
      }
      return std::get<0>(m_lstValue.getByKey(sKey));
   }

   QVariant QxSqlQuery::boundValue(int iPosition) const
   {
      if ((iPosition < 0) || (iPosition >= m_lstValue.count()))
      {
         qAssert(false);
         return QVariant();
      }
      return std::get<0>(m_lstValue.getByIndex(iPosition));
   }

   void QxSqlQuery::resolve(QSqlQuery &query, qx::QxCollection<QString, QVariantList> *pLstExecBatch /* = NULL */) const
   {
      verifyQuery();

      for (int i = 0; i < m_lstJoinQueryToResolve.count(); i++)
      {
         std::shared_ptr<QxSqlQuery> joinQuery = m_lstJoinQueryToResolve.at(i);
         joinQuery->resolve(query, pLstExecBatch);
      }

      if (m_lstSqlElement.count() > 0)
      {
         for (int i = 0; i < m_lstSqlElement.count(); i++)
         {
            m_lstSqlElement.at(i)->resolve(query, pLstExecBatch);
         }
         return;
      }

      bool bQuestionMark = (qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle() == qx::QxSqlDatabase::ph_style_question_mark);
      QxCollectionIterator<QString, type_bind_value> itr(m_lstValue);
      while (itr.next())
      {
         if (pLstExecBatch)
         {
            QString key = itr.key();
            if (!pLstExecBatch->exist(key))
            {
               QVariantList empty;
               pLstExecBatch->insert(key, empty);
            }
            QVariantList &values = const_cast<QVariantList &>(pLstExecBatch->getByKey(key));
            values.append(std::get<0>(itr.value()));
         }
         else
         {
            if (bQuestionMark)
            {
               query.addBindValue(std::get<0>(itr.value()), std::get<1>(itr.value()));
            }
            else
            {
               query.bindValue(itr.key(), std::get<0>(itr.value()), std::get<1>(itr.value()));
            }
         }
      }
   }

   void QxSqlQuery::resolveOutput(QSqlQuery &query, bool bFetchSqlResult)
   {
      QxCollection<QString, type_bind_value> lst;
      lst.reserve(m_lstValue.count());
      bool bQuestionMark = (qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle() == qx::QxSqlDatabase::ph_style_question_mark);
      for (long l = 0; l < m_lstValue.count(); l++)
      {
         QVariant outputValue;
         QString key = m_lstValue.getKeyByIndex(l);
         type_bind_value val = m_lstValue.getByIndex(l);
         QSql::ParamType paramType = std::get<1>(val);
         if (paramType == QSql::In)
         {
            lst.insert(key, val);
            continue;
         }
         if (bQuestionMark)
         {
            outputValue = query.boundValue(l);
         }
         else
         {
            outputValue = query.boundValue(key);
         }
         lst.insert(key, type_bind_value(outputValue, paramType));
      }
      if (bFetchSqlResult)
      {
         fetchSqlResult(query);
      }
      m_lstValue = lst;
   }

   QString QxSqlQuery::getJoinQuery(const QString &relationKey, const QString &relationAlias)
   {
      if (m_lstJoinQueryUser.contains(relationKey))
      {
         std::shared_ptr<QxSqlQuery> query = m_lstJoinQueryUser.value(relationKey);
         m_lstJoinQueryToResolve.append(query);
         return query->query().trimmed();
      }
      else if (m_lstJoinQueryUser.contains(relationAlias))
      {
         std::shared_ptr<QxSqlQuery> query = m_lstJoinQueryUser.value(relationAlias);
         m_lstJoinQueryToResolve.append(query);
         return query->query().trimmed();
      }
      return QString();
   }

   QString QxSqlQuery::getJoinQueryHash()
   {
      QString hash;
      QHashIterator<QString, std::shared_ptr<QxSqlQuery>> itr(m_lstJoinQueryUser);
      while (itr.hasNext())
      {
         itr.next();
         std::shared_ptr<QxSqlQuery> tmp = itr.value();
         hash += "|" + itr.key() + "|" + tmp->query();
      }
      return hash;
   }

   void QxSqlQuery::dumpBoundValues(const QSqlQuery &query)
   {
      QString sBoundValues = "";
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
      QVariantList lstBoundValues = query.boundValues();
#else  // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
      QMap<QString, QVariant> lstBoundValues = query.boundValues();
#endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
      if (lstBoundValues.count() <= 0)
      {
         return;
      }

#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
      for (int i = 0; i < lstBoundValues.size(); ++i)
      {
         sBoundValues += "\n  - position '" + QString::number(i) + "' : " + lstBoundValues.at(i).toString();
      }
#else  // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
      if (qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle() == qx::QxSqlDatabase::ph_style_question_mark)
      {
         QList<QVariant> lst = lstBoundValues.values();
         for (int i = 0; i < lst.size(); ++i)
         {
            sBoundValues += "\n  - position '" + QString::number(i) + "' : " + lst.at(i).toString();
         }
      }
      else
      {
         QMapIterator<QString, QVariant> itr(lstBoundValues);
         while (itr.hasNext())
         {
            itr.next();
            sBoundValues += "\n  - " + itr.key() + " : " + itr.value().toString();
         }
      }
#endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))

      if (!sBoundValues.isEmpty())
      {
         qDebug("[QxOrm] dump sql query bound values : %s", qPrintable(sBoundValues));
      }
   }

   QxSqlQuery &QxSqlQuery::setFctOnBeforeSqlPrepare(QxSqlQuery::type_fct_on_before_sql_prepare fct)
   {
      m_fctOnBeforeSqlPrepare = fct;
      return (*this);
   }

   void QxSqlQuery::onBeforeSqlPrepare(QString &sql)
   {
      if (m_fctOnBeforeSqlPrepare)
      {
         m_fctOnBeforeSqlPrepare(sql);
      }
   }

   void QxSqlQuery::fetchSqlResult(QSqlQuery &query)
   {
      bool bCheckRecord = true;
      m_pSqlResult = std::make_shared<QxSqlResult>();
      if (query.size() > 0)
      {
         m_pSqlResult->values.reserve(query.size());
      }
      while (query.next())
      {
         if (bCheckRecord)
         {
            bCheckRecord = false;
            QSqlRecord record = query.record();
            m_pSqlResult->positionByKey.reserve(record.count());
            for (int i = 0; i < record.count(); i++)
            {
               m_pSqlResult->positionByKey.insert(record.fieldName(i), i);
            }
            qAssert(record.count() == m_pSqlResult->positionByKey.count());
         }
         QVector<QVariant> lst;
         lst.reserve(m_pSqlResult->positionByKey.count());
         for (long j = 0; j < m_pSqlResult->positionByKey.count(); j++)
         {
            lst.append(query.value(j));
         }
         qAssert(lst.count() == m_pSqlResult->positionByKey.count());
         m_pSqlResult->values.append(lst);
      }
   }

   long QxSqlQuery::getSqlResultRowCount() const
   {
      if (!m_pSqlResult)
      {
         return 0;
      }
      return m_pSqlResult->values.count();
   }

   long QxSqlQuery::getSqlResultColumnCount() const
   {
      if (!m_pSqlResult)
      {
         return 0;
      }
      return m_pSqlResult->positionByKey.count();
   }

   QVariant QxSqlQuery::getSqlResultAt(long row, long column) const
   {
      if (!m_pSqlResult)
      {
         return QVariant();
      }
      if ((row < 0) || (row >= m_pSqlResult->values.count()))
      {
         return QVariant();
      }
      if ((column < 0) || (column >= m_pSqlResult->positionByKey.count()))
      {
         return QVariant();
      }
      return m_pSqlResult->values.at(row).at(column);
   }

   QVariant QxSqlQuery::getSqlResultAt(long row, const QString &column, bool caseSensitive /* = false */) const
   {
      if (!m_pSqlResult)
      {
         return QVariant();
      }
      if ((row < 0) || (row >= m_pSqlResult->values.count()))
      {
         return QVariant();
      }

      int i(-1);
      int col = m_pSqlResult->positionByKey.value(column, i);
      if (col >= 0)
      {
         return m_pSqlResult->values.at(row).at(col);
      }
      if (caseSensitive)
      {
         return QVariant();
      }

      if (m_pSqlResult->positionByKeyUpper.count() <= 0)
      {
         QHashIterator<QString, int> itr(m_pSqlResult->positionByKey);
         while (itr.hasNext())
         {
            itr.next();
            QString keyUpper = itr.key().toUpper();
            int val = itr.value();
            m_pSqlResult->positionByKeyUpper.insert(keyUpper, val);
         }
      }

      QString columnUpper = column.toUpper();
      int j(-1);
      col = m_pSqlResult->positionByKeyUpper.value(columnUpper, j);
      return ((col >= 0) ? m_pSqlResult->values.at(row).at(col) : QVariant());
   }

   QVector<QVariant> QxSqlQuery::getSqlResultAt(long row) const
   {
      if (!m_pSqlResult)
      {
         return QVector<QVariant>();
      }
      if ((row < 0) || (row >= m_pSqlResult->values.count()))
      {
         return QVector<QVariant>();
      }
      return m_pSqlResult->values.at(row);
   }

   QVector<QString> QxSqlQuery::getSqlResultAllColumns() const
   {
      if (!m_pSqlResult)
      {
         return QVector<QString>();
      }
      QVector<QString> lstAllColumns(m_pSqlResult->positionByKey.count());
      QHashIterator<QString, int> itr(m_pSqlResult->positionByKey);
      while (itr.hasNext())
      {
         itr.next();
         lstAllColumns[itr.value()] = itr.key();
      }
      return lstAllColumns;
   }

   void QxSqlQuery::dumpSqlResult()
   {
      if (!m_pSqlResult)
      {
         return;
      }
      QString sql(this->query()), sColumns("#"), sOutput;
      qDebug("[QxOrm] start dump sql result : '%s'", qPrintable(sql));
      QVector<QString> lstColumns = this->getSqlResultAllColumns();
      for (long i = 0; i < lstColumns.count(); i++)
      {
         sColumns += "|" + lstColumns.at(i);
      }
      qDebug("%s", qPrintable(sColumns));
      for (long j = 0; j < m_pSqlResult->values.count(); j++)
      {
         sOutput = QString::number(j);
         for (long k = 0; k < m_pSqlResult->positionByKey.count(); k++)
         {
            sOutput += "|" + m_pSqlResult->values.at(j).at(k).toString();
         }
         qDebug("%s", qPrintable(sOutput));
      }
      qDebug("[QxOrm] end dump sql result : '%s'", qPrintable(sql));
   }

   void QxSqlQuery::postProcess(QString &sql) const
   {
      verifyQuery();
      for (int i = 0; i < m_lstSqlElement.count(); i++)
      {
         m_lstSqlElement.at(i)->postProcess(sql);
      }
   }

   QxSqlQuery &QxSqlQuery::distinct()
   {
      m_bDistinct = true;
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::where(const QString &column)
   {
      return addSqlExpression(column, qx::dao::detail::QxSqlExpression::_where);
   }

   QxSqlQuery &QxSqlQuery::where_OpenParenthesis(const QString &column)
   {
      where(column);
      return openParenthesis();
   }

   QxSqlQuery &QxSqlQuery::and_(const QString &column)
   {
      return addSqlExpression(column, qx::dao::detail::QxSqlExpression::_and);
   }

   QxSqlQuery &QxSqlQuery::and_OpenParenthesis(const QString &column)
   {
      and_(column);
      return openParenthesis();
   }

   QxSqlQuery &QxSqlQuery::or_(const QString &column)
   {
      return addSqlExpression(column, qx::dao::detail::QxSqlExpression::_or);
   }

   QxSqlQuery &QxSqlQuery::or_OpenParenthesis(const QString &column)
   {
      or_(column);
      return openParenthesis();
   }

   QxSqlQuery &QxSqlQuery::openParenthesis()
   {
      qx::dao::detail::QxSqlExpression_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlExpression>(m_iSqlElementIndex++, qx::dao::detail::QxSqlExpression::_open_parenthesis);
      m_lstSqlElement.append(p);
      m_iParenthesisCount++;
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::closeParenthesis()
   {
      if (m_iParenthesisCount <= 0)
      {
         return (*this);
      }
      qx::dao::detail::QxSqlExpression_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlExpression>(m_iSqlElementIndex++, qx::dao::detail::QxSqlExpression::_close_parenthesis);
      m_lstSqlElement.append(p);
      m_iParenthesisCount--;
      m_pSqlElementTemp.reset();
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QStringList &columns)
   {
      return addSqlSort(columns, qx::dao::detail::QxSqlSort::_order_asc);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1)
   {
      return orderAsc(QStringList() << col1);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2)
   {
      return orderAsc(QStringList() << col1 << col2);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2, const QString &col3)
   {
      return orderAsc(QStringList() << col1 << col2 << col3);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2, const QString &col3, const QString &col4)
   {
      return orderAsc(QStringList() << col1 << col2 << col3 << col4);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5)
   {
      return orderAsc(QStringList() << col1 << col2 << col3 << col4 << col5);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6)
   {
      return orderAsc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7)
   {
      return orderAsc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7, const QString &col8)
   {
      return orderAsc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7 << col8);
   }

   QxSqlQuery &QxSqlQuery::orderAsc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7, const QString &col8, const QString &col9)
   {
      return orderAsc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7 << col8 << col9);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QStringList &columns)
   {
      return addSqlSort(columns, qx::dao::detail::QxSqlSort::_order_desc);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1)
   {
      return orderDesc(QStringList() << col1);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2)
   {
      return orderDesc(QStringList() << col1 << col2);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2, const QString &col3)
   {
      return orderDesc(QStringList() << col1 << col2 << col3);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2, const QString &col3, const QString &col4)
   {
      return orderDesc(QStringList() << col1 << col2 << col3 << col4);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5)
   {
      return orderDesc(QStringList() << col1 << col2 << col3 << col4 << col5);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6)
   {
      return orderDesc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7)
   {
      return orderDesc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7, const QString &col8)
   {
      return orderDesc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7 << col8);
   }

   QxSqlQuery &QxSqlQuery::orderDesc(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7, const QString &col8, const QString &col9)
   {
      return orderDesc(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7 << col8 << col9);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QStringList &columns)
   {
      return addSqlSort(columns, qx::dao::detail::QxSqlSort::_group_by);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1)
   {
      return groupBy(QStringList() << col1);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2)
   {
      return groupBy(QStringList() << col1 << col2);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2, const QString &col3)
   {
      return groupBy(QStringList() << col1 << col2 << col3);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2, const QString &col3, const QString &col4)
   {
      return groupBy(QStringList() << col1 << col2 << col3 << col4);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5)
   {
      return groupBy(QStringList() << col1 << col2 << col3 << col4 << col5);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6)
   {
      return groupBy(QStringList() << col1 << col2 << col3 << col4 << col5 << col6);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7)
   {
      return groupBy(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7, const QString &col8)
   {
      return groupBy(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7 << col8);
   }

   QxSqlQuery &QxSqlQuery::groupBy(const QString &col1, const QString &col2, const QString &col3, const QString &col4, const QString &col5, const QString &col6, const QString &col7, const QString &col8, const QString &col9)
   {
      return groupBy(QStringList() << col1 << col2 << col3 << col4 << col5 << col6 << col7 << col8 << col9);
   }

   QxSqlQuery &QxSqlQuery::limit(int rowsCount, int startRow /* = 0 */, bool withTies /* = false */)
   {
      qx::dao::detail::QxSqlLimit_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlLimit>(m_iSqlElementIndex++);
      p->setValues(QVariantList() << QVariant(startRow) << QVariant(rowsCount) << QVariant(withTies));
      m_lstSqlElement.append(p);
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::like(const QString &val)
   {
      return addSqlCompare(QVariant(val), qx::dao::detail::QxSqlCompare::_like);
   }

   QxSqlQuery &QxSqlQuery::notLike(const QString &val)
   {
      return addSqlCompare(QVariant(val), qx::dao::detail::QxSqlCompare::_not_like);
   }

   QxSqlQuery &QxSqlQuery::startsWith(const QString &val)
   {
      return addSqlCompare(QVariant(val), qx::dao::detail::QxSqlCompare::_starts_with);
   }

   QxSqlQuery &QxSqlQuery::endsWith(const QString &val)
   {
      return addSqlCompare(QVariant(val), qx::dao::detail::QxSqlCompare::_ends_with);
   }

   QxSqlQuery &QxSqlQuery::containsString(const QString &val)
   {
      return addSqlCompare(QVariant(val), qx::dao::detail::QxSqlCompare::_contains_string);
   }

   QxSqlQuery &QxSqlQuery::isEqualTo(const QVariant &val)
   {
      return addSqlCompare(val, qx::dao::detail::QxSqlCompare::_is_equal_to);
   }

   QxSqlQuery &QxSqlQuery::isNotEqualTo(const QVariant &val)
   {
      return addSqlCompare(val, qx::dao::detail::QxSqlCompare::_is_not_equal_to);
   }

   QxSqlQuery &QxSqlQuery::isGreaterThan(const QVariant &val)
   {
      return addSqlCompare(val, qx::dao::detail::QxSqlCompare::_is_greater_than);
   }

   QxSqlQuery &QxSqlQuery::isGreaterThanOrEqualTo(const QVariant &val)
   {
      return addSqlCompare(val, qx::dao::detail::QxSqlCompare::_is_greater_than_or_equal_to);
   }

   QxSqlQuery &QxSqlQuery::isLessThan(const QVariant &val)
   {
      return addSqlCompare(val, qx::dao::detail::QxSqlCompare::_is_less_than);
   }

   QxSqlQuery &QxSqlQuery::isLessThanOrEqualTo(const QVariant &val)
   {
      return addSqlCompare(val, qx::dao::detail::QxSqlCompare::_is_less_than_or_equal_to);
   }

   QxSqlQuery &QxSqlQuery::customOperator(const QString &sCustomOperator, const QVariant &val)
   {
      return addSqlCompare(val, qx::dao::detail::QxSqlCompare::_custom_operator, sCustomOperator);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariantList &values)
   {
      return addSqlIn(values, qx::dao::detail::QxSqlIn::_in);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1)
   {
      return in(QVariantList() << val1);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2)
   {
      return in(QVariantList() << val1 << val2);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2, const QVariant &val3)
   {
      return in(QVariantList() << val1 << val2 << val3);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4)
   {
      return in(QVariantList() << val1 << val2 << val3 << val4);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5)
   {
      return in(QVariantList() << val1 << val2 << val3 << val4 << val5);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6)
   {
      return in(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6, const QVariant &val7)
   {
      return in(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6 << val7);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6, const QVariant &val7, const QVariant &val8)
   {
      return in(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6 << val7 << val8);
   }

   QxSqlQuery &QxSqlQuery::in(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6, const QVariant &val7, const QVariant &val8, const QVariant &val9)
   {
      return in(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6 << val7 << val8 << val9);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariantList &values)
   {
      return addSqlIn(values, qx::dao::detail::QxSqlIn::_not_in);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1)
   {
      return notIn(QVariantList() << val1);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2)
   {
      return notIn(QVariantList() << val1 << val2);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2, const QVariant &val3)
   {
      return notIn(QVariantList() << val1 << val2 << val3);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4)
   {
      return notIn(QVariantList() << val1 << val2 << val3 << val4);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5)
   {
      return notIn(QVariantList() << val1 << val2 << val3 << val4 << val5);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6)
   {
      return notIn(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6, const QVariant &val7)
   {
      return notIn(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6 << val7);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6, const QVariant &val7, const QVariant &val8)
   {
      return notIn(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6 << val7 << val8);
   }

   QxSqlQuery &QxSqlQuery::notIn(const QVariant &val1, const QVariant &val2, const QVariant &val3, const QVariant &val4, const QVariant &val5, const QVariant &val6, const QVariant &val7, const QVariant &val8, const QVariant &val9)
   {
      return notIn(QVariantList() << val1 << val2 << val3 << val4 << val5 << val6 << val7 << val8 << val9);
   }

   QxSqlQuery &QxSqlQuery::in_Select(const QxSqlQuery &query)
   {
      if (query.m_lstSqlElement.count() <= 0)
      {
         addSqlIn(QVariantList() << QVariant(query.queryAt(0)), qx::dao::detail::QxSqlIn::_in_select);
      }
      else
      {
         addEmbedQuery(query, qx::dao::detail::QxSqlEmbedQuery::_in, true);
      }
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::notIn_Select(const QxSqlQuery &query)
   {
      if (query.m_lstSqlElement.count() <= 0)
      {
         addSqlIn(QVariantList() << QVariant(query.queryAt(0)), qx::dao::detail::QxSqlIn::_not_in_select);
      }
      else
      {
         addEmbedQuery(query, qx::dao::detail::QxSqlEmbedQuery::_not_in, true);
      }
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::isEqualTo_Select(const QxSqlQuery &query)
   {
      if (query.m_lstSqlElement.count() <= 0)
      {
         addSqlCompare(query.queryAt(0), qx::dao::detail::QxSqlCompare::_is_equal_to_select);
      }
      else
      {
         addEmbedQuery(query, qx::dao::detail::QxSqlEmbedQuery::_is_equal_to, true);
      }
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::isNotEqualTo_Select(const QxSqlQuery &query)
   {
      if (query.m_lstSqlElement.count() <= 0)
      {
         addSqlCompare(query.queryAt(0), qx::dao::detail::QxSqlCompare::_is_not_equal_to_select);
      }
      else
      {
         addEmbedQuery(query, qx::dao::detail::QxSqlEmbedQuery::_is_not_equal_to, true);
      }
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::isNull()
   {
      return addSqlIsNull(qx::dao::detail::QxSqlIsNull::_is_null);
   }

   QxSqlQuery &QxSqlQuery::isNotNull()
   {
      return addSqlIsNull(qx::dao::detail::QxSqlIsNull::_is_not_null);
   }

   QxSqlQuery &QxSqlQuery::isBetween(const QVariant &val1, const QVariant &val2)
   {
      return addSqlIsBetween(val1, val2, qx::dao::detail::QxSqlIsBetween::_is_between);
   }

   QxSqlQuery &QxSqlQuery::isNotBetween(const QVariant &val1, const QVariant &val2)
   {
      return addSqlIsBetween(val1, val2, qx::dao::detail::QxSqlIsBetween::_is_not_between);
   }

   QxSqlQuery &QxSqlQuery::freeText(const QString &text, const QVariantList &values /* = QVariantList() */)
   {
      if (text.isEmpty())
      {
         return (*this);
      }
      return addFreeText(text, values);
   }

   QxSqlQuery &QxSqlQuery::addJoinQuery(const QString &relationKeyOrAlias, const QxSqlQuery &joinQuery)
   {
      std::shared_ptr<QxSqlQuery> pQuery = std::make_shared<QxSqlQuery>(joinQuery);
      m_lstJoinQueryUser.insert(relationKeyOrAlias, pQuery);
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addSqlExpression(const QString &column, qx::dao::detail::QxSqlExpression::type type)
   {
      qx::dao::detail::QxSqlExpression_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlExpression>(m_iSqlElementIndex++, type);
      m_lstSqlElement.append(p);

      m_pSqlElementTemp = std::make_shared<qx::dao::detail::QxSqlElementTemp>();
      m_pSqlElementTemp->setColumn(column);
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addSqlCompare(const QVariant &val, qx::dao::detail::QxSqlCompare::type type, const QString &sCustomOperator /* = QString() */)
   {
      if (!m_pSqlElementTemp)
      {
         qDebug("[QxOrm] qx::QxSqlQuery::addSqlCompare : '%s'", "invalid SQL query, need a column name");
         qAssert(false);
         return (*this);
      }

      qx::dao::detail::QxSqlCompare_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlCompare>(m_iSqlElementIndex++, type, sCustomOperator);
      p->clone(m_pSqlElementTemp.get());
      p->setValue(val);

      m_lstSqlElement.append(p);
      m_pSqlElementTemp.reset();
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addSqlSort(const QStringList &columns, qx::dao::detail::QxSqlSort::type type)
   {
      qx::dao::detail::QxSqlSort_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlSort>(m_iSqlElementIndex++, type);
      p->setColumns(columns);
      m_lstSqlElement.append(p);
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addSqlIn(const QVariantList &values, qx::dao::detail::QxSqlIn::type type)
   {
      if (!m_pSqlElementTemp)
      {
         qDebug("[QxOrm] qx::QxSqlQuery::addSqlIn : '%s'", "invalid SQL query, need a column name");
         qAssert(false);
         return (*this);
      }

      qx::dao::detail::QxSqlIn_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlIn>(m_iSqlElementIndex++, type);
      p->clone(m_pSqlElementTemp.get());
      p->setValues(values);

      m_lstSqlElement.append(p);
      m_pSqlElementTemp.reset();
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addSqlIsNull(qx::dao::detail::QxSqlIsNull::type type)
   {
      if (!m_pSqlElementTemp)
      {
         qDebug("[QxOrm] qx::QxSqlQuery::addSqlIsNull : '%s'", "invalid SQL query, need a column name");
         qAssert(false);
         return (*this);
      }

      qx::dao::detail::QxSqlIsNull_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlIsNull>(m_iSqlElementIndex++, type);
      p->clone(m_pSqlElementTemp.get());

      m_lstSqlElement.append(p);
      m_pSqlElementTemp.reset();
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addSqlIsBetween(const QVariant &val1, const QVariant &val2, qx::dao::detail::QxSqlIsBetween::type type)
   {
      if (!m_pSqlElementTemp)
      {
         qDebug("[QxOrm] qx::QxSqlQuery::addSqlIsBetween : '%s'", "invalid SQL query, need a column name");
         qAssert(false);
         return (*this);
      }

      qx::dao::detail::QxSqlIsBetween_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlIsBetween>(m_iSqlElementIndex++, type);
      p->clone(m_pSqlElementTemp.get());
      p->setValues(QVariantList() << val1 << val2);

      m_lstSqlElement.append(p);
      m_pSqlElementTemp.reset();
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addFreeText(const QString &text, const QVariantList &values)
   {
      qx::dao::detail::QxSqlFreeText_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlFreeText>(m_iSqlElementIndex++);
      p->setText(text);
      p->setValues(values);
      m_lstSqlElement.append(p);
      return (*this);
   }

   QxSqlQuery &QxSqlQuery::addEmbedQuery(const QxSqlQuery &query, qx::dao::detail::QxSqlEmbedQuery::type type, bool requirePreviousElement)
   {
      if ((requirePreviousElement) && (!m_pSqlElementTemp))
      {
         qDebug("[QxOrm] qx::QxSqlQuery::addEmbedQuery : '%s'", "invalid SQL query, need a column name");
         qAssert(false);
         return (*this);
      }

      qx::dao::detail::QxSqlEmbedQuery_ptr p;
      p = std::make_shared<qx::dao::detail::QxSqlEmbedQuery>(m_iSqlElementIndex++, type);
      if (requirePreviousElement)
      {
         p->clone(m_pSqlElementTemp.get());
      }
      p->setQuery(query);

      m_lstSqlElement.append(p);
      m_pSqlElementTemp.reset();
      return (*this);
   }

} // namespace qx

namespace qx
{
   namespace dao
   {

      QSqlError call_query(qx::QxSqlQuery &query, QSqlDatabase *pDatabase /* = NULL */) { return qx::dao::helper::call_query_helper(query, pDatabase, true); }

      QSqlError call_query_without_prepare(qx::QxSqlQuery &query, QSqlDatabase *pDatabase /* = NULL */) { return qx::dao::helper::call_query_helper(query, pDatabase, false); }

      namespace helper
      {

         QSqlError call_query_helper(qx::QxSqlQuery &query, QSqlDatabase *pDatabase, bool bPrepare)
         {
#ifdef _QX_ENABLE_MONGODB
            if (qx::QxSqlDatabase::getSingleton()->getDriverName() == "QXMONGODB")
            {
               return qx::dao::mongodb::QxMongoDB_Helper::executeCommand(NULL, NULL, (&query));
            }
#endif // _QX_ENABLE_MONGODB

            QSqlError dbError;
            QSqlDatabase d = (pDatabase ? (*pDatabase) : qx::QxSqlDatabase::getDatabase(dbError));
            if (dbError.isValid())
            {
               return dbError;
            }
            bool bBoundValues = qx::QxSqlDatabase::getSingleton()->getTraceSqlBoundValues();
            bool bBoundValuesOnError = qx::QxSqlDatabase::getSingleton()->getTraceSqlBoundValuesOnError();
            QElapsedTimer timer;
            timer.start();
            QString sql = query.query();
            QSqlQuery q = QSqlQuery(d);
            q.setForwardOnly(true);

            do
            {
               if (bPrepare && !q.prepare(sql))
               {
                  dbError = q.lastError();
                  break;
               }
               query.resolve(q);
               if (bPrepare)
               {
                  if (!q.exec())
                  {
                     dbError = q.lastError();
                     break;
                  }
               }
               else
               {
                  if (!q.exec(sql))
                  {
                     dbError = q.lastError();
                     break;
                  }
               }
               query.resolveOutput(q, true);
            } while (0);

            qlonglong ms = static_cast<qlonglong>(timer.elapsed());
            if (dbError.isValid())
            {
               QString log = "custom sql query failed (" + QString::number(ms) + " ms) : " + sql;
               qDebug("[QxOrm] %s", qPrintable(log));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
               QString serr = dbError.nativeErrorCode();
#else  // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
               int ierr = dbError.number();
               QString serr = QString::number(ierr);
#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
               QString tmp = dbError.driverText();
               qDebug("Database error number '%s' : %s", qPrintable(serr), qPrintable(tmp));
               tmp = dbError.databaseText();
               qDebug("%s", qPrintable(tmp));
            }
            else if (qx::QxSqlDatabase::getSingleton()->getTraceSqlQuery())
            {
               qlonglong iTraceSqlOnlySlowQueriesDatabase = static_cast<qlonglong>(qx::QxSqlDatabase::getSingleton()->getTraceSqlOnlySlowQueriesDatabase());
               qlonglong iTraceSqlOnlySlowQueriesTotal = static_cast<qlonglong>(qx::QxSqlDatabase::getSingleton()->getTraceSqlOnlySlowQueriesTotal());
               if ((iTraceSqlOnlySlowQueriesDatabase > 0) && (iTraceSqlOnlySlowQueriesTotal < 0))
               {
                  iTraceSqlOnlySlowQueriesTotal = 999999999;
               }
               else if ((iTraceSqlOnlySlowQueriesTotal > 0) && (iTraceSqlOnlySlowQueriesDatabase < 0))
               {
                  iTraceSqlOnlySlowQueriesDatabase = 999999999;
               }
               if ((ms >= iTraceSqlOnlySlowQueriesTotal) || (ms >= iTraceSqlOnlySlowQueriesDatabase))
               {
                  QString log = "custom sql query (" + QString::number(ms) + " ms) : " + sql;
                  qDebug("[QxOrm] %s", qPrintable(log));
               }
            }

            if ((dbError.isValid() && bBoundValuesOnError) || (bBoundValues))
            {
               qx::QxSqlQuery::dumpBoundValues(q);
            }
            return dbError;
         }

      } // namespace helper
   } // namespace dao
} // namespace qx

#ifdef _QX_ENABLE_BOOST_SERIALIZATION

namespace boost
{
   namespace serialization
   {

      template <class Archive>
      inline void qx_save(Archive &ar, const qx::QxSqlQuery &t, const unsigned int file_version)
      {
         Q_UNUSED(file_version);
         QHash<QString, int> lstResultPosByKey;
         QVector<QVector<QVariant>> lstResultValues;
         qx::dao::detail::IxSqlElement::type_class eNoSqlType = qx::dao::detail::IxSqlElement::_no_type;

         if (t.m_pSqlResult)
         {
            lstResultPosByKey = t.m_pSqlResult->positionByKey;
            lstResultValues = t.m_pSqlResult->values;
         }

         ar << boost::serialization::make_nvp("query", t.m_sQuery);
         ar << boost::serialization::make_nvp("list_values", t.m_lstValue);
         ar << boost::serialization::make_nvp("sql_element_index", t.m_iSqlElementIndex);
         ar << boost::serialization::make_nvp("parenthesis_count", t.m_iParenthesisCount);
         ar << boost::serialization::make_nvp("distinct", t.m_bDistinct);
         ar << boost::serialization::make_nvp("result_position_by_key", lstResultPosByKey);
         ar << boost::serialization::make_nvp("result_values", lstResultValues);
         ar << boost::serialization::make_nvp("response", t.m_vResponse);
         ar << boost::serialization::make_nvp("type", t.m_sType);
         ar << boost::serialization::make_nvp("list_join_query_user", t.m_lstJoinQueryUser);
         ar << boost::serialization::make_nvp("list_join_query_resolve", t.m_lstJoinQueryToResolve);

         if (!t.m_pSqlElementTemp)
         {
            ar << boost::serialization::make_nvp("sql_element_temp_type", eNoSqlType);
         }
         else
         {
            qx::dao::detail::IxSqlElement::type_class eTypeSqlElement = t.m_pSqlElementTemp->getTypeClass();
            ar << boost::serialization::make_nvp("sql_element_temp_type", eTypeSqlElement);
            t.m_pSqlElementTemp->qxSave(ar);
         }

         long lSqlElementCount = t.m_lstSqlElement.count();
         ar << boost::serialization::make_nvp("sql_element_count", lSqlElementCount);
         Q_FOREACH (qx::dao::detail::IxSqlElement_ptr pSqlElement, t.m_lstSqlElement)
         {
            if (!pSqlElement)
            {
               ar << boost::serialization::make_nvp("sql_element_type", eNoSqlType);
               continue;
            }
            qx::dao::detail::IxSqlElement::type_class eTypeSqlElement = pSqlElement->getTypeClass();
            ar << boost::serialization::make_nvp("sql_element_type", eTypeSqlElement);
            pSqlElement->qxSave(ar);
         }
      }

      template <class Archive>
      inline void qx_load(Archive &ar, qx::QxSqlQuery &t, const unsigned int file_version)
      {
         Q_UNUSED(file_version);
         QHash<QString, int> lstResultPosByKey;
         QVector<QVector<QVariant>> lstResultValues;

         ar >> boost::serialization::make_nvp("query", t.m_sQuery);
         ar >> boost::serialization::make_nvp("list_values", t.m_lstValue);
         ar >> boost::serialization::make_nvp("sql_element_index", t.m_iSqlElementIndex);
         ar >> boost::serialization::make_nvp("parenthesis_count", t.m_iParenthesisCount);
         ar >> boost::serialization::make_nvp("distinct", t.m_bDistinct);
         ar >> boost::serialization::make_nvp("result_position_by_key", lstResultPosByKey);
         ar >> boost::serialization::make_nvp("result_values", lstResultValues);
         ar >> boost::serialization::make_nvp("response", t.m_vResponse);
         ar >> boost::serialization::make_nvp("type", t.m_sType);
         ar >> boost::serialization::make_nvp("list_join_query_user", t.m_lstJoinQueryUser);
         ar >> boost::serialization::make_nvp("list_join_query_resolve", t.m_lstJoinQueryToResolve);

         t.m_pSqlResult.reset();
         if ((lstResultPosByKey.count() > 0) || (lstResultValues.count() > 0))
         {
            t.m_pSqlResult = std::shared_ptr<qx::QxSqlQuery::QxSqlResult>(new qx::QxSqlQuery::QxSqlResult());
            t.m_pSqlResult->positionByKey = lstResultPosByKey;
            t.m_pSqlResult->values = lstResultValues;
         }

         t.m_pSqlElementTemp.reset();
         qx::dao::detail::IxSqlElement::type_class eTypeSqlElement = qx::dao::detail::IxSqlElement::_no_type;
         ar >> boost::serialization::make_nvp("sql_element_temp_type", eTypeSqlElement);
         if (eTypeSqlElement != qx::dao::detail::IxSqlElement::_no_type)
         {
            t.m_pSqlElementTemp = qx::dao::detail::create_sql_element(eTypeSqlElement);
            qAssert(t.m_pSqlElementTemp);
            if (t.m_pSqlElementTemp)
            {
               t.m_pSqlElementTemp->qxLoad(ar);
            }
         }

         t.m_lstSqlElement.clear();
         long lSqlElementCount = 0;
         ar >> boost::serialization::make_nvp("sql_element_count", lSqlElementCount);
         t.m_lstSqlElement.reserve(lSqlElementCount);
         for (long l = 0; l < lSqlElementCount; l++)
         {
            qx::dao::detail::IxSqlElement_ptr pSqlElement;
            eTypeSqlElement = qx::dao::detail::IxSqlElement::_no_type;
            ar >> boost::serialization::make_nvp("sql_element_type", eTypeSqlElement);
            if (eTypeSqlElement != qx::dao::detail::IxSqlElement::_no_type)
            {
               pSqlElement = qx::dao::detail::create_sql_element(eTypeSqlElement);
               qAssert(pSqlElement);
               if (pSqlElement)
               {
                  pSqlElement->qxLoad(ar);
               }
            }
            t.m_lstSqlElement.append(pSqlElement);
         }
      }

   } // namespace serialization
} // namespace boost

QX_SERIALIZE_FAST_COMPIL_SAVE_LOAD_CPP(qx::QxSqlQuery)

#endif // _QX_ENABLE_BOOST_SERIALIZATION

QDataStream &operator<<(QDataStream &stream, const qx::QxSqlQuery &t)
{
   QHash<QString, int> lstResultPosByKey;
   QVector<QVector<QVariant>> lstResultValues;
   qx::dao::detail::IxSqlElement::type_class eNoSqlType = qx::dao::detail::IxSqlElement::_no_type;

   if (t.m_pSqlResult)
   {
      lstResultPosByKey = t.m_pSqlResult->positionByKey;
      lstResultValues = t.m_pSqlResult->values;
   }

   stream << t.m_sQuery;
   stream << t.m_lstValue;
   stream << t.m_iSqlElementIndex;
   stream << t.m_iParenthesisCount;
   stream << t.m_bDistinct;
   stream << lstResultPosByKey;
   stream << lstResultValues;
   stream << t.m_vResponse;
   stream << t.m_sType;
   stream << t.m_lstJoinQueryUser;
   stream << t.m_lstJoinQueryToResolve;

   if (!t.m_pSqlElementTemp)
   {
      stream << (qint32)(eNoSqlType);
   }
   else
   {
      qx::dao::detail::IxSqlElement::type_class eTypeSqlElement = t.m_pSqlElementTemp->getTypeClass();
      stream << (qint32)(eTypeSqlElement);
      stream << (*t.m_pSqlElementTemp);
   }

   long lSqlElementCount = t.m_lstSqlElement.count();
   stream << (qint64)(lSqlElementCount);
   Q_FOREACH (qx::dao::detail::IxSqlElement_ptr pSqlElement, t.m_lstSqlElement)
   {
      if (!pSqlElement)
      {
         stream << (qint32)(eNoSqlType);
         continue;
      }
      qx::dao::detail::IxSqlElement::type_class eTypeSqlElement = pSqlElement->getTypeClass();
      stream << (qint32)(eTypeSqlElement);
      stream << (*pSqlElement);
   }

   return stream;
}

QDataStream &operator>>(QDataStream &stream, qx::QxSqlQuery &t)
{
   QHash<QString, int> lstResultPosByKey;
   QVector<QVector<QVariant>> lstResultValues;
   qint32 i32Temp = 0;

   stream >> t.m_sQuery;
   stream >> t.m_lstValue;
   stream >> t.m_iSqlElementIndex;
   stream >> t.m_iParenthesisCount;
   stream >> t.m_bDistinct;
   stream >> lstResultPosByKey;
   stream >> lstResultValues;
   stream >> t.m_vResponse;
   stream >> t.m_sType;
   stream >> t.m_lstJoinQueryUser;
   stream >> t.m_lstJoinQueryToResolve;

   t.m_pSqlResult.reset();
   if ((lstResultPosByKey.count() > 0) || (lstResultValues.count() > 0))
   {
      t.m_pSqlResult = std::shared_ptr<qx::QxSqlQuery::QxSqlResult>(new qx::QxSqlQuery::QxSqlResult());
      t.m_pSqlResult->positionByKey = lstResultPosByKey;
      t.m_pSqlResult->values = lstResultValues;
   }

   t.m_pSqlElementTemp.reset();
   qx::dao::detail::IxSqlElement::type_class eTypeSqlElement = qx::dao::detail::IxSqlElement::_no_type;
   stream >> i32Temp;
   eTypeSqlElement = static_cast<qx::dao::detail::IxSqlElement::type_class>(i32Temp);
   if (eTypeSqlElement != qx::dao::detail::IxSqlElement::_no_type)
   {
      t.m_pSqlElementTemp = qx::dao::detail::create_sql_element(eTypeSqlElement);
      qAssert(t.m_pSqlElementTemp);
      if (t.m_pSqlElementTemp)
      {
         stream >> (*t.m_pSqlElementTemp);
      }
   }

   t.m_lstSqlElement.clear();
   qint64 lSqlElementCount = 0;
   stream >> lSqlElementCount;
   t.m_lstSqlElement.reserve(static_cast<long>(lSqlElementCount));
   for (long l = 0; l < static_cast<long>(lSqlElementCount); l++)
   {
      qx::dao::detail::IxSqlElement_ptr pSqlElement;
      eTypeSqlElement = qx::dao::detail::IxSqlElement::_no_type;
      stream >> i32Temp;
      eTypeSqlElement = static_cast<qx::dao::detail::IxSqlElement::type_class>(i32Temp);
      if (eTypeSqlElement != qx::dao::detail::IxSqlElement::_no_type)
      {
         pSqlElement = qx::dao::detail::create_sql_element(eTypeSqlElement);
         qAssert(pSqlElement);
         if (pSqlElement)
         {
            stream >> (*pSqlElement);
         }
      }
      t.m_lstSqlElement.append(pSqlElement);
   }

   return stream;
}
