src/sql/drivers/db2/qsql_db2.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtSql module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qsql_db2.h"
       
    43 #include <qcoreapplication.h>
       
    44 #include <qdatetime.h>
       
    45 #include <qsqlfield.h>
       
    46 #include <qsqlerror.h>
       
    47 #include <qsqlindex.h>
       
    48 #include <qsqlrecord.h>
       
    49 #include <qstringlist.h>
       
    50 #include <qvarlengtharray.h>
       
    51 #include <qvector.h>
       
    52 #include <QDebug>
       
    53 
       
    54 #if defined(Q_CC_BOR)
       
    55 // DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE
       
    56 // and SQL_BIGUINT_TYPE to wrong the types for Borland; so do the defines to
       
    57 // the right type before including the header
       
    58 #define SQL_BIGINT_TYPE qint64
       
    59 #define SQL_BIGUINT_TYPE quint64
       
    60 #endif
       
    61 
       
    62 #define UNICODE
       
    63 
       
    64 #include <sqlcli1.h>
       
    65 
       
    66 #include <string.h>
       
    67 
       
    68 QT_BEGIN_NAMESPACE
       
    69 
       
    70 static const int COLNAMESIZE = 255;
       
    71 static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
       
    72 
       
    73 class QDB2DriverPrivate
       
    74 {
       
    75 public:
       
    76     QDB2DriverPrivate(): hEnv(0), hDbc(0) {}
       
    77     SQLHANDLE hEnv;
       
    78     SQLHANDLE hDbc;
       
    79     QString user;
       
    80 };
       
    81 
       
    82 class QDB2ResultPrivate
       
    83 {
       
    84 public:
       
    85     QDB2ResultPrivate(const QDB2DriverPrivate* d): dp(d), hStmt(0)
       
    86     {}
       
    87     ~QDB2ResultPrivate()
       
    88     {
       
    89         emptyValueCache();
       
    90     }
       
    91     void clearValueCache()
       
    92     {
       
    93         for (int i = 0; i < valueCache.count(); ++i) {
       
    94             delete valueCache[i];
       
    95             valueCache[i] = NULL;
       
    96         }
       
    97     }
       
    98     void emptyValueCache()
       
    99     {
       
   100         clearValueCache();
       
   101         valueCache.clear();
       
   102     }
       
   103 
       
   104     const QDB2DriverPrivate* dp;
       
   105     SQLHANDLE hStmt;
       
   106     QSqlRecord recInf;
       
   107     QVector<QVariant*> valueCache;
       
   108 };
       
   109 
       
   110 static QString qFromTChar(SQLTCHAR* str)
       
   111 {
       
   112     return QString::fromUtf16(str);
       
   113 }
       
   114 
       
   115 // dangerous!! (but fast). Don't use in functions that
       
   116 // require out parameters!
       
   117 static SQLTCHAR* qToTChar(const QString& str)
       
   118 {
       
   119     return (SQLTCHAR*)str.utf16();
       
   120 }
       
   121 
       
   122 static QString qWarnDB2Handle(int handleType, SQLHANDLE handle)
       
   123 {
       
   124     SQLINTEGER nativeCode;
       
   125     SQLSMALLINT msgLen;
       
   126     SQLRETURN r = SQL_ERROR;
       
   127     SQLTCHAR state[SQL_SQLSTATE_SIZE + 1];
       
   128     SQLTCHAR description[SQL_MAX_MESSAGE_LENGTH];
       
   129     r = SQLGetDiagRec(handleType,
       
   130                        handle,
       
   131                        1,
       
   132                        (SQLTCHAR*) state,
       
   133                        &nativeCode,
       
   134                        (SQLTCHAR*) description,
       
   135                        SQL_MAX_MESSAGE_LENGTH - 1, /* in bytes, not in characters */
       
   136                        &msgLen);
       
   137     if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
       
   138         return QString(qFromTChar(description));
       
   139     return QString();
       
   140 }
       
   141 
       
   142 static QString qDB2Warn(const QDB2DriverPrivate* d)
       
   143 {
       
   144     return (qWarnDB2Handle(SQL_HANDLE_ENV, d->hEnv) + QLatin1Char(' ')
       
   145              + qWarnDB2Handle(SQL_HANDLE_DBC, d->hDbc));
       
   146 }
       
   147 
       
   148 static QString qDB2Warn(const QDB2ResultPrivate* d)
       
   149 {
       
   150     return (qWarnDB2Handle(SQL_HANDLE_ENV, d->dp->hEnv) + QLatin1Char(' ')
       
   151              + qWarnDB2Handle(SQL_HANDLE_DBC, d->dp->hDbc)
       
   152              + qWarnDB2Handle(SQL_HANDLE_STMT, d->hStmt));
       
   153 }
       
   154 
       
   155 static void qSqlWarning(const QString& message, const QDB2DriverPrivate* d)
       
   156 {
       
   157     qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
       
   158                               qDB2Warn(d).toLocal8Bit().constData());
       
   159 }
       
   160 
       
   161 static void qSqlWarning(const QString& message, const QDB2ResultPrivate* d)
       
   162 {
       
   163     qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
       
   164                               qDB2Warn(d).toLocal8Bit().constData());
       
   165 }
       
   166 
       
   167 static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
       
   168                             const QDB2DriverPrivate* p)
       
   169 {
       
   170     return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
       
   171 }
       
   172 
       
   173 static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
       
   174                             const QDB2ResultPrivate* p)
       
   175 {
       
   176     return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
       
   177 }
       
   178 
       
   179 static QVariant::Type qDecodeDB2Type(SQLSMALLINT sqltype)
       
   180 {
       
   181     QVariant::Type type = QVariant::Invalid;
       
   182     switch (sqltype) {
       
   183     case SQL_REAL:
       
   184     case SQL_FLOAT:
       
   185     case SQL_DOUBLE:
       
   186     case SQL_DECIMAL:
       
   187     case SQL_NUMERIC:
       
   188         type = QVariant::Double;
       
   189         break;
       
   190     case SQL_SMALLINT:
       
   191     case SQL_INTEGER:
       
   192     case SQL_BIT:
       
   193     case SQL_TINYINT:
       
   194         type = QVariant::Int;
       
   195         break;
       
   196     case SQL_BIGINT:
       
   197         type = QVariant::LongLong;
       
   198         break;
       
   199     case SQL_BLOB:
       
   200     case SQL_BINARY:
       
   201     case SQL_VARBINARY:
       
   202     case SQL_LONGVARBINARY:
       
   203     case SQL_CLOB:
       
   204     case SQL_DBCLOB:
       
   205         type = QVariant::ByteArray;
       
   206         break;
       
   207     case SQL_DATE:
       
   208     case SQL_TYPE_DATE:
       
   209         type = QVariant::Date;
       
   210         break;
       
   211     case SQL_TIME:
       
   212     case SQL_TYPE_TIME:
       
   213         type = QVariant::Time;
       
   214         break;
       
   215     case SQL_TIMESTAMP:
       
   216     case SQL_TYPE_TIMESTAMP:
       
   217         type = QVariant::DateTime;
       
   218         break;
       
   219     case SQL_WCHAR:
       
   220     case SQL_WVARCHAR:
       
   221     case SQL_WLONGVARCHAR:
       
   222     case SQL_CHAR:
       
   223     case SQL_VARCHAR:
       
   224     case SQL_LONGVARCHAR:
       
   225         type = QVariant::String;
       
   226         break;
       
   227     default:
       
   228         type = QVariant::ByteArray;
       
   229         break;
       
   230     }
       
   231     return type;
       
   232 }
       
   233 
       
   234 static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i)
       
   235 {
       
   236     SQLSMALLINT colNameLen;
       
   237     SQLSMALLINT colType;
       
   238     SQLUINTEGER colSize;
       
   239     SQLSMALLINT colScale;
       
   240     SQLSMALLINT nullable;
       
   241     SQLRETURN r = SQL_ERROR;
       
   242     SQLTCHAR colName[COLNAMESIZE];
       
   243     r = SQLDescribeCol(d->hStmt,
       
   244                         i+1,
       
   245                         colName,
       
   246                         (SQLSMALLINT) COLNAMESIZE,
       
   247                         &colNameLen,
       
   248                         &colType,
       
   249                         &colSize,
       
   250                         &colScale,
       
   251                         &nullable);
       
   252 
       
   253     if (r != SQL_SUCCESS) {
       
   254         qSqlWarning(QString::fromLatin1("qMakeFieldInfo: Unable to describe column %1").arg(i), d);
       
   255         return QSqlField();
       
   256     }
       
   257     QSqlField f(qFromTChar(colName), qDecodeDB2Type(colType));
       
   258     // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
       
   259     if (nullable == SQL_NO_NULLS)
       
   260         f.setRequired(true);
       
   261     else if (nullable == SQL_NULLABLE)
       
   262         f.setRequired(false);
       
   263     // else required is unknown
       
   264     f.setLength(colSize == 0 ? -1 : int(colSize));
       
   265     f.setPrecision(colScale == 0 ? -1 : int(colScale));
       
   266     f.setSqlType(int(colType));
       
   267     return f;
       
   268 }
       
   269 
       
   270 static int qGetIntData(SQLHANDLE hStmt, int column, bool& isNull)
       
   271 {
       
   272     SQLINTEGER intbuf;
       
   273     isNull = false;
       
   274     SQLINTEGER lengthIndicator = 0;
       
   275     SQLRETURN r = SQLGetData(hStmt,
       
   276                               column + 1,
       
   277                               SQL_C_SLONG,
       
   278                               (SQLPOINTER) &intbuf,
       
   279                               0,
       
   280                               &lengthIndicator);
       
   281     if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
       
   282         isNull = true;
       
   283         return 0;
       
   284     }
       
   285     return int(intbuf);
       
   286 }
       
   287 
       
   288 static double qGetDoubleData(SQLHANDLE hStmt, int column, bool& isNull)
       
   289 {
       
   290     SQLDOUBLE dblbuf;
       
   291     isNull = false;
       
   292     SQLINTEGER lengthIndicator = 0;
       
   293     SQLRETURN r = SQLGetData(hStmt,
       
   294                               column+1,
       
   295                               SQL_C_DOUBLE,
       
   296                               (SQLPOINTER) &dblbuf,
       
   297                               0,
       
   298                               &lengthIndicator);
       
   299     if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
       
   300         isNull = true;
       
   301         return 0.0;
       
   302     }
       
   303 
       
   304     return (double) dblbuf;
       
   305 }
       
   306 
       
   307 static SQLBIGINT qGetBigIntData(SQLHANDLE hStmt, int column, bool& isNull)
       
   308 {
       
   309     SQLBIGINT lngbuf = Q_INT64_C(0);
       
   310     isNull = false;
       
   311     SQLINTEGER lengthIndicator = 0;
       
   312     SQLRETURN r = SQLGetData(hStmt,
       
   313                               column+1,
       
   314                               SQL_C_SBIGINT,
       
   315                               (SQLPOINTER) &lngbuf,
       
   316                               0,
       
   317                               &lengthIndicator);
       
   318     if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA)
       
   319         isNull = true;
       
   320 
       
   321     return lngbuf;
       
   322 }
       
   323 
       
   324 static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool& isNull)
       
   325 {
       
   326     QString     fieldVal;
       
   327     SQLRETURN   r = SQL_ERROR;
       
   328     SQLINTEGER  lengthIndicator = 0;
       
   329 
       
   330     if (colSize <= 0)
       
   331         colSize = 255;
       
   332     else if (colSize > 65536) // limit buffer size to 64 KB
       
   333         colSize = 65536;
       
   334     else
       
   335         colSize++; // make sure there is room for more than the 0 termination
       
   336     SQLTCHAR* buf = new SQLTCHAR[colSize];
       
   337 
       
   338     while (true) {
       
   339         r = SQLGetData(hStmt,
       
   340                         column + 1,
       
   341                         SQL_C_WCHAR,
       
   342                         (SQLPOINTER)buf,
       
   343                         colSize * sizeof(SQLTCHAR),
       
   344                         &lengthIndicator);
       
   345         if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
       
   346             if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
       
   347                 fieldVal.clear();
       
   348                 isNull = true;
       
   349                 break;
       
   350             }
       
   351             fieldVal += qFromTChar(buf);
       
   352         } else if (r == SQL_NO_DATA) {
       
   353             break;
       
   354         } else {
       
   355             qWarning("qGetStringData: Error while fetching data (%d)", r);
       
   356             fieldVal.clear();
       
   357             break;
       
   358         }
       
   359     }
       
   360     delete[] buf;
       
   361     return fieldVal;
       
   362 }
       
   363 
       
   364 static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLINTEGER& lengthIndicator, bool& isNull)
       
   365 {
       
   366     QByteArray fieldVal;
       
   367     SQLSMALLINT colNameLen;
       
   368     SQLSMALLINT colType;
       
   369     SQLUINTEGER colSize;
       
   370     SQLSMALLINT colScale;
       
   371     SQLSMALLINT nullable;
       
   372     SQLRETURN r = SQL_ERROR;
       
   373 
       
   374     SQLTCHAR colName[COLNAMESIZE];
       
   375     r = SQLDescribeCol(hStmt,
       
   376                         column+1,
       
   377                         colName,
       
   378                         COLNAMESIZE,
       
   379                         &colNameLen,
       
   380                         &colType,
       
   381                         &colSize,
       
   382                         &colScale,
       
   383                         &nullable);
       
   384     if (r != SQL_SUCCESS)
       
   385         qWarning("qGetBinaryData: Unable to describe column %d", column);
       
   386     // SQLDescribeCol may return 0 if size cannot be determined
       
   387     if (!colSize)
       
   388         colSize = 255;
       
   389     else if (colSize > 65536) // read the field in 64 KB chunks
       
   390         colSize = 65536;
       
   391     char * buf = new char[colSize];
       
   392     while (true) {
       
   393         r = SQLGetData(hStmt,
       
   394                         column+1,
       
   395                         colType == SQL_DBCLOB ? SQL_C_CHAR : SQL_C_BINARY,
       
   396                         (SQLPOINTER) buf,
       
   397                         colSize,
       
   398                         &lengthIndicator);
       
   399         if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
       
   400             if (lengthIndicator == SQL_NULL_DATA) {
       
   401                 isNull = true;
       
   402                 break;
       
   403             } else {
       
   404                 int rSize;
       
   405                 r == SQL_SUCCESS ? rSize = lengthIndicator : rSize = colSize;
       
   406                 if (lengthIndicator == SQL_NO_TOTAL) // size cannot be determined
       
   407                     rSize = colSize;
       
   408                 fieldVal.append(QByteArray(buf, rSize));
       
   409                 if (r == SQL_SUCCESS) // the whole field was read in one chunk
       
   410                     break;
       
   411             }
       
   412         } else {
       
   413             break;
       
   414         }
       
   415     }
       
   416     delete [] buf;
       
   417     return fieldVal;
       
   418 }
       
   419 
       
   420 static void qSplitTableQualifier(const QString & qualifier, QString * catalog,
       
   421                                   QString * schema, QString * table)
       
   422 {
       
   423     if (!catalog || !schema || !table)
       
   424         return;
       
   425     QStringList l = qualifier.split(QLatin1Char('.'));
       
   426     if (l.count() > 3)
       
   427         return; // can't possibly be a valid table qualifier
       
   428     int i = 0, n = l.count();
       
   429     if (n == 1) {
       
   430         *table = qualifier;
       
   431     } else {
       
   432         for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
       
   433             if (n == 3) {
       
   434                 if (i == 0)
       
   435                     *catalog = *it;
       
   436                 else if (i == 1)
       
   437                     *schema = *it;
       
   438                 else if (i == 2)
       
   439                     *table = *it;
       
   440             } else if (n == 2) {
       
   441                 if (i == 0)
       
   442                     *schema = *it;
       
   443                 else if (i == 1)
       
   444                     *table = *it;
       
   445             }
       
   446             i++;
       
   447         }
       
   448     }
       
   449 }
       
   450 
       
   451 // creates a QSqlField from a valid hStmt generated
       
   452 // by SQLColumns. The hStmt has to point to a valid position.
       
   453 static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt)
       
   454 {
       
   455     bool isNull;
       
   456     int type = qGetIntData(hStmt, 4, isNull);
       
   457     QSqlField f(qGetStringData(hStmt, 3, -1, isNull), qDecodeDB2Type(type));
       
   458     int required = qGetIntData(hStmt, 10, isNull); // nullable-flag
       
   459     // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
       
   460     if (required == SQL_NO_NULLS)
       
   461         f.setRequired(true);
       
   462     else if (required == SQL_NULLABLE)
       
   463         f.setRequired(false);
       
   464     // else we don't know.
       
   465     f.setLength(qGetIntData(hStmt, 6, isNull)); // column size
       
   466     f.setPrecision(qGetIntData(hStmt, 8, isNull)); // precision
       
   467     f.setSqlType(type);
       
   468     return f;
       
   469 }
       
   470 
       
   471 static bool qMakeStatement(QDB2ResultPrivate* d, bool forwardOnly, bool setForwardOnly = true)
       
   472 {
       
   473     SQLRETURN r;
       
   474     if (!d->hStmt) {
       
   475         r = SQLAllocHandle(SQL_HANDLE_STMT,
       
   476                             d->dp->hDbc,
       
   477                             &d->hStmt);
       
   478         if (r != SQL_SUCCESS) {
       
   479             qSqlWarning(QLatin1String("QDB2Result::reset: Unable to allocate statement handle"), d);
       
   480             return false;
       
   481         }
       
   482     } else {
       
   483         r = SQLFreeStmt(d->hStmt, SQL_CLOSE);
       
   484         if (r != SQL_SUCCESS) {
       
   485             qSqlWarning(QLatin1String("QDB2Result::reset: Unable to close statement handle"), d);
       
   486             return false;
       
   487         }
       
   488     }
       
   489 
       
   490     if (!setForwardOnly)
       
   491         return true;
       
   492 
       
   493     if (forwardOnly) {
       
   494         r = SQLSetStmtAttr(d->hStmt,
       
   495                             SQL_ATTR_CURSOR_TYPE,
       
   496                             (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY,
       
   497                             SQL_IS_UINTEGER);
       
   498     } else {
       
   499         r = SQLSetStmtAttr(d->hStmt,
       
   500                             SQL_ATTR_CURSOR_TYPE,
       
   501                             (SQLPOINTER) SQL_CURSOR_STATIC,
       
   502                             SQL_IS_UINTEGER);
       
   503     }
       
   504     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
   505         qSqlWarning(QString::fromLatin1("QDB2Result::reset: Unable to set %1 attribute.").arg(
       
   506                      forwardOnly ? QLatin1String("SQL_CURSOR_FORWARD_ONLY")
       
   507                                  : QLatin1String("SQL_CURSOR_STATIC")), d);
       
   508         return false;
       
   509     }
       
   510     return true;
       
   511 }
       
   512 
       
   513 QVariant QDB2Result::handle() const
       
   514 {
       
   515     return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
       
   516 }
       
   517 
       
   518 /************************************/
       
   519 
       
   520 QDB2Result::QDB2Result(const QDB2Driver* dr, const QDB2DriverPrivate* dp)
       
   521     : QSqlResult(dr)
       
   522 {
       
   523     d = new QDB2ResultPrivate(dp);
       
   524 }
       
   525 
       
   526 QDB2Result::~QDB2Result()
       
   527 {
       
   528     if (d->hStmt) {
       
   529         SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
       
   530         if (r != SQL_SUCCESS)
       
   531             qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
       
   532                         + QString::number(r), d);
       
   533     }
       
   534     delete d;
       
   535 }
       
   536 
       
   537 bool QDB2Result::reset (const QString& query)
       
   538 {
       
   539     setActive(false);
       
   540     setAt(QSql::BeforeFirstRow);
       
   541     SQLRETURN r;
       
   542 
       
   543     d->recInf.clear();
       
   544     d->emptyValueCache();
       
   545 
       
   546     if (!qMakeStatement(d, isForwardOnly()))
       
   547         return false;
       
   548 
       
   549     r = SQLExecDirect(d->hStmt,
       
   550                        qToTChar(query),
       
   551                        (SQLINTEGER) query.length());
       
   552     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
   553         setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
       
   554                                 "Unable to execute statement"), QSqlError::StatementError, d));
       
   555         return false;
       
   556     }
       
   557     SQLSMALLINT count;
       
   558     r = SQLNumResultCols(d->hStmt, &count);
       
   559     if (count) {
       
   560         setSelect(true);
       
   561         for (int i = 0; i < count; ++i) {
       
   562             d->recInf.append(qMakeFieldInfo(d, i));
       
   563         }
       
   564     } else {
       
   565         setSelect(false);
       
   566     }
       
   567     d->valueCache.resize(count);
       
   568     d->valueCache.fill(NULL);
       
   569     setActive(true);
       
   570     return true;
       
   571 }
       
   572 
       
   573 bool QDB2Result::prepare(const QString& query)
       
   574 {
       
   575     setActive(false);
       
   576     setAt(QSql::BeforeFirstRow);
       
   577     SQLRETURN r;
       
   578 
       
   579     d->recInf.clear();
       
   580     d->emptyValueCache();
       
   581 
       
   582     if (!qMakeStatement(d, isForwardOnly()))
       
   583         return false;
       
   584 
       
   585     r = SQLPrepare(d->hStmt,
       
   586                     qToTChar(query),
       
   587                     (SQLINTEGER) query.length());
       
   588 
       
   589     if (r != SQL_SUCCESS) {
       
   590         setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
       
   591                      "Unable to prepare statement"), QSqlError::StatementError, d));
       
   592         return false;
       
   593     }
       
   594     return true;
       
   595 }
       
   596 
       
   597 bool QDB2Result::exec()
       
   598 {
       
   599     QList<QByteArray> tmpStorage; // holds temporary ptrs
       
   600     QVarLengthArray<SQLINTEGER, 32> indicators(boundValues().count());
       
   601 
       
   602     memset(indicators.data(), 0, indicators.size() * sizeof(SQLINTEGER));
       
   603     setActive(false);
       
   604     setAt(QSql::BeforeFirstRow);
       
   605     SQLRETURN r;
       
   606 
       
   607     d->recInf.clear();
       
   608     d->emptyValueCache();
       
   609 
       
   610     if (!qMakeStatement(d, isForwardOnly(), false))
       
   611         return false;
       
   612 
       
   613 
       
   614     QVector<QVariant> &values = boundValues();
       
   615     int i;
       
   616     for (i = 0; i < values.count(); ++i) {
       
   617         // bind parameters - only positional binding allowed
       
   618         SQLINTEGER *ind = &indicators[i];
       
   619         if (values.at(i).isNull())
       
   620             *ind = SQL_NULL_DATA;
       
   621         if (bindValueType(i) & QSql::Out)
       
   622             values[i].detach();
       
   623 
       
   624         switch (values.at(i).type()) {
       
   625             case QVariant::Date: {
       
   626                 QByteArray ba;
       
   627                 ba.resize(sizeof(DATE_STRUCT));
       
   628                 DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData();
       
   629                 QDate qdt = values.at(i).toDate();
       
   630                 dt->year = qdt.year();
       
   631                 dt->month = qdt.month();
       
   632                 dt->day = qdt.day();
       
   633                 r = SQLBindParameter(d->hStmt,
       
   634                                      i + 1,
       
   635                                      qParamType[(QFlag)(bindValueType(i)) & 3],
       
   636                                      SQL_C_DATE,
       
   637                                      SQL_DATE,
       
   638                                      0,
       
   639                                      0,
       
   640                                      (void *) dt,
       
   641                                      0,
       
   642                                      *ind == SQL_NULL_DATA ? ind : NULL);
       
   643                 tmpStorage.append(ba);
       
   644                 break; }
       
   645             case QVariant::Time: {
       
   646                 QByteArray ba;
       
   647                 ba.resize(sizeof(TIME_STRUCT));
       
   648                 TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData();
       
   649                 QTime qdt = values.at(i).toTime();
       
   650                 dt->hour = qdt.hour();
       
   651                 dt->minute = qdt.minute();
       
   652                 dt->second = qdt.second();
       
   653                 r = SQLBindParameter(d->hStmt,
       
   654                                       i + 1,
       
   655                                       qParamType[(QFlag)(bindValueType(i)) & 3],
       
   656                                       SQL_C_TIME,
       
   657                                       SQL_TIME,
       
   658                                       0,
       
   659                                       0,
       
   660                                       (void *) dt,
       
   661                                       0,
       
   662                                       *ind == SQL_NULL_DATA ? ind : NULL);
       
   663                 tmpStorage.append(ba);
       
   664                 break; }
       
   665             case QVariant::DateTime: {
       
   666                 QByteArray ba;
       
   667                 ba.resize(sizeof(TIMESTAMP_STRUCT));
       
   668                 TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData();
       
   669                 QDateTime qdt = values.at(i).toDateTime();
       
   670                 dt->year = qdt.date().year();
       
   671                 dt->month = qdt.date().month();
       
   672                 dt->day = qdt.date().day();
       
   673                 dt->hour = qdt.time().hour();
       
   674                 dt->minute = qdt.time().minute();
       
   675                 dt->second = qdt.time().second();
       
   676                 dt->fraction = qdt.time().msec() * 1000000;
       
   677                 r = SQLBindParameter(d->hStmt,
       
   678                                       i + 1,
       
   679                                       qParamType[(QFlag)(bindValueType(i)) & 3],
       
   680                                       SQL_C_TIMESTAMP,
       
   681                                       SQL_TIMESTAMP,
       
   682                                       0,
       
   683                                       0,
       
   684                                       (void *) dt,
       
   685                                       0,
       
   686                                       *ind == SQL_NULL_DATA ? ind : NULL);
       
   687                 tmpStorage.append(ba);
       
   688                 break; }
       
   689             case QVariant::Int:
       
   690                 r = SQLBindParameter(d->hStmt,
       
   691                                       i + 1,
       
   692                                       qParamType[(QFlag)(bindValueType(i)) & 3],
       
   693                                       SQL_C_SLONG,
       
   694                                       SQL_INTEGER,
       
   695                                       0,
       
   696                                       0,
       
   697                                       (void *)values.at(i).constData(),
       
   698                                       0,
       
   699                                       *ind == SQL_NULL_DATA ? ind : NULL);
       
   700                 break;
       
   701             case QVariant::Double:
       
   702                 r = SQLBindParameter(d->hStmt,
       
   703                                       i + 1,
       
   704                                       qParamType[(QFlag)(bindValueType(i)) & 3],
       
   705                                       SQL_C_DOUBLE,
       
   706                                       SQL_DOUBLE,
       
   707                                       0,
       
   708                                       0,
       
   709                                       (void *)values.at(i).constData(),
       
   710                                       0,
       
   711                                       *ind == SQL_NULL_DATA ? ind : NULL);
       
   712                 break;
       
   713             case QVariant::ByteArray: {
       
   714                 int len = values.at(i).toByteArray().size();
       
   715                 if (*ind != SQL_NULL_DATA)
       
   716                     *ind = len;
       
   717                 r = SQLBindParameter(d->hStmt,
       
   718                                       i + 1,
       
   719                                       qParamType[(QFlag)(bindValueType(i)) & 3],
       
   720                                       SQL_C_BINARY,
       
   721                                       SQL_LONGVARBINARY,
       
   722                                       len,
       
   723                                       0,
       
   724                                       (void *)values.at(i).toByteArray().constData(),
       
   725                                       len,
       
   726                                       ind);
       
   727                 break; }
       
   728             case QVariant::String:
       
   729             {
       
   730                 QString str(values.at(i).toString());
       
   731                 if (*ind != SQL_NULL_DATA)
       
   732                     *ind = str.length() * sizeof(QChar);
       
   733                 if (bindValueType(i) & QSql::Out) {
       
   734                     QByteArray ba((char*)str.utf16(), str.capacity() * sizeof(QChar));
       
   735                     r = SQLBindParameter(d->hStmt,
       
   736                                         i + 1,
       
   737                                         qParamType[(QFlag)(bindValueType(i)) & 3],
       
   738                                         SQL_C_WCHAR,
       
   739                                         SQL_WVARCHAR,
       
   740                                         str.length(),
       
   741                                         0,
       
   742                                         (void *)ba.constData(),
       
   743                                         ba.size(),
       
   744                                         ind);
       
   745                     tmpStorage.append(ba);
       
   746                 } else {
       
   747                     void *data = (void*)str.utf16();
       
   748                     int len = str.length();
       
   749                     r = SQLBindParameter(d->hStmt,
       
   750                                         i + 1,
       
   751                                         qParamType[(QFlag)(bindValueType(i)) & 3],
       
   752                                         SQL_C_WCHAR,
       
   753                                         SQL_WVARCHAR,
       
   754                                         len,
       
   755                                         0,
       
   756                                         data,
       
   757                                         len * sizeof(QChar),
       
   758                                         ind);
       
   759                 }
       
   760                 break;
       
   761             }
       
   762             default: {
       
   763                 QByteArray ba = values.at(i).toString().toAscii();
       
   764                 int len = ba.length() + 1;
       
   765                 if (*ind != SQL_NULL_DATA)
       
   766                     *ind = ba.length();
       
   767                 r = SQLBindParameter(d->hStmt,
       
   768                                       i + 1,
       
   769                                       qParamType[(QFlag)(bindValueType(i)) & 3],
       
   770                                       SQL_C_CHAR,
       
   771                                       SQL_VARCHAR,
       
   772                                       len,
       
   773                                       0,
       
   774                                       (void *) ba.constData(),
       
   775                                       len,
       
   776                                       ind);
       
   777                 tmpStorage.append(ba);
       
   778                 break; }
       
   779         }
       
   780         if (r != SQL_SUCCESS) {
       
   781             qWarning("QDB2Result::exec: unable to bind variable: %s",
       
   782                      qDB2Warn(d).toLocal8Bit().constData());
       
   783             setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
       
   784                                     "Unable to bind variable"), QSqlError::StatementError, d));
       
   785             return false;
       
   786         }
       
   787     }
       
   788 
       
   789     r = SQLExecute(d->hStmt);
       
   790     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
   791         qWarning("QDB2Result::exec: Unable to execute statement: %s",
       
   792                  qDB2Warn(d).toLocal8Bit().constData());
       
   793         setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
       
   794                                 "Unable to execute statement"), QSqlError::StatementError, d));
       
   795         return false;
       
   796     }
       
   797     SQLSMALLINT count;
       
   798     r = SQLNumResultCols(d->hStmt, &count);
       
   799     if (count) {
       
   800         setSelect(true);
       
   801         for (int i = 0; i < count; ++i) {
       
   802             d->recInf.append(qMakeFieldInfo(d, i));
       
   803         }
       
   804     } else {
       
   805         setSelect(false);
       
   806     }
       
   807     setActive(true);
       
   808     d->valueCache.resize(count);
       
   809     d->valueCache.fill(NULL);
       
   810 
       
   811     //get out parameters
       
   812     if (!hasOutValues())
       
   813         return true;
       
   814 
       
   815     for (i = 0; i < values.count(); ++i) {
       
   816         switch (values[i].type()) {
       
   817             case QVariant::Date: {
       
   818                 DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData());
       
   819                 values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
       
   820                 break; }
       
   821             case QVariant::Time: {
       
   822                 TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData());
       
   823                 values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
       
   824                 break; }
       
   825             case QVariant::DateTime: {
       
   826                 TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT *)tmpStorage.takeFirst().constData());
       
   827                 values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day),
       
   828                               QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000)));
       
   829                 break; }
       
   830             case QVariant::Int:
       
   831             case QVariant::Double:
       
   832             case QVariant::ByteArray:
       
   833                 break;
       
   834             case QVariant::String:
       
   835                 if (bindValueType(i) & QSql::Out)
       
   836                     values[i] = QString::fromUtf16((ushort*)tmpStorage.takeFirst().constData());
       
   837                 break;
       
   838             default: {
       
   839                 values[i] = QString::fromAscii(tmpStorage.takeFirst().constData());
       
   840                 break; }
       
   841         }
       
   842         if (indicators[i] == SQL_NULL_DATA)
       
   843             values[i] = QVariant(values[i].type());
       
   844     }
       
   845     return true;
       
   846 }
       
   847 
       
   848 bool QDB2Result::fetch(int i)
       
   849 {
       
   850     if (isForwardOnly() && i < at())
       
   851         return false;
       
   852     if (i == at())
       
   853         return true;
       
   854     d->clearValueCache();
       
   855     int actualIdx = i + 1;
       
   856     if (actualIdx <= 0) {
       
   857         setAt(QSql::BeforeFirstRow);
       
   858         return false;
       
   859     }
       
   860     SQLRETURN r;
       
   861     if (isForwardOnly()) {
       
   862         bool ok = true;
       
   863         while (ok && i > at())
       
   864             ok = fetchNext();
       
   865         return ok;
       
   866     } else {
       
   867         r = SQLFetchScroll(d->hStmt,
       
   868                             SQL_FETCH_ABSOLUTE,
       
   869                             actualIdx);
       
   870     }
       
   871     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
       
   872         setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
       
   873                                 "Unable to fetch record %1").arg(i), QSqlError::StatementError, d));
       
   874         return false;
       
   875     }
       
   876     else if (r == SQL_NO_DATA)
       
   877         return false;
       
   878     setAt(i);
       
   879     return true;
       
   880 }
       
   881 
       
   882 bool QDB2Result::fetchNext()
       
   883 {
       
   884     SQLRETURN r;
       
   885     d->clearValueCache();
       
   886     r = SQLFetchScroll(d->hStmt,
       
   887                        SQL_FETCH_NEXT,
       
   888                        0);
       
   889     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
   890         if (r != SQL_NO_DATA)
       
   891             setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
       
   892                                     "Unable to fetch next"), QSqlError::StatementError, d));
       
   893         return false;
       
   894     }
       
   895     setAt(at() + 1);
       
   896     return true;
       
   897 }
       
   898 
       
   899 bool QDB2Result::fetchFirst()
       
   900 {
       
   901     if (isForwardOnly() && at() != QSql::BeforeFirstRow)
       
   902         return false;
       
   903     if (isForwardOnly())
       
   904         return fetchNext();
       
   905     d->clearValueCache();
       
   906     SQLRETURN r;
       
   907     r = SQLFetchScroll(d->hStmt,
       
   908                        SQL_FETCH_FIRST,
       
   909                        0);
       
   910     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
   911         if(r!= SQL_NO_DATA)
       
   912             setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch first"),
       
   913                                     QSqlError::StatementError, d));
       
   914         return false;
       
   915     }
       
   916     setAt(0);
       
   917     return true;
       
   918 }
       
   919 
       
   920 bool QDB2Result::fetchLast()
       
   921 {
       
   922     d->clearValueCache();
       
   923 
       
   924     int i = at();
       
   925     if (i == QSql::AfterLastRow) {
       
   926         if (isForwardOnly()) {
       
   927             return false;
       
   928         } else {
       
   929             if (!fetch(0))
       
   930                 return false;
       
   931             i = at();
       
   932         }
       
   933     }
       
   934 
       
   935     while (fetchNext())
       
   936         ++i;
       
   937 
       
   938     if (i == QSql::BeforeFirstRow) {
       
   939         setAt(QSql::AfterLastRow);
       
   940         return false;
       
   941     }
       
   942 
       
   943     if (!isForwardOnly())
       
   944         return fetch(i);
       
   945 
       
   946     setAt(i);
       
   947     return true;
       
   948 }
       
   949 
       
   950 
       
   951 QVariant QDB2Result::data(int field)
       
   952 {
       
   953     if (field >= d->recInf.count()) {
       
   954         qWarning("QDB2Result::data: column %d out of range", field);
       
   955         return QVariant();
       
   956     }
       
   957     SQLRETURN r = 0;
       
   958     SQLINTEGER lengthIndicator = 0;
       
   959     bool isNull = false;
       
   960     const QSqlField info = d->recInf.field(field);
       
   961 
       
   962     if (!info.isValid() || field >= d->valueCache.size())
       
   963         return QVariant();
       
   964 
       
   965     if (d->valueCache[field])
       
   966         return *d->valueCache[field];
       
   967 
       
   968 
       
   969     QVariant* v = 0;
       
   970     switch (info.type()) {
       
   971         case QVariant::LongLong:
       
   972             v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull));
       
   973             break;
       
   974         case QVariant::Int:
       
   975             v = new QVariant(qGetIntData(d->hStmt, field, isNull));
       
   976             break;
       
   977         case QVariant::Date: {
       
   978             DATE_STRUCT dbuf;
       
   979             r = SQLGetData(d->hStmt,
       
   980                             field + 1,
       
   981                             SQL_C_DATE,
       
   982                             (SQLPOINTER) &dbuf,
       
   983                             0,
       
   984                             &lengthIndicator);
       
   985             if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
       
   986                 v = new QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
       
   987             } else {
       
   988                 v = new QVariant(QDate());
       
   989                 isNull = true;
       
   990             }
       
   991             break; }
       
   992         case QVariant::Time: {
       
   993             TIME_STRUCT tbuf;
       
   994             r = SQLGetData(d->hStmt,
       
   995                             field + 1,
       
   996                             SQL_C_TIME,
       
   997                             (SQLPOINTER) &tbuf,
       
   998                             0,
       
   999                             &lengthIndicator);
       
  1000             if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
       
  1001                 v = new QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
       
  1002             } else {
       
  1003                 v = new QVariant(QTime());
       
  1004                 isNull = true;
       
  1005             }
       
  1006             break; }
       
  1007         case QVariant::DateTime: {
       
  1008             TIMESTAMP_STRUCT dtbuf;
       
  1009             r = SQLGetData(d->hStmt,
       
  1010                             field + 1,
       
  1011                             SQL_C_TIMESTAMP,
       
  1012                             (SQLPOINTER) &dtbuf,
       
  1013                             0,
       
  1014                             &lengthIndicator);
       
  1015             if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
       
  1016                 v = new QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
       
  1017                                              QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
       
  1018             } else {
       
  1019                 v = new QVariant(QDateTime());
       
  1020                 isNull = true;
       
  1021             }
       
  1022             break; }
       
  1023         case QVariant::ByteArray:
       
  1024             v = new QVariant(qGetBinaryData(d->hStmt, field, lengthIndicator, isNull));
       
  1025             break;
       
  1026         case QVariant::Double:
       
  1027             {
       
  1028             QString value=qGetStringData(d->hStmt, field, info.length() + 1, isNull);
       
  1029             switch(numericalPrecisionPolicy()) {
       
  1030                 case QSql::LowPrecisionInt32:
       
  1031                     v = new QVariant(qGetIntData(d->hStmt, field, isNull));
       
  1032                     break;
       
  1033                 case QSql::LowPrecisionInt64:
       
  1034                     v = new QVariant(qGetBigIntData(d->hStmt, field, isNull));
       
  1035                     break;
       
  1036                 case QSql::LowPrecisionDouble:
       
  1037                     v = new QVariant(qGetDoubleData(d->hStmt, field, isNull));
       
  1038                     break;
       
  1039                 case QSql::HighPrecision:
       
  1040                 default:
       
  1041                     // length + 1 for the comma
       
  1042                     v = new QVariant(qGetStringData(d->hStmt, field, info.length() + 1, isNull));
       
  1043                     break;
       
  1044             }
       
  1045             break;
       
  1046             }
       
  1047         case QVariant::String:
       
  1048         default:
       
  1049             v = new QVariant(qGetStringData(d->hStmt, field, info.length(), isNull));
       
  1050             break;
       
  1051     }
       
  1052     if (isNull)
       
  1053         *v = QVariant(info.type());
       
  1054     d->valueCache[field] = v;
       
  1055     return *v;
       
  1056 }
       
  1057 
       
  1058 bool QDB2Result::isNull(int i)
       
  1059 {
       
  1060     if (i >= d->valueCache.size())
       
  1061         return true;
       
  1062 
       
  1063     if (d->valueCache[i])
       
  1064         return d->valueCache[i]->isNull();
       
  1065     return data(i).isNull();
       
  1066 }
       
  1067 
       
  1068 int QDB2Result::numRowsAffected()
       
  1069 {
       
  1070     SQLINTEGER affectedRowCount = 0;
       
  1071     SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
       
  1072     if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
       
  1073         return affectedRowCount;
       
  1074     else
       
  1075         qSqlWarning(QLatin1String("QDB2Result::numRowsAffected: Unable to count affected rows"), d);
       
  1076     return -1;
       
  1077 }
       
  1078 
       
  1079 int QDB2Result::size()
       
  1080 {
       
  1081     return -1;
       
  1082 }
       
  1083 
       
  1084 QSqlRecord QDB2Result::record() const
       
  1085 {
       
  1086     if (isActive())
       
  1087         return d->recInf;
       
  1088     return QSqlRecord();
       
  1089 }
       
  1090 
       
  1091 bool QDB2Result::nextResult()
       
  1092 {
       
  1093     setActive(false);
       
  1094     setAt(QSql::BeforeFirstRow);
       
  1095     d->recInf.clear();
       
  1096     d->emptyValueCache();
       
  1097     setSelect(false);
       
  1098 
       
  1099     SQLRETURN r = SQLMoreResults(d->hStmt);
       
  1100     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
  1101         if (r != SQL_NO_DATA) {
       
  1102             setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
       
  1103                 "Unable to fetch last"), QSqlError::ConnectionError, d));
       
  1104         }
       
  1105         return false;
       
  1106     }
       
  1107 
       
  1108     SQLSMALLINT fieldCount;
       
  1109     r = SQLNumResultCols(d->hStmt, &fieldCount);
       
  1110     setSelect(fieldCount > 0);
       
  1111     for (int i = 0; i < fieldCount; ++i)
       
  1112         d->recInf.append(qMakeFieldInfo(d, i));
       
  1113 
       
  1114     d->valueCache.resize(fieldCount);
       
  1115     d->valueCache.fill(NULL);
       
  1116     setActive(true);
       
  1117 
       
  1118     return true;
       
  1119 }
       
  1120 
       
  1121 void QDB2Result::virtual_hook(int id, void *data)
       
  1122 {
       
  1123     switch (id) {
       
  1124     case QSqlResult::NextResult:
       
  1125         Q_ASSERT(data);
       
  1126         *static_cast<bool*>(data) = nextResult();
       
  1127         break;
       
  1128     case QSqlResult::DetachFromResultSet:
       
  1129         if (d->hStmt)
       
  1130             SQLCloseCursor(d->hStmt);
       
  1131         break;
       
  1132     default:
       
  1133         QSqlResult::virtual_hook(id, data);
       
  1134     }
       
  1135 }
       
  1136 
       
  1137 /************************************/
       
  1138 
       
  1139 QDB2Driver::QDB2Driver(QObject* parent)
       
  1140     : QSqlDriver(parent)
       
  1141 {
       
  1142     d = new QDB2DriverPrivate;
       
  1143 }
       
  1144 
       
  1145 QDB2Driver::QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent)
       
  1146     : QSqlDriver(parent)
       
  1147 {
       
  1148     d = new QDB2DriverPrivate;
       
  1149     d->hEnv = (SQLHANDLE)env;
       
  1150     d->hDbc = (SQLHANDLE)con;
       
  1151     if (env && con) {
       
  1152         setOpen(true);
       
  1153         setOpenError(false);
       
  1154     }
       
  1155 }
       
  1156 
       
  1157 QDB2Driver::~QDB2Driver()
       
  1158 {
       
  1159     close();
       
  1160     delete d;
       
  1161 }
       
  1162 
       
  1163 bool QDB2Driver::open(const QString& db, const QString& user, const QString& password, const QString& host, int port,
       
  1164                        const QString& connOpts)
       
  1165 {
       
  1166     if (isOpen())
       
  1167       close();
       
  1168     SQLRETURN r;
       
  1169     r = SQLAllocHandle(SQL_HANDLE_ENV,
       
  1170                         SQL_NULL_HANDLE,
       
  1171                         &d->hEnv);
       
  1172     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
  1173         qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate environment"), d);
       
  1174         setOpenError(true);
       
  1175         return false;
       
  1176     }
       
  1177 
       
  1178     r = SQLAllocHandle(SQL_HANDLE_DBC,
       
  1179                         d->hEnv,
       
  1180                         &d->hDbc);
       
  1181     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
  1182         qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate connection"), d);
       
  1183         setOpenError(true);
       
  1184         return false;
       
  1185     }
       
  1186 
       
  1187     QString protocol;
       
  1188     // Set connection attributes
       
  1189     const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
       
  1190     for (int i = 0; i < opts.count(); ++i) {
       
  1191         const QString tmp(opts.at(i));
       
  1192         int idx;
       
  1193         if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) {
       
  1194             qWarning("QDB2Driver::open: Illegal connect option value '%s'",
       
  1195                      tmp.toLocal8Bit().constData());
       
  1196             continue;
       
  1197         }
       
  1198 
       
  1199         const QString opt(tmp.left(idx));
       
  1200         const QString val(tmp.mid(idx + 1).simplified());
       
  1201 
       
  1202         SQLUINTEGER v = 0;
       
  1203         r = SQL_SUCCESS;
       
  1204         if (opt == QLatin1String("SQL_ATTR_ACCESS_MODE")) {
       
  1205             if (val == QLatin1String("SQL_MODE_READ_ONLY")) {
       
  1206                 v = SQL_MODE_READ_ONLY;
       
  1207             } else if (val == QLatin1String("SQL_MODE_READ_WRITE")) {
       
  1208                 v = SQL_MODE_READ_WRITE;
       
  1209             } else {
       
  1210                 qWarning("QDB2Driver::open: Unknown option value '%s'",
       
  1211                          tmp.toLocal8Bit().constData());
       
  1212                 continue;
       
  1213             }
       
  1214             r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) v, 0);
       
  1215         } else if (opt == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) {
       
  1216             v = val.toUInt();
       
  1217             r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) v, 0);
       
  1218         } else if (opt.compare(QLatin1String("PROTOCOL"), Qt::CaseInsensitive) == 0) {
       
  1219                         protocol = tmp;
       
  1220         }
       
  1221         else {
       
  1222             qWarning("QDB2Driver::open: Unknown connection attribute '%s'",
       
  1223                       tmp.toLocal8Bit().constData());
       
  1224         }
       
  1225         if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
       
  1226             qSqlWarning(QString::fromLatin1("QDB2Driver::open: "
       
  1227                            "Unable to set connection attribute '%1'").arg(opt), d);
       
  1228     }
       
  1229 
       
  1230     if (protocol.isEmpty())
       
  1231         protocol = QLatin1String("PROTOCOL=TCPIP");
       
  1232 
       
  1233     if (port < 0 )
       
  1234         port = 50000;
       
  1235 
       
  1236     QString connQStr;
       
  1237     connQStr =  protocol + QLatin1String(";DATABASE=") + db + QLatin1String(";HOSTNAME=") + host
       
  1238         + QLatin1String(";PORT=") + QString::number(port) + QLatin1String(";UID=") + user
       
  1239         + QLatin1String(";PWD=") + password;
       
  1240 
       
  1241 
       
  1242     SQLTCHAR connOut[SQL_MAX_OPTION_STRING_LENGTH];
       
  1243     SQLSMALLINT cb;
       
  1244 
       
  1245     r = SQLDriverConnect(d->hDbc,
       
  1246                           NULL,
       
  1247                           qToTChar(connQStr),
       
  1248                           (SQLSMALLINT) connQStr.length(),
       
  1249                           connOut,
       
  1250                           SQL_MAX_OPTION_STRING_LENGTH,
       
  1251                           &cb,
       
  1252                           SQL_DRIVER_NOPROMPT);
       
  1253     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
  1254         setLastError(qMakeError(tr("Unable to connect"),
       
  1255                                 QSqlError::ConnectionError, d));
       
  1256         setOpenError(true);
       
  1257         return false;
       
  1258     }
       
  1259 
       
  1260     d->user = user;
       
  1261     setOpen(true);
       
  1262     setOpenError(false);
       
  1263     return true;
       
  1264 }
       
  1265 
       
  1266 void QDB2Driver::close()
       
  1267 {
       
  1268     SQLRETURN r;
       
  1269     if (d->hDbc) {
       
  1270         // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect
       
  1271         if (isOpen()) {
       
  1272             r = SQLDisconnect(d->hDbc);
       
  1273             if (r != SQL_SUCCESS)
       
  1274                 qSqlWarning(QLatin1String("QDB2Driver::close: Unable to disconnect datasource"), d);
       
  1275         }
       
  1276         r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc);
       
  1277         if (r != SQL_SUCCESS)
       
  1278             qSqlWarning(QLatin1String("QDB2Driver::close: Unable to free connection handle"), d);
       
  1279         d->hDbc = 0;
       
  1280     }
       
  1281 
       
  1282     if (d->hEnv) {
       
  1283         r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv);
       
  1284         if (r != SQL_SUCCESS)
       
  1285             qSqlWarning(QLatin1String("QDB2Driver::close: Unable to free environment handle"), d);
       
  1286         d->hEnv = 0;
       
  1287     }
       
  1288     setOpen(false);
       
  1289     setOpenError(false);
       
  1290 }
       
  1291 
       
  1292 QSqlResult *QDB2Driver::createResult() const
       
  1293 {
       
  1294     return new QDB2Result(this, d);
       
  1295 }
       
  1296 
       
  1297 QSqlRecord QDB2Driver::record(const QString& tableName) const
       
  1298 {
       
  1299     QSqlRecord fil;
       
  1300     if (!isOpen())
       
  1301         return fil;
       
  1302 
       
  1303     SQLHANDLE hStmt;
       
  1304     QString catalog, schema, table;
       
  1305     qSplitTableQualifier(tableName, &catalog, &schema, &table);
       
  1306     if (schema.isEmpty())
       
  1307         schema = d->user;
       
  1308 
       
  1309     if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
       
  1310         catalog = stripDelimiters(catalog, QSqlDriver::TableName);
       
  1311     else
       
  1312         catalog = catalog.toUpper();
       
  1313 
       
  1314     if (isIdentifierEscaped(schema, QSqlDriver::TableName))
       
  1315         schema = stripDelimiters(schema, QSqlDriver::TableName);
       
  1316     else
       
  1317         schema = schema.toUpper();
       
  1318 
       
  1319     if (isIdentifierEscaped(table, QSqlDriver::TableName))
       
  1320         table = stripDelimiters(table, QSqlDriver::TableName);
       
  1321     else
       
  1322         table = table.toUpper();
       
  1323 
       
  1324     SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
       
  1325                                   d->hDbc,
       
  1326                                   &hStmt);
       
  1327     if (r != SQL_SUCCESS) {
       
  1328         qSqlWarning(QLatin1String("QDB2Driver::record: Unable to allocate handle"), d);
       
  1329         return fil;
       
  1330     }
       
  1331 
       
  1332     r = SQLSetStmtAttr(hStmt,
       
  1333                         SQL_ATTR_CURSOR_TYPE,
       
  1334                         (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY,
       
  1335                         SQL_IS_UINTEGER);
       
  1336 
       
  1337 
       
  1338     //Aside: szSchemaName and szTableName parameters of SQLColumns
       
  1339     //are case sensitive search patterns, so no escaping is used.
       
  1340     r =  SQLColumns(hStmt,
       
  1341                      NULL,
       
  1342                      0,
       
  1343                      qToTChar(schema),
       
  1344                      schema.length(),
       
  1345                      qToTChar(table),
       
  1346                      table.length(),
       
  1347                      NULL,
       
  1348                      0);
       
  1349 
       
  1350     if (r != SQL_SUCCESS)
       
  1351         qSqlWarning(QLatin1String("QDB2Driver::record: Unable to execute column list"), d);
       
  1352     r = SQLFetchScroll(hStmt,
       
  1353                         SQL_FETCH_NEXT,
       
  1354                         0);
       
  1355     while (r == SQL_SUCCESS) {
       
  1356         fil.append(qMakeFieldInfo(hStmt));
       
  1357         r = SQLFetchScroll(hStmt,
       
  1358                             SQL_FETCH_NEXT,
       
  1359                             0);
       
  1360     }
       
  1361 
       
  1362     r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
       
  1363     if (r != SQL_SUCCESS)
       
  1364         qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
       
  1365                     + QString::number(r), d);
       
  1366 
       
  1367     return fil;
       
  1368 }
       
  1369 
       
  1370 QStringList QDB2Driver::tables(QSql::TableType type) const
       
  1371 {
       
  1372     QStringList tl;
       
  1373     if (!isOpen())
       
  1374         return tl;
       
  1375 
       
  1376     SQLHANDLE hStmt;
       
  1377 
       
  1378     SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
       
  1379                                   d->hDbc,
       
  1380                                   &hStmt);
       
  1381     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
       
  1382         qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to allocate handle"), d);
       
  1383         return tl;
       
  1384     }
       
  1385     r = SQLSetStmtAttr(hStmt,
       
  1386                         SQL_ATTR_CURSOR_TYPE,
       
  1387                         (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
       
  1388                         SQL_IS_UINTEGER);
       
  1389 
       
  1390     QString tableType;
       
  1391     if (type & QSql::Tables)
       
  1392         tableType += QLatin1String("TABLE,");
       
  1393     if (type & QSql::Views)
       
  1394         tableType += QLatin1String("VIEW,");
       
  1395     if (type & QSql::SystemTables)
       
  1396         tableType += QLatin1String("SYSTEM TABLE,");
       
  1397     if (tableType.isEmpty())
       
  1398         return tl;
       
  1399     tableType.chop(1);
       
  1400 
       
  1401     r = SQLTables(hStmt,
       
  1402                    NULL,
       
  1403                    0,
       
  1404                    NULL,
       
  1405                    0,
       
  1406                    NULL,
       
  1407                    0,
       
  1408                    qToTChar(tableType),
       
  1409                    tableType.length());
       
  1410 
       
  1411     if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
       
  1412         qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to execute table list"), d);
       
  1413     r = SQLFetchScroll(hStmt,
       
  1414                         SQL_FETCH_NEXT,
       
  1415                         0);
       
  1416     while (r == SQL_SUCCESS) {
       
  1417         bool isNull;
       
  1418         QString fieldVal = qGetStringData(hStmt, 2, -1, isNull);
       
  1419         QString userVal = qGetStringData(hStmt, 1, -1, isNull);
       
  1420         QString user = d->user;
       
  1421         if ( isIdentifierEscaped(user, QSqlDriver::TableName))
       
  1422             user = stripDelimiters(user, QSqlDriver::TableName);
       
  1423         else
       
  1424             user = user.toUpper();
       
  1425 
       
  1426         if (userVal != user)
       
  1427             fieldVal = userVal + QLatin1Char('.') + fieldVal;
       
  1428         tl.append(fieldVal);
       
  1429         r = SQLFetchScroll(hStmt,
       
  1430                             SQL_FETCH_NEXT,
       
  1431                             0);
       
  1432     }
       
  1433 
       
  1434     r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
       
  1435     if (r != SQL_SUCCESS)
       
  1436         qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to free statement handle ")
       
  1437                     + QString::number(r), d);
       
  1438     return tl;
       
  1439 }
       
  1440 
       
  1441 QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const
       
  1442 {
       
  1443     QSqlIndex index(tablename);
       
  1444     if (!isOpen())
       
  1445         return index;
       
  1446     QSqlRecord rec = record(tablename);
       
  1447 
       
  1448     SQLHANDLE hStmt;
       
  1449     SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
       
  1450                                   d->hDbc,
       
  1451                                   &hStmt);
       
  1452     if (r != SQL_SUCCESS) {
       
  1453         qSqlWarning(QLatin1String("QDB2Driver::primaryIndex: Unable to list primary key"), d);
       
  1454         return index;
       
  1455     }
       
  1456     QString catalog, schema, table;
       
  1457     qSplitTableQualifier(tablename, &catalog, &schema, &table);
       
  1458 
       
  1459     if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
       
  1460         catalog = stripDelimiters(catalog, QSqlDriver::TableName);
       
  1461     else
       
  1462         catalog = catalog.toUpper();
       
  1463 
       
  1464     if (isIdentifierEscaped(schema, QSqlDriver::TableName))
       
  1465         schema = stripDelimiters(schema, QSqlDriver::TableName);
       
  1466     else
       
  1467         schema = schema.toUpper();
       
  1468 
       
  1469     if (isIdentifierEscaped(table, QSqlDriver::TableName))
       
  1470         table = stripDelimiters(table, QSqlDriver::TableName);
       
  1471     else
       
  1472         table = table.toUpper();
       
  1473 
       
  1474     r = SQLSetStmtAttr(hStmt,
       
  1475                         SQL_ATTR_CURSOR_TYPE,
       
  1476                         (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
       
  1477                         SQL_IS_UINTEGER);
       
  1478 
       
  1479     r = SQLPrimaryKeys(hStmt,
       
  1480                         NULL,
       
  1481                         0,
       
  1482                         qToTChar(schema),
       
  1483                         schema.length(),
       
  1484                         qToTChar(table),
       
  1485                         table.length());
       
  1486     r = SQLFetchScroll(hStmt,
       
  1487                         SQL_FETCH_NEXT,
       
  1488                         0);
       
  1489 
       
  1490     bool isNull;
       
  1491     QString cName, idxName;
       
  1492     // Store all fields in a StringList because the driver can't detail fields in this FETCH loop
       
  1493     while (r == SQL_SUCCESS) {
       
  1494         cName = qGetStringData(hStmt, 3, -1, isNull); // column name
       
  1495         idxName = qGetStringData(hStmt, 5, -1, isNull); // pk index name
       
  1496         index.append(rec.field(cName));
       
  1497         index.setName(idxName);
       
  1498         r = SQLFetchScroll(hStmt,
       
  1499                             SQL_FETCH_NEXT,
       
  1500                             0);
       
  1501     }
       
  1502     r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
       
  1503     if (r!= SQL_SUCCESS)
       
  1504         qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
       
  1505                     + QString::number(r), d);
       
  1506     return index;
       
  1507 }
       
  1508 
       
  1509 bool QDB2Driver::hasFeature(DriverFeature f) const
       
  1510 {
       
  1511     switch (f) {
       
  1512         case QuerySize:
       
  1513         case NamedPlaceholders:
       
  1514         case BatchOperations:
       
  1515         case LastInsertId:
       
  1516         case SimpleLocking:
       
  1517         case EventNotifications:
       
  1518             return false;
       
  1519         case BLOB:
       
  1520         case Transactions:
       
  1521         case MultipleResultSets:
       
  1522         case PreparedQueries:
       
  1523         case PositionalPlaceholders:
       
  1524         case LowPrecisionNumbers:
       
  1525         case FinishQuery:
       
  1526             return true;
       
  1527         case Unicode:
       
  1528             return true;
       
  1529     }
       
  1530     return false;
       
  1531 }
       
  1532 
       
  1533 bool QDB2Driver::beginTransaction()
       
  1534 {
       
  1535     if (!isOpen()) {
       
  1536         qWarning("QDB2Driver::beginTransaction: Database not open");
       
  1537         return false;
       
  1538     }
       
  1539     return setAutoCommit(false);
       
  1540 }
       
  1541 
       
  1542 bool QDB2Driver::commitTransaction()
       
  1543 {
       
  1544     if (!isOpen()) {
       
  1545         qWarning("QDB2Driver::commitTransaction: Database not open");
       
  1546         return false;
       
  1547     }
       
  1548     SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
       
  1549                               d->hDbc,
       
  1550                               SQL_COMMIT);
       
  1551     if (r != SQL_SUCCESS) {
       
  1552         setLastError(qMakeError(tr("Unable to commit transaction"),
       
  1553                      QSqlError::TransactionError, d));
       
  1554         return false;
       
  1555     }
       
  1556     return setAutoCommit(true);
       
  1557 }
       
  1558 
       
  1559 bool QDB2Driver::rollbackTransaction()
       
  1560 {
       
  1561     if (!isOpen()) {
       
  1562         qWarning("QDB2Driver::rollbackTransaction: Database not open");
       
  1563         return false;
       
  1564     }
       
  1565     SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
       
  1566                               d->hDbc,
       
  1567                               SQL_ROLLBACK);
       
  1568     if (r != SQL_SUCCESS) {
       
  1569         setLastError(qMakeError(tr("Unable to rollback transaction"),
       
  1570                                 QSqlError::TransactionError, d));
       
  1571         return false;
       
  1572     }
       
  1573     return setAutoCommit(true);
       
  1574 }
       
  1575 
       
  1576 bool QDB2Driver::setAutoCommit(bool autoCommit)
       
  1577 {
       
  1578     SQLUINTEGER ac = autoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
       
  1579     SQLRETURN r  = SQLSetConnectAttr(d->hDbc,
       
  1580                                       SQL_ATTR_AUTOCOMMIT,
       
  1581                                       (SQLPOINTER)ac,
       
  1582                                       sizeof(ac));
       
  1583     if (r != SQL_SUCCESS) {
       
  1584         setLastError(qMakeError(tr("Unable to set autocommit"),
       
  1585                                 QSqlError::TransactionError, d));
       
  1586         return false;
       
  1587     }
       
  1588     return true;
       
  1589 }
       
  1590 
       
  1591 QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const
       
  1592 {
       
  1593     if (field.isNull())
       
  1594         return QLatin1String("NULL");
       
  1595 
       
  1596     switch (field.type()) {
       
  1597         case QVariant::DateTime: {
       
  1598             // Use an escape sequence for the datetime fields
       
  1599             if (field.value().toDateTime().isValid()) {
       
  1600                 QDate dt = field.value().toDateTime().date();
       
  1601                 QTime tm = field.value().toDateTime().time();
       
  1602                 // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
       
  1603                 return QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-')
       
  1604                        + QString::number(dt.month()) + QLatin1Char('-')
       
  1605                        + QString::number(dt.day()) + QLatin1Char('-')
       
  1606                        + QString::number(tm.hour()) + QLatin1Char('.')
       
  1607                        + QString::number(tm.minute()).rightJustified(2, QLatin1Char('0'), true)
       
  1608                        + QLatin1Char('.')
       
  1609                        + QString::number(tm.second()).rightJustified(2, QLatin1Char('0'), true)
       
  1610                        + QLatin1Char('.')
       
  1611                        + QString::number(tm.msec() * 1000).rightJustified(6, QLatin1Char('0'), true)
       
  1612                        + QLatin1Char('\'');
       
  1613                 } else {
       
  1614                     return QLatin1String("NULL");
       
  1615                 }
       
  1616         }
       
  1617         case QVariant::ByteArray: {
       
  1618             QByteArray ba = field.value().toByteArray();
       
  1619             QString res = QString::fromLatin1("BLOB(X'");
       
  1620             static const char hexchars[] = "0123456789abcdef";
       
  1621             for (int i = 0; i < ba.size(); ++i) {
       
  1622                 uchar s = (uchar) ba[i];
       
  1623                 res += QLatin1Char(hexchars[s >> 4]);
       
  1624                 res += QLatin1Char(hexchars[s & 0x0f]);
       
  1625             }
       
  1626             res += QLatin1String("')");
       
  1627             return res;
       
  1628         }
       
  1629         default:
       
  1630             return QSqlDriver::formatValue(field, trimStrings);
       
  1631     }
       
  1632 }
       
  1633 
       
  1634 QVariant QDB2Driver::handle() const
       
  1635 {
       
  1636     return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc);
       
  1637 }
       
  1638 
       
  1639 QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType) const
       
  1640 {
       
  1641     QString res = identifier;
       
  1642     if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
       
  1643         res.replace(QLatin1Char('"'), QLatin1String("\"\""));
       
  1644         res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
       
  1645         res.replace(QLatin1Char('.'), QLatin1String("\".\""));
       
  1646     }
       
  1647     return res;
       
  1648 }
       
  1649 
       
  1650 QT_END_NAMESPACE