LCOV - code coverage report
Current view: top level - src/odbc/unittests - cancel.c (source / functions) Hit Total Coverage
Test: FreeTDS coverage Lines: 137 150 91.3 %
Date: 2025-02-21 09:36:06 Functions: 8 9 88.9 %

          Line data    Source code
       1             : /* Testing SQLCancel() */
       2             : 
       3             : #include "common.h"
       4             : 
       5             : #include <assert.h>
       6             : #include <signal.h>
       7             : 
       8             : #if HAVE_UNISTD_H
       9             : #include <unistd.h>
      10             : #endif /* HAVE_UNISTD_H */
      11             : 
      12             : #include <freetds/thread.h>
      13             : #include <freetds/utils.h>
      14             : #include <freetds/bool.h>
      15             : #include <freetds/replacements.h>
      16             : 
      17             : #if TDS_HAVE_MUTEX
      18             : 
      19             : #ifdef _WIN32
      20             : #undef HAVE_ALARM
      21             : #endif
      22             : 
      23             : static SQLTCHAR sqlstate[SQL_SQLSTATE_SIZE + 1];
      24             : static tds_mutex mtx;
      25             : 
      26             : static void
      27          40 : getErrorInfo(SQLSMALLINT sqlhdltype, SQLHANDLE sqlhandle)
      28             : {
      29          40 :         SQLINTEGER naterror = 0;
      30             :         SQLTCHAR msgtext[SQL_MAX_MESSAGE_LENGTH + 1];
      31          40 :         SQLSMALLINT msgtextl = 0;
      32             : 
      33          40 :         msgtext[0] = 0;
      34          40 :         SQLGetDiagRec(sqlhdltype,
      35             :                               (SQLHANDLE) sqlhandle,
      36             :                               1,
      37             :                               sqlstate,
      38             :                               &naterror,
      39             :                               msgtext, (SQLSMALLINT) TDS_VECTOR_SIZE(msgtext), &msgtextl);
      40          40 :         sqlstate[TDS_VECTOR_SIZE(sqlstate)-1] = 0;
      41          40 :         fprintf(stderr, "Diagnostic info:\n");
      42          40 :         fprintf(stderr, "  SQL State: %s\n", C(sqlstate));
      43          40 :         fprintf(stderr, "  SQL code : %d\n", (int) naterror);
      44          40 :         fprintf(stderr, "  Message  : %s\n", C(msgtext));
      45          40 : }
      46             : 
      47             : static void
      48           0 : exit_forced(int s TDS_UNUSED)
      49             : {
      50           0 :         exit(1);
      51             : }
      52             : 
      53             : #if HAVE_ALARM
      54             : static void
      55          20 : sigalrm_handler(int s TDS_UNUSED)
      56             : {
      57          20 :         printf(">>>> SQLCancel() ...\n");
      58          20 :         CHKCancel("S");
      59          20 :         printf(">>>> ... SQLCancel done\n");
      60             : 
      61          20 :         alarm(4);
      62          20 :         signal(SIGALRM, exit_forced);
      63          20 : }
      64             : #else
      65             : #define alarm(x) return;
      66             : #define signal(sig,h)
      67             : #endif
      68             : 
      69             : #ifdef _WIN32
      70             : 
      71             : static HANDLE alarm_cond = NULL;
      72             : 
      73             : static DWORD WINAPI
      74             : alarm_thread_proc(LPVOID arg)
      75             : {
      76             :         unsigned int timeout = (uintptr_t) arg;
      77             :         switch (WaitForSingleObject(alarm_cond, timeout * 1000)) {
      78             :         case WAIT_OBJECT_0:
      79             :                 return 0;
      80             :         }
      81             :         abort();
      82             :         return 0;
      83             : }
      84             : 
      85             : #undef alarm
      86             : #define alarm tds_alarm
      87             : static void
      88             : alarm(unsigned int timeout)
      89             : {
      90             :         static HANDLE thread = NULL;
      91             : 
      92             :         /* create an event to stop the alarm thread */
      93             :         if (alarm_cond == NULL) {
      94             :                 alarm_cond = CreateEvent(NULL, TRUE, FALSE, NULL);
      95             :                 assert(alarm_cond != NULL);
      96             :         }
      97             : 
      98             :         /* stop old alarm */
      99             :         if (thread) {
     100             :                 SetEvent(alarm_cond);
     101             :                 assert(WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0);
     102             :                 CloseHandle(thread);
     103             :                 thread = NULL;
     104             :         }
     105             : 
     106             :         if (timeout) {
     107             :                 ResetEvent(alarm_cond);
     108             : 
     109             :                 /* start alarm thread */
     110             :                 thread = CreateThread(NULL, 0, alarm_thread_proc, (LPVOID) (uintptr_t) timeout, 0, NULL);
     111             :                 assert(thread);
     112             :         }
     113             : }
     114             : #endif
     115             : 
     116             : volatile bool exit_thread;
     117             : 
     118          20 : static TDS_THREAD_PROC_DECLARE(wait_thread_proc, arg TDS_UNUSED)
     119             : {
     120             :         int n;
     121             : 
     122          20 :         tds_sleep_s(4);
     123             : 
     124          20 :         printf(">>>> SQLCancel() ...\n");
     125          20 :         CHKCancel("S");
     126          20 :         printf(">>>> ... SQLCancel done\n");
     127             : 
     128          20 :         for (n = 0; n < 4; ++n) {
     129          20 :                 tds_sleep_s(1);
     130          20 :                 tds_mutex_lock(&mtx);
     131          20 :                 if (exit_thread) {
     132          20 :                         tds_mutex_unlock(&mtx);
     133             :                         return TDS_THREAD_RESULT(0);
     134             :                 }
     135           0 :                 tds_mutex_unlock(&mtx);
     136             :         }
     137             : 
     138             :         exit_forced(0);
     139             :         return TDS_THREAD_RESULT(0);
     140             : }
     141             : 
     142             : static void
     143          40 : Test(bool use_threads, bool return_data)
     144             : {
     145             :         tds_thread wait_thread;
     146             : 
     147             : #if !HAVE_ALARM
     148             :         if (!use_threads) return;
     149             : #endif
     150             : 
     151          40 :         printf("testing with %s\n", use_threads ? "threads" : "signals");
     152          40 :         printf(">> Wait 5 minutes...\n");
     153          40 :         if (!use_threads) {
     154          20 :                 alarm(4);
     155          20 :                 signal(SIGALRM, sigalrm_handler);
     156             :         } else {
     157             :                 int err;
     158             : 
     159          20 :                 exit_thread = false;
     160          20 :                 alarm(120);
     161          20 :                 err = tds_thread_create(&wait_thread, wait_thread_proc, NULL);
     162          20 :                 if (err != 0) {
     163           0 :                         perror("tds_thread_create");
     164           0 :                         exit(1);
     165             :                 }
     166             :         }
     167          40 :         if (!return_data)
     168          20 :                 CHKExecDirect(T("WAITFOR DELAY '000:05:00'"), SQL_NTS, "E");
     169             :         else
     170          20 :                 odbc_command2("SELECT MAX(p1.k + p2.k * p3.k ^ p4.k) FROM tab1 p1, tab1 p2, tab1 p3, tab1 p4", "E");
     171             : 
     172          40 :         tds_mutex_lock(&mtx);
     173          40 :         exit_thread = true;
     174          40 :         tds_mutex_unlock(&mtx);
     175          40 :         alarm(0);
     176          40 :         if (use_threads)
     177          20 :                 tds_thread_join(wait_thread, NULL);
     178             : 
     179          40 :         getErrorInfo(SQL_HANDLE_STMT, odbc_stmt);
     180          40 :         if (strcmp(C(sqlstate), "HY008") != 0) {
     181           0 :                 fprintf(stderr, "Unexpected sql state returned\n");
     182           0 :                 odbc_disconnect();
     183           0 :                 exit(1);
     184             :         }
     185             : 
     186          40 :         odbc_reset_statement();
     187             : 
     188          40 :         odbc_command("SELECT name FROM sysobjects WHERE 0=1");
     189          80 :         while (CHKMoreResults("SNo") == SQL_SUCCESS)
     190           0 :                 continue;
     191          40 : }
     192             : 
     193             : /* Check that SQLCancel sends a cancellation even if no other activities are done */
     194             : static void
     195          10 : TestSendDuringCancel(void)
     196             : {
     197          10 :         odbc_check_no_row("IF EXISTS(SELECT * FROM tab1 WHERE k=10000) SELECT 1");
     198          10 :         odbc_check_no_row("IF EXISTS(SELECT * FROM tab1 WHERE k=10001) SELECT 2");
     199          10 :         alarm(15);
     200             :         /* WAITFOR + INSERT */
     201          10 :         printf("Sending query...\n");
     202          10 :         CHKExecDirect(T(/* this row will be inserted */
     203             :                         "INSERT INTO tab1 VALUES(10000, 'aaa') "
     204             :                         /* force server to return a packet, otherwise SQLExecDirect will stop */
     205             :                         "SELECT * FROM tab1 "
     206             :                         /* wait 2 seconds, giving us time to cancel */
     207             :                         "WAITFOR DELAY '000:00:02' "
     208             :                         /* this won't be executed */
     209             :                         "INSERT INTO tab1 VALUES(10001, 'bbb')"), SQL_NTS, "S");
     210             :         /* Cancel, should cancel the insert  */
     211          10 :         printf("Cancel...\n");
     212          10 :         CHKCancel("S");
     213             :         /* sleep */
     214          10 :         printf("Sleep...\n");
     215          10 :         tds_sleep_s(3);
     216          10 :         alarm(6);
     217          10 :         printf("Checking...\n");
     218          10 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM tab1 WHERE k=10000) SELECT 3");
     219          10 :         odbc_check_no_row("IF EXISTS(SELECT * FROM tab1 WHERE k=10001) SELECT 4");
     220          10 :         alarm(0);
     221          10 : }
     222             : 
     223             : /* Cancelling an IDLE statement should work */
     224             : static void
     225          10 : TestIdleStatement(void)
     226             : {
     227             :         SQLHSTMT stmt;
     228          10 :         CHKAllocStmt(&stmt, "S");
     229             : 
     230          10 :         SWAP_STMT(stmt);
     231          10 :         CHKCancel("S");
     232          10 :         CHKFreeStmt(SQL_DROP, "S");
     233             : 
     234          10 :         SWAP_STMT(stmt);
     235          10 : }
     236             : 
     237             : /* Cancelling another statement should not conflict with an active one.
     238             :  * Very similar to TestSendDuringCancel but cancelling a different statement. */
     239             : static void
     240          20 : TestOtherStatement(bool some_activity)
     241             : {
     242             :         SQLHSTMT stmt;
     243          20 :         CHKAllocStmt(&stmt, "S");
     244             : 
     245          20 :         if (some_activity)
     246          10 :                 SWAP_STMT(stmt);
     247          20 :         odbc_command("DELETE tab1 WHERE k >= 10000");
     248          40 :         while (CHKMoreResults("SNo") == SQL_SUCCESS)
     249           0 :                 continue;
     250          20 :         if (some_activity)
     251          10 :                 SWAP_STMT(stmt);
     252             : 
     253          20 :         odbc_check_no_row("IF EXISTS(SELECT * FROM tab1 WHERE k=10000) SELECT 1");
     254          20 :         odbc_check_no_row("IF EXISTS(SELECT * FROM tab1 WHERE k=10001) SELECT 2");
     255          20 :         alarm(15);
     256             :         /* WAITFOR + INSERT */
     257          20 :         printf("Sending query...\n");
     258          20 :         CHKExecDirect(T(/* this row will be inserted */
     259             :                         "INSERT INTO tab1 VALUES(10000, 'aaa') "
     260             :                         /* force server to return a packet, otherwise SQLExecDirect will stop */
     261             :                         "SELECT * FROM tab1 "
     262             :                         /* wait 2 seconds, giving us time to cancel */
     263             :                         "WAITFOR DELAY '000:00:02' "
     264             :                         /* this won't be executed */
     265             :                         "INSERT INTO tab1 VALUES(10001, 'bbb')"), SQL_NTS, "S");
     266             :         /* Cancel, should not cancel the insert  */
     267          20 :         printf("Cancel...\n");
     268          20 :         SWAP_STMT(stmt);
     269          20 :         CHKCancel("S");
     270          20 :         SWAP_STMT(stmt);
     271             :         /* sleep */
     272          20 :         printf("Sleep...\n");
     273          20 :         tds_sleep_s(3);
     274          40 :         while (CHKMoreResults("SNo") == SQL_SUCCESS)
     275           0 :                 continue;
     276          20 :         alarm(6);
     277          20 :         printf("Checking...\n");
     278          20 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM tab1 WHERE k=10000) SELECT 3");
     279          20 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM tab1 WHERE k=10001) SELECT 4");
     280          20 :         alarm(0);
     281             : 
     282          20 :         SWAP_STMT(stmt);
     283          20 :         CHKFreeStmt(SQL_DROP, "S");
     284          20 :         odbc_stmt = SQL_NULL_HSTMT;
     285          20 :         SWAP_STMT(stmt);
     286          20 : }
     287             : 
     288             : int
     289          10 : main(void)
     290             : {
     291          10 :         if (tds_mutex_init(&mtx))
     292             :                 return 1;
     293             : 
     294          10 :         if (odbc_read_login_info())
     295           0 :                 exit(1);
     296             : 
     297             :         /*
     298             :          * prepare our odbcinst.ini
     299             :          * is better to do it before connect cause unixODBC cache INIs
     300             :          * the name must be odbcinst.ini cause unixODBC accept only this name
     301             :          */
     302          10 :         if (odbc_driver[0]) {
     303          10 :                 FILE *f = fopen("odbcinst.ini", "w");
     304             : 
     305          10 :                 if (f) {
     306          10 :                         fprintf(f, "[FreeTDS]\nDriver = %s\nThreading = 0\n", odbc_driver);
     307          10 :                         fclose(f);
     308             :                         /* force iODBC */
     309          10 :                         setenv("ODBCINSTINI", "./odbcinst.ini", 1);
     310          10 :                         setenv("SYSODBCINSTINI", "./odbcinst.ini", 1);
     311             :                         /* force unixODBC (only directory) */
     312          10 :                         setenv("ODBCSYSINI", ".", 1);
     313             :                 }
     314             :         }
     315             : 
     316          10 :         odbc_use_version3 = 1;
     317          10 :         odbc_connect();
     318             : 
     319          10 :         odbc_command("IF OBJECT_ID('tab1') IS NOT NULL DROP TABLE tab1");
     320          10 :         odbc_command("CREATE TABLE tab1 ( k INT, vc VARCHAR(200) )");
     321             : 
     322          10 :         printf(">> Creating tab1...\n");
     323          10 :         odbc_command("DECLARE @i INT\n"
     324             :                 "SET NOCOUNT ON\n"
     325             :                 "SET @i = 1\n"
     326             :                 "WHILE @i <= 2000 BEGIN\n"
     327             :                 "INSERT INTO tab1 VALUES ( @i, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )\n"
     328             :                 "SET @i = @i + 1\n"
     329             :                 "END");
     330          20 :         while (CHKMoreResults("SNo") == SQL_SUCCESS)
     331           0 :                 continue;
     332          10 :         printf(">> ...done.\n");
     333             : 
     334          10 :         odbc_reset_statement();
     335             : 
     336          10 :         Test(false, false);
     337          10 :         Test(true,  false);
     338          10 :         Test(false, true);
     339          10 :         Test(true,  true);
     340             : 
     341          10 :         TestSendDuringCancel();
     342             : 
     343          10 :         TestIdleStatement();
     344             : 
     345          10 :         TestOtherStatement(false);
     346             : 
     347          10 :         TestOtherStatement(true);
     348             : 
     349          10 :         odbc_command("DROP TABLE tab1");
     350             : 
     351          10 :         odbc_disconnect();
     352          10 :         return 0;
     353             : }
     354             : 
     355             : #else
     356             : int
     357             : main(void)
     358             : {
     359             :         printf("Not possible for this platform.\n");
     360             :         return 0;
     361             : }
     362             : #endif
     363             : 

Generated by: LCOV version 1.13