Fix error when inserting to tables with datetime fields with QODBC
authorThiago A. Correa <thiago.correa@gmail.com>
Fri, 13 Jul 2012 17:28:18 +0000 (14:28 -0300)
committerQt by Nokia <qt-info@nokia.com>
Mon, 16 Jul 2012 21:48:10 +0000 (23:48 +0200)
SQL Server 10 introduced stricter rules for TIMESTAMP validation,
making it necessary to specify the decimal digits.
Other databases might do the same as well, so this patch introduces
a check for the TIMESTAMP column size and adjusts the decimal digits
parameter as needed.

Task-number: QTBUG-2192

Change-Id: If6d798c6c928ebda75bc474e49a07fbbfbe5816c
Reviewed-by: Mark Brand <mabrand@mabrand.nl>
src/sql/drivers/odbc/qsql_odbc.cpp
tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp

index 05f740c..f874374 100644 (file)
@@ -54,6 +54,7 @@
 #include <qstringlist.h>
 #include <qvarlengtharray.h>
 #include <qvector.h>
+#include <qmath.h>
 #include <QDebug>
 #include <QSqlQuery>
 
@@ -115,7 +116,7 @@ class QODBCDriverPrivate
 public:
     enum DefaultCase{Lower, Mixed, Upper, Sensitive};
     QODBCDriverPrivate()
-    : hEnv(0), hDbc(0), unicode(false), useSchema(false), disconnectCount(0), isMySqlServer(false),
+    : hEnv(0), hDbc(0), unicode(false), useSchema(false), disconnectCount(0), datetime_precision(19), isMySqlServer(false),
            isMSSqlServer(false), isFreeTDSDriver(false), hasSQLFetchScroll(true),
            hasMultiResultSets(false), isQuoteInitialized(false), quote(QLatin1Char('"'))
     {
@@ -127,6 +128,7 @@ public:
     bool unicode;
     bool useSchema;
     int disconnectCount;
+    int datetime_precision;
     bool isMySqlServer;
     bool isMSSqlServer;
     bool isFreeTDSDriver;
@@ -139,6 +141,7 @@ public:
     void checkHasSQLFetchScroll();
     void checkHasMultiResults();
     void checkSchemaUsage();
+    void checkDateTimePrecision();
     bool setConnectionOptions(const QString& connOpts);
     void splitTableQualifier(const QString &qualifier, QString &catalog,
                              QString &schema, QString &table);
@@ -1395,14 +1398,25 @@ bool QODBCResult::exec()
                 dt->hour = qdt.time().hour();
                 dt->minute = qdt.time().minute();
                 dt->second = qdt.time().second();
-                dt->fraction = qdt.time().msec() * 1000000;
+
+                int precision = d->driverPrivate->datetime_precision - 20; // (20 includes a separating period)
+                if (precision <= 0) {
+                    dt->fraction = 0;
+                } else {
+                    dt->fraction = qdt.time().msec() * 1000000;
+
+                    // (How many leading digits do we want to keep?  With SQL Server 2005, this should be 3: 123000000)
+                    int keep = (int)qPow(10.0, 9 - qMin(9, precision));
+                    dt->fraction /= keep * keep;
+                }
+
                 r = SQLBindParameter(d->hStmt,
                                       i + 1,
                                       qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
                                       SQL_C_TIMESTAMP,
                                       SQL_TIMESTAMP,
-                                      19,
-                                      0,
+                                      d->driverPrivate->datetime_precision,
+                                      precision,
                                       (void *) dt,
                                       0,
                                       *ind == SQL_NULL_DATA ? ind : NULL);
@@ -1887,6 +1901,7 @@ bool QODBCDriver::open(const QString & db,
     d->checkSqlServer();
     d->checkHasSQLFetchScroll();
     d->checkHasMultiResults();
+    d->checkDateTimePrecision();
     setOpen(true);
     setOpenError(false);
     if(d->isMSSqlServer) {
@@ -2122,6 +2137,29 @@ void QODBCDriverPrivate::checkHasMultiResults()
 #endif
 }
 
+void QODBCDriverPrivate::checkDateTimePrecision()
+{
+    SQLINTEGER columnSize;
+    SQLHANDLE hStmt;
+
+    SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
+    if (r != SQL_SUCCESS) {
+        return;
+    }
+
+    r = SQLGetTypeInfo(hStmt, SQL_TIMESTAMP);
+    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+        r = SQLFetch(hStmt);
+        if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO )
+        {
+            if (SQLGetData(hStmt, 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS) {
+                datetime_precision = (int)columnSize;
+            }
+        }
+    }
+    SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+}
+
 QSqlResult *QODBCDriver::createResult() const
 {
     return new QODBCResult(this, d);
index b5fb36a..d39934d 100644 (file)
@@ -219,6 +219,8 @@ private slots:
     void QTBUG_16967(); //clean close
     void QTBUG_23895_data() { generic_data("QSQLITE"); }
     void QTBUG_23895(); //sqlite boolean type
+    void QTBUG_2192_data() { generic_data(); }
+    void QTBUG_2192();
 
     void sqlite_constraint_data() { generic_data("QSQLITE"); }
     void sqlite_constraint();
@@ -341,7 +343,8 @@ void tst_QSqlQuery::dropTestTables( QSqlDatabase db )
                << qTableName( "task_250026", __FILE__ )
                << qTableName( "task_234422", __FILE__ )
                << qTableName("test141895", __FILE__)
-               << qTableName("qtest_oraOCINumber", __FILE__);
+               << qTableName("qtest_oraOCINumber", __FILE__)
+               << qTableName( "bug2192", __FILE__);
 
     if ( db.driverName().startsWith("QPSQL") )
         tablenames << qTableName("task_233829", __FILE__);
@@ -3357,6 +3360,29 @@ void tst_QSqlQuery::QTBUG_23895()
     QVERIFY(!q.next());
 }
 
+void tst_QSqlQuery::QTBUG_2192()
+{
+    QFETCH( QString, dbName );
+    QSqlDatabase db = QSqlDatabase::database( dbName );
+    CHECK_DATABASE( db );
+    {
+        const QString tableName(qTableName("bug2192", __FILE__));
+        tst_Databases::safeDropTable( db, tableName );
+
+        QSqlQuery q(db);
+        QVERIFY_SQL(q, exec("CREATE TABLE " + tableName + " (dt DATETIME)"));
+
+        QVERIFY_SQL(q, prepare("INSERT INTO " + tableName + " (dt) VALUES (?)"));
+        q.bindValue(0, QVariant(QDateTime(QDate(2012, 7, 4), QTime(23, 59, 59, 999))));
+        QVERIFY_SQL(q, exec());
+
+        // Check if value was stored with at least second precision.
+        QVERIFY_SQL(q, exec("SELECT dt FROM " + tableName));
+        QVERIFY_SQL(q, next());
+        QVERIFY(q.value(0).toDateTime().msecsTo(QDateTime(QDate(2012, 7, 4), QTime(23, 59, 59, 999))) < 1000 );
+    }
+}
+
 void tst_QSqlQuery::oraOCINumber()
 {
     QFETCH( QString, dbName );