Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. MSSQL with unixODBC and FreeTDS
Forum Updated to NodeBB v4.3 + New Features

MSSQL with unixODBC and FreeTDS

Scheduled Pinned Locked Moved Unsolved General and Desktop
32 Posts 5 Posters 4.8k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • hskoglundH Offline
    hskoglundH Offline
    hskoglund
    wrote on last edited by
    #8

    Hmm, running out of ideas :-(

    Qt's ODBC plugin hasn't been updated for a long time, a couple of years ago I had problem with Unicode traversal for Visual Foxpro databases, so I added my own ODBC code at the end of my own lib: https://gitlab.com/tungware/lib/-/blob/master/TWDB/TWDB.cpp

    If you have other SQL statements that runs fine through Qt's ODBC plugin, and only that Restore statement fails, maybe you could detect its success by a SELECT something to see if the Restore ran to a ok success or not.

    Jonas KvingeJ 1 Reply Last reply
    1
    • hskoglundH hskoglund

      Hmm, running out of ideas :-(

      Qt's ODBC plugin hasn't been updated for a long time, a couple of years ago I had problem with Unicode traversal for Visual Foxpro databases, so I added my own ODBC code at the end of my own lib: https://gitlab.com/tungware/lib/-/blob/master/TWDB/TWDB.cpp

      If you have other SQL statements that runs fine through Qt's ODBC plugin, and only that Restore statement fails, maybe you could detect its success by a SELECT something to see if the Restore ran to a ok success or not.

      Jonas KvingeJ Offline
      Jonas KvingeJ Offline
      Jonas Kvinge
      wrote on last edited by
      #9

      @hskoglund
      I tried to do another query like:

      select * from sys.databases
      

      It worked fine and returned all databases.

      I also tested (now) on a MSSQL 2008 R2 instance with the same result using a backup file produced on the same instance.

      I haven't tried to do other BACKUP/RESTORE related queries tough, but I suspect the same thing would happen.

      1 Reply Last reply
      0
      • hskoglundH Offline
        hskoglundH Offline
        hskoglund
        wrote on last edited by
        #10

        You could submit a Qt bug report.
        And if you want to very helpful, include some tcpdump outputs, to show the TDS stream over port 1433 and how Qt's ODBC plugins fails to interpret it.

        On the other hand, if your other SQL statements work, you could try to live with this "feature" and work around it, by doing a 2nd query for some props/etc. that a successful Restore would show.

        Jonas KvingeJ 1 Reply Last reply
        2
        • hskoglundH hskoglund

          You could submit a Qt bug report.
          And if you want to very helpful, include some tcpdump outputs, to show the TDS stream over port 1433 and how Qt's ODBC plugins fails to interpret it.

          On the other hand, if your other SQL statements work, you could try to live with this "feature" and work around it, by doing a 2nd query for some props/etc. that a successful Restore would show.

          Jonas KvingeJ Offline
          Jonas KvingeJ Offline
          Jonas Kvinge
          wrote on last edited by
          #11

          @hskoglund
          RESTORE HEADERONLY lists all databases in the backup file and what position they are in.
          I need that info before starting the restore. I don't think I can get that any other way.

          JonBJ 1 Reply Last reply
          0
          • Christian EhrlicherC Offline
            Christian EhrlicherC Offline
            Christian Ehrlicher
            Lifetime Qt Champion
            wrote on last edited by
            #12

            Before writing a bug report - does the odbc driver really supports what you want? I would guess no.

            Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
            Visit the Qt Academy at https://academy.qt.io/catalog

            1 Reply Last reply
            0
            • hskoglundH Offline
              hskoglundH Offline
              hskoglund
              wrote on last edited by
              #13

              Also it could be that Qt's ODBC plugin is ok but the layer below isn't, I mean the unixODBC/FreeTDS combo. I.e. when you instead use Qt with Microsoft's ODBC Driver 17 for SQL Server proprietary driver, it works.

              Maybe openSUSE's versions of unixODBC/FreeTDS are not the latest/greatest, perhaps if you downloaded their source and built them yourself.

              1 Reply Last reply
              0
              • Jonas KvingeJ Jonas Kvinge

                @hskoglund
                RESTORE HEADERONLY lists all databases in the backup file and what position they are in.
                I need that info before starting the restore. I don't think I can get that any other way.

                JonBJ Offline
                JonBJ Offline
                JonB
                wrote on last edited by JonB
                #14

                @Jonas-Kvinge said in MSSQL with unixODBC and FreeTDS:

                RESTORE HEADERONLY lists all databases in the backup file and what position they are in.
                I need that info before starting the restore. I don't think I can get that any other way.

                I have previously used RESTORE HEADERONLY from a C# project. If, for whatever reason, it is not working fro you from ODBC, would you like a workaround? It's a bit long! And unfortunately cannot avoid that you must know the format of the header written. But I'll paste (an extract from it) anyway, in a stored procedure I have:

                	  -- see https://msdn.microsoft.com/en-us/library/ms178536.aspx for table definition
                	  CREATE TABLE #backup_header
                	  (
                	  [BackupName] nvarchar(128),
                	  [BackupDescription] nvarchar(255),
                	  [BackupType] smallint,
                	  [ExpirationDate] datetime,
                	  [Compressed] bit,
                	  [Position] smallint,
                	  [DeviceType] tinyint,
                	  [UserName] nvarchar(128),
                	  [ServerName] nvarchar(128),
                	  [DatabaseName] nvarchar(128),
                	  [DatabaseVersion] int,
                	  [DatabaseCreationDate] datetime,
                	  [BackupSize] numeric(20, 0),
                	  [FirstLSN] numeric(25, 0),
                	  [LastLSN] numeric(25, 0),
                	  [CheckpointLSN] numeric(25, 0),
                	  [DatabaseBackupLSN] numeric(25, 0),
                	  [BackupStartDate] datetime,
                	  [BackupFinishDate] datetime,
                	  [SortOrder] smallint,
                	  [CodePage] smallint,
                	  [UnicodeLocaleId] int,
                	  [UnicodeComparisonStyle] int,
                	  [CompatibilityLevel] tinyint,
                	  [SoftwareVendorId] int,
                	  [SoftwareVersionMajor] int,
                	  [SoftwareVersionMinor] int,
                	  [SoftwareVersionBuild] int,
                	  [MachineName] nvarchar(128),
                	  [Flags] int,
                	  [BindingID] uniqueidentifier,
                	  [RecoveryForkID] uniqueidentifier,
                	  [Collation] nvarchar(128),
                	  [FamilyGUID] uniqueidentifier,
                	  [HasBulkLoggedData] bit,
                	  [IsSnapshot] bit,
                	  [IsReadOnly] bit,
                	  [IsSingleUser] bit,
                	  [HasBackupChecksums] bit,
                	  [IsDamaged] bit,
                	  [BeginsLogChain] bit,
                	  [HasIncompleteMetaData] bit,
                	  [IsForceOffline] bit,
                	  [IsCopyOnly] bit,
                	  [FirstRecoveryForkID] uniqueidentifier,
                	  [ForkPointLSN] numeric(25, 0) NULL,
                	  [RecoveryModel] nvarchar(60),
                	  [DifferentialBaseLSN] numeric(25, 0) NULL,
                	  [DifferentialBaseGUID] uniqueidentifier,
                	  [BackupTypeDescription] nvarchar(60),
                	  [BackupSetGUID] uniqueidentifier NULL
                	  )
                	  IF @sql_ver_major >= 10	-- SQL 2008
                	  BEGIN
                	    ALTER TABLE #backup_header
                	      ADD [CompressedBackupSize] bigint NULL
                	  END
                	  IF @sql_ver_major >= 11	-- SQL 2012
                	  BEGIN
                	    ALTER TABLE #backup_header
                	      ADD [Containment] tinyint
                	  END
                	  IF @sql_ver_major > 12 OR	-- SQL 2014 SP1
                	       (@sql_ver_major = 12 AND
                	         (@sql_ver_minor > 0 OR
                	           (@sql_ver_minor = 0 AND
                	             (@sql_ver_build >= 4100)
                	           )
                	         )
                	       )
                	  BEGIN
                	    ALTER TABLE #backup_header
                	      ADD KeyAlgorithm nvarchar(32) NULL, 
                		  EncryptorThumbprint varbinary(20) NULL, 
                		  EncryptorType nvarchar(32) NULL
                	  END
                	  -- note: if FILE = <backup_set_file_number> not specified, default for RESTORE HEADERONLY is all backup sets in the media set are processed
                	  SET @command = N'RESTORE HEADERONLY FROM ' + @device + N' = ''' + @device_file + N''''
                	  INSERT INTO #backup_header
                	    EXEC (@command)
                	  -- set @file_number to greatest [Position] in backup sets
                	  -- do not forget this will return NULL if no backup sets!
                	  SELECT @file_number = MAX([Position]) FROM #backup_header
                
                	  DROP TABLE #backup_header
                

                The point here is that I put the output from RESTORE HEADERONLY into a temporary table. Your stored procedure could then e.g. finish with a SELECT from it, which should return a resultset which you are able to read normally. You might feel like adapting to your use case, if things aren't working for you otherwise!

                1 Reply Last reply
                2
                • Jonas KvingeJ Offline
                  Jonas KvingeJ Offline
                  Jonas Kvinge
                  wrote on last edited by
                  #15

                  I traced the query result from restore headeronly with wireshark and it's t here.

                  1 Reply Last reply
                  0
                  • hskoglundH Offline
                    hskoglundH Offline
                    hskoglund
                    wrote on last edited by
                    #16

                    Yeah, makes sense, since you mentioned: "The same SQL query works fine using tsql (freeTDS diagnostic tool)." which presumably utilizes the TDS same stream as Qt.

                    hskoglundH 1 Reply Last reply
                    0
                    • hskoglundH hskoglund

                      Yeah, makes sense, since you mentioned: "The same SQL query works fine using tsql (freeTDS diagnostic tool)." which presumably utilizes the TDS same stream as Qt.

                      hskoglundH Offline
                      hskoglundH Offline
                      hskoglund
                      wrote on last edited by
                      #17

                      What else to do on a Saturday evening? Found an old VM with MSSQLServer 2008 and the Northwind db on it, made a backupfile of it, then wrote a small QT console app that just either does:

                      "select * from sys.databases"
                      or "restore headeronly from disk = 'C:/Northwind.bak'"

                      First I tested the Qt app on Windows, both statements worked (as expected). Then I launched a fresh Ubuntu 19.10 VM, installed Qt and did:
                      sudo apt-get install unixodbc unixodbc-dev freetds-dev tdsodbc

                      Run the same program, the "select * ..." worked (also as expected) but the "restore ..." failed the same way (the query returns ok, but next() fails).

                      And of course, if I tested with osql, first I made a 1-line text file, containing one of the 2 SQL commands above, and then tried:

                      osql -S dsn1 -U sa -P adminsa < commandfile
                      

                      (dsn1 is defined in my .freetds.conf and .odbc.ini in my ~)
                      And both SQL commands worked. Which means I reproduced your openSUSE experience on Ubuntu 19.10 and Qt 5.14. (Also I could see the traffic on port 1433 using tcpdump)

                      This pretty much points the finger on Qt's ODBC plugin libqsqlodbc.so :-(

                      1 Reply Last reply
                      0
                      • Christian EhrlicherC Offline
                        Christian EhrlicherC Offline
                        Christian Ehrlicher
                        Lifetime Qt Champion
                        wrote on last edited by
                        #18

                        @hskoglund said in MSSQL with unixODBC and FreeTDS:

                        This pretty much points the finger on Qt's ODBC plugin libqsqlodbc.so :-(

                        Why? You're using the same one on Windows, or?

                        Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                        Visit the Qt Academy at https://academy.qt.io/catalog

                        hskoglundH 1 Reply Last reply
                        0
                        • Christian EhrlicherC Christian Ehrlicher

                          @hskoglund said in MSSQL with unixODBC and FreeTDS:

                          This pretty much points the finger on Qt's ODBC plugin libqsqlodbc.so :-(

                          Why? You're using the same one on Windows, or?

                          hskoglundH Offline
                          hskoglundH Offline
                          hskoglund
                          wrote on last edited by
                          #19

                          @Christian-Ehrlicher Yes, it's the same Qt .cpp code for Windows and Linux, so I'm thinking it's something wrong during the handoff of TDS traffic between Qt and the underlying ODBC driver, i.e. on Windows it's a .dll from Microsoft (where it works) and on Ubuntu it's /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so.

                          1 Reply Last reply
                          0
                          • hskoglundH Offline
                            hskoglundH Offline
                            hskoglund
                            wrote on last edited by hskoglund
                            #20

                            Continuing with "What to do on a Saturday evening...", found the culprit and I've built a working libqsqlodbc.so. It is indeed some kind of handoff problem, I could see all the 52 field descriptors were being returned ok (yes "restore headeronly..." returns 52 columns!) but when time come to the fetch, it failed in QODBCResult::fetchFirst() on Ubuntu, but not on WIndows.

                            Here's the culprit/patched part of qsql_odbc.cpp:

                            ...
                            bool QODBCResult::fetchFirst()
                            {
                                Q_D(QODBCResult);
                                if (isForwardOnly() && at() != QSql::BeforeFirstRow)
                                    return false;
                                SQLRETURN r;
                                d->clearValues();
                                if (isForwardOnly()) {
                                    return fetchNext();
                                }
                            // line 1133 qsql_odbc.cpp: following 3 lines are replaced with a SQLFetch
                            //    r = SQLFetchScroll(d->hStmt,
                            //                       SQL_FETCH_FIRST,
                            //                       0);
                                r = SQLFetch(d->hStmt);
                                if (r != SQL_SUCCESS) {
                                    if (r != SQL_NO_DATA)
                                        setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
                                            "Unable to fetch first"), QSqlError::ConnectionError, d));
                                    return false;
                                }
                                setAt(0);
                                return true;
                            }
                            ... 
                            

                            SQLFetchScroll() is an ODBC 3.0-flavored function and maybe it's too advanced for FreeTDS, so on a lark I replaced it with the legacy ODBC 2.0 SQLFetch(), voila!

                            But, as @Christian-Ehrlicher says above, this is not really a bug in Qt, rather FreeTDS/unixODBC stumbling with ODBC 2.0/3.0 differences or something. So consider it a workaround. So feel free to debug more/deeper and submit a bugreport :-)

                            P.S. If you want to build it yourself, it's pretty easy, start MaintenanceTool and select Sources. Then open
                            ~/Qt/5.14.1/Src/qtbase/src/plugins/sqldrivers/sqldrivers.pro
                            from Qt Creator, edit odbc/qsql_odbc.cpp as per above, and then just rebuild the project. A new directory will appear in the directory above, i.e.
                            ~/Qt/5.14.1/Src/qtbase/src/plugins/build-sqldrivers-Desktop_Qt_5_14_1_GCC_64bit-Release and in that directory you'll find the new, working libqsqlodbc.so in ../plugins/sqldrivers

                            1 Reply Last reply
                            3
                            • Christian EhrlicherC Offline
                              Christian EhrlicherC Offline
                              Christian Ehrlicher
                              Lifetime Qt Champion
                              wrote on last edited by
                              #21

                              @hskoglund said in MSSQL with unixODBC and FreeTDS:

                              So consider it a workaround. So feel free to debug more/deeper and submit a bugreport

                              Why? We don't support ODBC2 anymore.

                              Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                              Visit the Qt Academy at https://academy.qt.io/catalog

                              1 Reply Last reply
                              0
                              • hskoglundH Offline
                                hskoglundH Offline
                                hskoglund
                                wrote on last edited by
                                #22

                                Indeed, Qt doesn't. I was thinking of submitting a bug report somewhere to unixODBC or FreeTDS

                                Jonas KvingeJ 1 Reply Last reply
                                0
                                • hskoglundH hskoglund

                                  Indeed, Qt doesn't. I was thinking of submitting a bug report somewhere to unixODBC or FreeTDS

                                  Jonas KvingeJ Offline
                                  Jonas KvingeJ Offline
                                  Jonas Kvinge
                                  wrote on last edited by
                                  #23

                                  @hskoglund
                                  Thanks for doing all the debugging on this.
                                  I already did submit a bug report to Qt: https://bugreports.qt.io/browse/QTBUG-82724
                                  But if you think the problem is in FreeTDS or unixODBC, maybe we should submit a bug report there instead.

                                  hskoglundH 1 Reply Last reply
                                  0
                                  • Jonas KvingeJ Jonas Kvinge

                                    @hskoglund
                                    Thanks for doing all the debugging on this.
                                    I already did submit a bug report to Qt: https://bugreports.qt.io/browse/QTBUG-82724
                                    But if you think the problem is in FreeTDS or unixODBC, maybe we should submit a bug report there instead.

                                    hskoglundH Offline
                                    hskoglundH Offline
                                    hskoglund
                                    wrote on last edited by
                                    #24

                                    @Jonas-Kvinge Yeah, it seems to me like there's some forgotten corner in unixODBC or FreeTDS that handle SQL commands, and someone forgot to upgrade that to ODBC3 support for "restore...".

                                    On the other hand, there already is a isFreeTDSDriver boolean var in qsql_odbc.cpp that's used for some workarounds, like turning off Unicode support(!): unicode = unicode && !isFreeTDSDriver

                                    So I mean, since the infrastructure is already in place in qsql_odbc.cpp to detect the presence of FreeTDS, you could suggest in your bug report that Qt implements yet another workaround, e.g.

                                    if (isFreeTDSDriver) 
                                        r = SQLFetch(d->hStmt);
                                    else
                                        r = SQLFetchScroll(d->hStmt,SQL_FETCH_FIRST,0);
                                    

                                    (Or we download the sources of unixODBC and FreeTDS and start digging. Maybe next Saturday :-)

                                    Jonas KvingeJ 1 Reply Last reply
                                    1
                                    • hskoglundH hskoglund

                                      @Jonas-Kvinge Yeah, it seems to me like there's some forgotten corner in unixODBC or FreeTDS that handle SQL commands, and someone forgot to upgrade that to ODBC3 support for "restore...".

                                      On the other hand, there already is a isFreeTDSDriver boolean var in qsql_odbc.cpp that's used for some workarounds, like turning off Unicode support(!): unicode = unicode && !isFreeTDSDriver

                                      So I mean, since the infrastructure is already in place in qsql_odbc.cpp to detect the presence of FreeTDS, you could suggest in your bug report that Qt implements yet another workaround, e.g.

                                      if (isFreeTDSDriver) 
                                          r = SQLFetch(d->hStmt);
                                      else
                                          r = SQLFetchScroll(d->hStmt,SQL_FETCH_FIRST,0);
                                      

                                      (Or we download the sources of unixODBC and FreeTDS and start digging. Maybe next Saturday :-)

                                      Jonas KvingeJ Offline
                                      Jonas KvingeJ Offline
                                      Jonas Kvinge
                                      wrote on last edited by
                                      #25

                                      @hskoglund
                                      I modified the qodbc driver but I still can't make it work. I tried both with Qt 5.14.1 and the dev branch. Am I missing something?
                                      I'm using openSUSE tumbleweed (up to date) with unixODBC 2.3.7 and FreeTDS ODBC driver 1.1.20

                                      In checkHasSQLFetchScroll() I just added || isFreeTDSDriver

                                      In bool QODBCResult::fetchFirst():

                                      if (d->hasSQLFetchScroll)
                                             r = SQLFetchScroll(d->hStmt, SQL_FETCH_FIRST, 0);
                                         else
                                             r = SQLFetch(d->hStmt);
                                      

                                      If you want to test my code, it's all on GitHub:
                                      https://github.com/jonaski/sqlrestore
                                      Look at the freetds branch, the modified qodbc driver is in 3rdparty and will exposed as QODBCX , select QODBCX in test settings AND restart the program (that's the statically compiled driver in 3rdparty)

                                      hskoglundH 1 Reply Last reply
                                      0
                                      • Jonas KvingeJ Jonas Kvinge

                                        @hskoglund
                                        I modified the qodbc driver but I still can't make it work. I tried both with Qt 5.14.1 and the dev branch. Am I missing something?
                                        I'm using openSUSE tumbleweed (up to date) with unixODBC 2.3.7 and FreeTDS ODBC driver 1.1.20

                                        In checkHasSQLFetchScroll() I just added || isFreeTDSDriver

                                        In bool QODBCResult::fetchFirst():

                                        if (d->hasSQLFetchScroll)
                                               r = SQLFetchScroll(d->hStmt, SQL_FETCH_FIRST, 0);
                                           else
                                               r = SQLFetch(d->hStmt);
                                        

                                        If you want to test my code, it's all on GitHub:
                                        https://github.com/jonaski/sqlrestore
                                        Look at the freetds branch, the modified qodbc driver is in 3rdparty and will exposed as QODBCX , select QODBCX in test settings AND restart the program (that's the statically compiled driver in 3rdparty)

                                        hskoglundH Offline
                                        hskoglundH Offline
                                        hskoglund
                                        wrote on last edited by
                                        #26

                                        @Jonas-Kvinge You were 99% there :-)

                                        Only thing you forgot, since you altered the odbc.json file to just contain "QODBCX", you also need to add it the list of kosher names in main.cpp's QODBCDriverPlugin::create(), i.e. change line 62 in main.cpp from

                                        if (name == QLatin1String("QODBC") || name == QLatin1String("QODBC3")) {
                                        

                                        to

                                        if (name == QLatin1String("QODBC") || name == QLatin1String("QODBC3") || name == QLatin1String("QODBCX")) {
                                        

                                        P.S. I can see in your logging: "d->hasSQLFetchScroll: false" and this warning rears its ugly head:
                                        "QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries"
                                        Maybe you neutered qsql_odbc.cpp too much, because other SQL commands like Select works without the patch, but maybe better safe than sorry.

                                        P.P.S. I see you're using QThread for the database calls, I never got around to doing that, right now I have some really slooow SQLServers to interface with, maybe I could use some of your QThreading code...

                                        Jonas KvingeJ 1 Reply Last reply
                                        1
                                        • hskoglundH hskoglund

                                          @Jonas-Kvinge You were 99% there :-)

                                          Only thing you forgot, since you altered the odbc.json file to just contain "QODBCX", you also need to add it the list of kosher names in main.cpp's QODBCDriverPlugin::create(), i.e. change line 62 in main.cpp from

                                          if (name == QLatin1String("QODBC") || name == QLatin1String("QODBC3")) {
                                          

                                          to

                                          if (name == QLatin1String("QODBC") || name == QLatin1String("QODBC3") || name == QLatin1String("QODBCX")) {
                                          

                                          P.S. I can see in your logging: "d->hasSQLFetchScroll: false" and this warning rears its ugly head:
                                          "QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries"
                                          Maybe you neutered qsql_odbc.cpp too much, because other SQL commands like Select works without the patch, but maybe better safe than sorry.

                                          P.P.S. I see you're using QThread for the database calls, I never got around to doing that, right now I have some really slooow SQLServers to interface with, maybe I could use some of your QThreading code...

                                          Jonas KvingeJ Offline
                                          Jonas KvingeJ Offline
                                          Jonas Kvinge
                                          wrote on last edited by
                                          #27

                                          @hskoglund
                                          I'm doing that: https://github.com/jonaski/sqlrestore/blob/freetds/3rdparty/qsqlodbc/qsql_odbc_main.cpp#L57

                                          1 Reply Last reply
                                          0

                                          • Login

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Categories
                                          • Recent
                                          • Tags
                                          • Popular
                                          • Users
                                          • Groups
                                          • Search
                                          • Get Qt Extensions
                                          • Unsolved