LCOV - code coverage report
Current view: top level - src/odbc/unittests - tvp.c (source / functions) Hit Total Coverage
Test: FreeTDS coverage Lines: 263 274 96.0 %
Date: 2025-01-18 11:50:39 Functions: 11 11 100.0 %

          Line data    Source code
       1             : /* Test binding and calling of TVPs */
       2             : 
       3             : #include "common.h"
       4             : #include <assert.h>
       5             : #include <odbcss.h>
       6             : 
       7             : #undef MEMORY_TESTS
       8             : #if defined(HAVE_MALLOC_H)
       9             : #  include <malloc.h>
      10             : #  if defined(HAVE_MALLINFO2) || defined(HAVE_MALLINFO) || defined(HAVE__HEAPWALK)
      11             : #    define MEMORY_TESTS 1
      12             : #  endif
      13             : #endif
      14             : 
      15             : #if defined(HAVE_VALGRIND_MEMCHECK_H)
      16             : #  include <valgrind/valgrind.h>
      17             : #else
      18             : #  define RUNNING_ON_VALGRIND 0
      19             : #endif
      20             : 
      21             : #include <freetds/bool.h>
      22             : 
      23             : #define MAX_ROWS 5
      24             : #define MAX_STRING_LENGTH 20
      25             : 
      26             : static SQLINTEGER intCol[MAX_ROWS];
      27             : static SQLCHAR strCol[MAX_ROWS][MAX_STRING_LENGTH], binCol[MAX_ROWS][MAX_STRING_LENGTH];
      28             : static SQL_DATE_STRUCT dateCol[MAX_ROWS];
      29             : static SQL_NUMERIC_STRUCT numericCol[MAX_ROWS];
      30             : 
      31             : static SQLLEN lIntCol[MAX_ROWS];
      32             : static SQLLEN lStrCol[MAX_ROWS];
      33             : static SQLLEN lDateCol[MAX_ROWS];
      34             : static SQLLEN lNumericCol[MAX_ROWS];
      35             : static SQLLEN lBinCol[MAX_ROWS];
      36             : 
      37             : static SQLCHAR outputBuffer[256];
      38             : static SQLLEN lenBuffer;
      39             : 
      40             : typedef union {
      41             :         SQLPOINTER fldSQLPOINTER;
      42             :         SQLSMALLINT fldSQLSMALLINT;
      43             :         SQLUSMALLINT fldSQLUSMALLINT;
      44             :         SQLINTEGER fldSQLINTEGER;
      45             :         SQLUINTEGER fldSQLUINTEGER;
      46             :         SQLLEN fldSQLLEN;
      47             :         SQLULEN fldSQLULEN;
      48             : } field_output;
      49             : 
      50             : /* utility to get a field from descriptor */
      51             : static field_output
      52          54 : get_desc_field(SQLINTEGER desc_type, SQLSMALLINT icol, SQLSMALLINT fDescType, size_t size)
      53             : {
      54             :         SQLHDESC desc;
      55             :         SQLINTEGER ind;
      56             :         field_output buf;
      57             : 
      58          54 :         assert(size <= sizeof(buf));
      59          54 :         CHKGetStmtAttr(desc_type, &desc, sizeof(desc), &ind, "S");
      60             : 
      61          54 :         memset(&buf, 0x5a, sizeof(buf));
      62          54 :         ind = 1234;
      63          54 :         CHKGetDescField(desc, icol, fDescType, &buf, size, &ind, "S");
      64          54 :         assert(ind == size);
      65             : 
      66          54 :         return buf;
      67             : }
      68             : 
      69             : /* Utility to get a field from descriptor.
      70             :  * desc_type   APP or IMP.
      71             :  * col         column number (or 0 if does not matter).
      72             :  * field       descriptor field (SQL_DESC_xxx).
      73             :  * type        SQL type to be returned, strings not supported.
      74             :  */
      75             : #define GET_DESC_FIELD(desc_type, col, field, type) \
      76             :         (get_desc_field(SQL_ATTR_ ## desc_type ## _PARAM_DESC, col, field, sizeof(type)).fld ## type)
      77             : 
      78             : /* utility to check condition and returns error string */
      79             : static char*
      80          52 : check_cond(bool condition, const char *fmt, ...)
      81             : {
      82             :         va_list ap;
      83          52 :         char *ret = NULL;
      84             : 
      85          52 :         if (condition)
      86             :                 return ret;
      87             : 
      88           0 :         va_start(ap, fmt);
      89           0 :         assert(vasprintf(&ret, fmt, ap) >= 0);
      90           0 :         va_end(ap);
      91           0 :         return ret;
      92             : }
      93             : 
      94             : #define CHECK_COND(args) do { \
      95             :         char *err = check_cond args; \
      96             :         if (err) { \
      97             :                 failed = true; \
      98             :                 fprintf(stderr, "Wrong condition at line %d: %s\n", __LINE__, err); \
      99             :                 free(err); \
     100             :         } \
     101             : } while(0)
     102             : 
     103             : /*
     104             :  * Generate some data as the columns of our TVPs
     105             :  */
     106             : static void
     107           8 : setup(void)
     108             : {
     109             :         int i;
     110             : 
     111          48 :         for (i = 0; i < MAX_ROWS; i++) {
     112             :                 /* Setup integer column */
     113          40 :                 intCol[i] = i * 10;
     114          40 :                 lIntCol[i] = sizeof(SQLINTEGER);
     115             : 
     116             :                 /* Setup string column */
     117          40 :                 sprintf((char *) strCol[i], "Dummy value %d", i * 3);
     118          40 :                 lStrCol[i] = strlen((char *) strCol[i]);
     119             : 
     120             :                 /* Setup date column */
     121          40 :                 dateCol[i].day = (i % 28) + 1;
     122          40 :                 dateCol[i].month = (i * 5) % 12 + 1;
     123          40 :                 dateCol[i].year = i + 2000;
     124          40 :                 lDateCol[i] = sizeof(SQL_DATE_STRUCT);
     125             : 
     126             :                 /* Setup numeric values column */
     127          40 :                 numericCol[i].precision = 10;
     128          40 :                 numericCol[i].scale = 0;
     129          40 :                 numericCol[i].sign = i % 2;
     130          40 :                 memset(numericCol[i].val, 0, SQL_MAX_NUMERIC_LEN);
     131          40 :                 sprintf((char *) numericCol[i].val, "%x", i * 10);
     132          40 :                 lNumericCol[i] = sizeof(SQL_NUMERIC_STRUCT);
     133             : 
     134             :                 /* Setup binary values column */
     135          40 :                 sprintf((char *) binCol[i], "%d", i * 11);
     136          40 :                 lBinCol[i] = strlen((char *) binCol[i]);
     137             :         }
     138           8 : }
     139             : 
     140             : static void
     141             : dirty_name(SQLWCHAR *name)
     142             : {
     143           8 :         const SQLWCHAR f = name[0];
     144           8 :         name[0] = (f == 'X' || f == 'x') ? 'Y' : 'X';
     145             : }
     146             : 
     147             : /*
     148             :  * Test calling a RPC with a TVP containing 3 columns and 4 rows
     149             :  */
     150             : static void
     151           2 : TestTVPInsert(void)
     152             : {
     153             :         SQLWCHAR *tableName;
     154             :         SQLLEN numRows;
     155             :         SQLHDESC apd;
     156           2 :         bool failed = false;
     157             :         SQLPOINTER ptr;
     158             : 
     159             :         /* here we append some dummy string to check binding with a length */
     160           2 :         tableName = odbc_get_sqlwchar(&odbc_buf, "TVPType_and_garbage");
     161             : 
     162           2 :         odbc_command("IF OBJECT_ID('TestTVPProc') IS NOT NULL DROP PROC TestTVPProc");
     163           2 :         odbc_command("IF TYPE_ID('TVPType') IS NOT NULL DROP TYPE TVPType");
     164           2 :         odbc_command("IF OBJECT_ID('TVPTable') IS NOT NULL DROP TABLE TVPTable");
     165             : 
     166           2 :         odbc_command("CREATE TABLE TVPTable (PersonID INT PRIMARY KEY, Name VARCHAR(50))");
     167           2 :         odbc_command("CREATE TYPE TVPType " "AS TABLE (vPersonID INT PRIMARY KEY, vName VARCHAR(50))");
     168           2 :         odbc_command("CREATE PROCEDURE TestTVPProc (@TVPParam TVPType READONLY) "
     169             :                 "AS INSERT INTO TVPTable SELECT * FROM @TVPParam");
     170             : 
     171           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     172           2 :         ptr = GET_DESC_FIELD(APP, 1, SQL_DESC_DATA_PTR, SQLPOINTER);
     173           2 :         CHECK_COND((ptr == intCol, "SQL_DESC_DATA_PTR expected %p got %p", intCol, ptr));
     174           2 :         CHKGetStmtAttr(SQL_ATTR_APP_PARAM_DESC, &apd, sizeof(apd), NULL, "S");
     175           2 :         CHKSetDescField(apd, 1, SQL_DESC_CONCISE_TYPE, TDS_INT2PTR(SQL_C_DOUBLE), sizeof(SQLSMALLINT), "S");
     176           2 :         ptr = GET_DESC_FIELD(APP, 1, SQL_DESC_DATA_PTR, SQLPOINTER);
     177           2 :         CHECK_COND((ptr == NULL, "SQL_DESC_DATA_PTR expected %p got %p", NULL, ptr));
     178           2 :         assert(!failed);
     179             : 
     180           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, 7 * sizeof(SQLWCHAR), NULL, "S");
     181           2 :         dirty_name(tableName);
     182             : 
     183           2 :         CHKGetStmtAttr(SQL_ATTR_APP_PARAM_DESC, &apd, sizeof(apd), NULL, "S");
     184           2 :         CHKSetDescField(apd, 1, SQL_DESC_OCTET_LENGTH_PTR, (SQLPOINTER) &numRows, sizeof(void*), "S");
     185             : 
     186           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER, "S");
     187             : 
     188           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     189           2 :         CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, MAX_STRING_LENGTH, 0, strCol, MAX_STRING_LENGTH, lStrCol, "S");
     190             : 
     191           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER, "S");
     192             : 
     193             :         /* Population of the StrLen_or_IndPtr buffer can be deferred */
     194             :         /* We use one rows less than the maximum to check if code is using the right value */
     195           2 :         numRows = MAX_ROWS - 1;
     196             : 
     197           2 :         CHKExecDirect(T("{CALL TestTVPProc(?)}"), SQL_NTS, "S");
     198             : 
     199             :         /* Ensure that we successfully add 5 rows */
     200           2 :         odbc_command("SELECT COUNT(*) FROM TVPTable");
     201             : 
     202           2 :         CHKFetch("SI");
     203             : 
     204           2 :         CHKGetData(1, SQL_C_CHAR, outputBuffer, sizeof(outputBuffer), &lenBuffer, "S");
     205           2 :         if (atoi((char *) outputBuffer) != numRows) {
     206           0 :                 fprintf(stderr, "Wrong number of rows inserted, expected %ld, got %s\n", (long) numRows, outputBuffer);
     207           0 :                 exit(1);
     208             :         }
     209             : 
     210           2 :         CHKFetch("No");
     211           2 :         CHKCloseCursor("SI");
     212             : 
     213             :         /* check all rows are present */
     214           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable WHERE PersonID = 0 AND Name = 'Dummy value 0') SELECT 1");
     215           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable WHERE PersonID = 10 AND Name = 'Dummy value 3') SELECT 1");
     216           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable WHERE PersonID = 20 AND Name = 'Dummy value 6') SELECT 1");
     217           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable WHERE PersonID = 30 AND Name = 'Dummy value 9') SELECT 1");
     218           2 :         odbc_check_no_row("IF EXISTS(SELECT * FROM TVPTable WHERE PersonID = 40 AND Name = 'Dummy value 12') SELECT 1");
     219             : 
     220           2 :         odbc_command("DROP PROC TestTVPProc");
     221           2 :         odbc_command("DROP TYPE TVPType");
     222           2 :         odbc_command("DROP TABLE TVPTable");
     223             : 
     224           2 :         CHKFreeStmt(SQL_RESET_PARAMS, "S");
     225           2 : }
     226             : 
     227             : /*
     228             :  * Test TVP usage with more parameter types, using a TVP of 2 columns and 5 rows
     229             :  */
     230             : static void
     231           2 : TestTVPInsert2(void)
     232             : {
     233             :         SQLWCHAR *tableName;    /* Test explicit schema declaration */
     234             :         SQLLEN numRows;
     235             : 
     236           2 :         tableName = odbc_get_sqlwchar(&odbc_buf, "TVPType2");
     237             : 
     238           2 :         odbc_command("IF OBJECT_ID('TestTVPProc2') IS NOT NULL DROP PROC TestTVPProc2");
     239           2 :         odbc_command("IF TYPE_ID('TVPType2') IS NOT NULL DROP TYPE TVPType2");
     240           2 :         odbc_command("IF OBJECT_ID('TVPTable2') IS NOT NULL DROP TABLE TVPTable2");
     241             : 
     242           2 :         odbc_command("CREATE TABLE TVPTable2 (Num NUMERIC(10, 5), Bin BINARY(10))");
     243           2 :         odbc_command("CREATE TYPE TVPType2 " "AS TABLE (vNum NUMERIC(10, 5), vBin VARBINARY(10))");
     244           2 :         odbc_command("CREATE PROCEDURE TestTVPProc2 (@TVPParam TVPType2 READONLY) "
     245             :                 "AS INSERT INTO TVPTable2 SELECT vNum, vBin FROM @TVPParam");
     246             : 
     247           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     248           2 :         dirty_name(tableName);
     249             : 
     250           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER, "S");
     251             : 
     252           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_NUMERIC, SQL_NUMERIC,
     253             :                 10, 5, numericCol, sizeof(SQL_NUMERIC_STRUCT), lNumericCol, "S");
     254           2 :         CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY,
     255             :                 MAX_STRING_LENGTH, 4, binCol, MAX_STRING_LENGTH, lBinCol, "S");
     256             : 
     257           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER, "S");
     258             : 
     259             :         /* Population of the StrLen_or_IndPtr buffer can be deferred */
     260           2 :         numRows = MAX_ROWS;
     261             : 
     262           2 :         CHKExecDirect(T("{CALL TestTVPProc2(?)}"), SQL_NTS, "S");
     263             : 
     264             :         /* Ensure that we successfully add 5 rows */
     265           2 :         odbc_command("SELECT COUNT(*) FROM TVPTable2");
     266             : 
     267           2 :         CHKFetch("SI");
     268             : 
     269           2 :         CHKGetData(1, SQL_C_CHAR, outputBuffer, sizeof(outputBuffer), &lenBuffer, "S");
     270           2 :         if (strcmp((char *) outputBuffer, "5") != 0) {
     271           0 :                 fprintf(stderr, "Wrong number of columns inserted, expected %ld, got %s\n", (long) numRows, outputBuffer);
     272           0 :                 exit(1);
     273             :         }
     274             : 
     275           2 :         CHKFetch("No");
     276           2 :         CHKFetch("No");
     277           2 :         CHKCloseCursor("SI");
     278             : 
     279           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable2 WHERE Bin = 0x30 AND Num = -48) SELECT 1");
     280           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable2 WHERE Bin = 0x3131 AND Num = 97) SELECT 1");
     281           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable2 WHERE Bin = 0x3232 AND Num = -13361) SELECT 1");
     282           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable2 WHERE Bin = 0x3333 AND Num = 25905) SELECT 1");
     283           2 :         odbc_check_no_row("IF NOT EXISTS(SELECT * FROM TVPTable2 WHERE Bin = 0x3434 AND Num = -14386) SELECT 1");
     284             : 
     285           2 :         odbc_command("DROP PROC TestTVPProc2");
     286           2 :         odbc_command("DROP TYPE TVPType2");
     287           2 :         odbc_command("DROP TABLE TVPTable2");
     288             : 
     289           2 :         CHKFreeStmt(SQL_RESET_PARAMS, "S");
     290           2 : }
     291             : 
     292             : /*
     293             :  * Test freeing a TVP without executing it - simulates the case
     294             :  * where we encounter an error before calling SQLExecDirect
     295             :  */
     296             : static void
     297           2 : TestTVPMemoryManagement(void)
     298             : {
     299             :         SQLWCHAR *tableName;
     300             :         SQLLEN numRows;
     301             : 
     302           2 :         tableName = odbc_get_sqlwchar(&odbc_buf, "TVPType");
     303             : 
     304           2 :         odbc_command("IF OBJECT_ID('TestTVPProc') IS NOT NULL DROP PROC TestTVPProc");
     305           2 :         odbc_command("IF TYPE_ID('TVPType') IS NOT NULL DROP TYPE TVPType");
     306           2 :         odbc_command("IF OBJECT_ID('TVPTable') IS NOT NULL DROP TABLE TVPTable");
     307             : 
     308           2 :         odbc_command("CREATE TABLE TVPTable (PersonID INT PRIMARY KEY, Name VARCHAR(50))");
     309           2 :         odbc_command("CREATE TYPE TVPType " "AS TABLE (vPersonID INT PRIMARY KEY, vName VARCHAR(50))");
     310           2 :         odbc_command("CREATE PROCEDURE TestTVPProc (@TVPParam TVPType READONLY) "
     311             :                 "AS INSERT INTO TVPTable SELECT * FROM @TVPParam");
     312             : 
     313           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     314           2 :         dirty_name(tableName);
     315             : 
     316           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER, "S");
     317             : 
     318           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     319           2 :         CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, MAX_STRING_LENGTH, 0, strCol, MAX_STRING_LENGTH, lStrCol, "S");
     320             : 
     321           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER, "S");
     322             : 
     323           2 :         odbc_command("DROP PROC TestTVPProc");
     324           2 :         odbc_command("DROP TYPE TVPType");
     325           2 :         odbc_command("DROP TABLE TVPTable");
     326             : 
     327           2 :         CHKFreeStmt(SQL_RESET_PARAMS, "S");
     328           2 : }
     329             : 
     330             : /* Test some errors happens when we expect them */
     331             : static void
     332           2 : TestErrors(void)
     333             : {
     334             :         SQLWCHAR *tableName;
     335             :         SQLLEN numRows;
     336             : 
     337           2 :         tableName = odbc_get_sqlwchar(&odbc_buf, "TVPType");
     338             : 
     339             :         /* SQL error 07006 -- [Microsoft][ODBC Driver 17 for SQL Server]Restricted data type attribute violation */
     340           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "E");
     341           2 :         odbc_read_error();
     342           2 :         assert(strcmp(odbc_sqlstate, "07006") == 0);
     343             : 
     344             :         /* SQL error HY105 -- [Microsoft][ODBC Driver 17 for SQL Server]Invalid parameter type */
     345           2 :         CHKBindParameter(1, SQL_PARAM_OUTPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "E");
     346           2 :         odbc_read_error();
     347           2 :         assert(strcmp(odbc_sqlstate, "HY105") == 0);
     348             : 
     349             :         /* SQL error HY105 -- [Microsoft][ODBC Driver 17 for SQL Server]Invalid parameter type */
     350           2 :         CHKBindParameter(1, SQL_PARAM_OUTPUT, SQL_C_LONG, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "E");
     351           2 :         odbc_read_error();
     352           2 :         assert(strcmp(odbc_sqlstate, "HY105") == 0);
     353             : 
     354             :         /* SQL error HY105 -- [Microsoft][ODBC Driver 17 for SQL Server]Invalid parameter type */
     355           2 :         CHKBindParameter(1, SQL_PARAM_INPUT_OUTPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "E");
     356           2 :         odbc_read_error();
     357           2 :         assert(strcmp(odbc_sqlstate, "HY105") == 0);
     358             : 
     359           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     360           2 :         tableName[0] = 'A';
     361             : 
     362           2 :         CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     363             : 
     364             :         /* SQL error IM020 -- [Microsoft][ODBC Driver 17 for SQL Server]Parameter focus does not refer to a table-valued parameter */
     365           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 2, SQL_IS_INTEGER, "E");
     366           2 :         odbc_read_error();
     367           2 :         assert(strcmp(odbc_sqlstate, "IM020") == 0);
     368             : 
     369           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER, "S");
     370             : 
     371             :         /* SQL error HY004 -- [Microsoft][ODBC Driver 17 for SQL Server]Invalid SQL data type */
     372           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "E");
     373           2 :         odbc_read_error();
     374           2 :         assert(strcmp(odbc_sqlstate, "HY004") == 0);
     375             : 
     376           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     377           2 :         CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, MAX_STRING_LENGTH, 0, strCol, MAX_STRING_LENGTH, lStrCol, "S");
     378             : 
     379           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER, "S");
     380             : 
     381           2 :         odbc_reset_statement();
     382           2 : }
     383             : 
     384             : static void
     385           2 : TestDescriptorValues(void)
     386             : {
     387             :         SQLWCHAR *tableName;
     388             :         SQLLEN numRows;
     389             :         SQLPOINTER ptr;
     390             :         SQLSMALLINT count, type;
     391             :         SQLLEN len;
     392           2 :         bool failed = false;
     393             : 
     394           2 :         tableName = odbc_get_sqlwchar(&odbc_buf, "TVPType");
     395             : 
     396           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     397           2 :         CHECK_COND((count == 0, "count %d == 0", (int) count));
     398           2 :         count = GET_DESC_FIELD(IMP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     399           2 :         CHECK_COND((count == 0, "count %d == 0", (int) count));
     400             : 
     401           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     402           2 :         dirty_name(tableName);
     403             : 
     404           2 :         count = GET_DESC_FIELD(APP, 1, SQL_DESC_LENGTH, SQLULEN);
     405           2 :         CHECK_COND((count == 1, "count %d == 1", (int) count));
     406           2 :         count = GET_DESC_FIELD(IMP, 1, SQL_DESC_LENGTH, SQLULEN);
     407           2 :         CHECK_COND((count == 0, "count %d == 0", (int) count));
     408             : 
     409           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_ARRAY_SIZE, SQLULEN);
     410           2 :         CHECK_COND((count == 1, "count %d == 1", (int) count));
     411             : 
     412           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     413           2 :         CHECK_COND((count == 1, "count %d == 1", (int) count));
     414           2 :         count = GET_DESC_FIELD(IMP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     415           2 :         CHECK_COND((count == 1, "count %d == 1", (int) count));
     416             : 
     417             :         /* data pointer should point to table name */
     418           2 :         ptr = GET_DESC_FIELD(APP, 1, SQL_DESC_DATA_PTR, SQLPOINTER);
     419           2 :         CHECK_COND((ptr == tableName, "SQL_DESC_DATA_PTR expected %p got %p", tableName, ptr));
     420           2 :         if (odbc_driver_is_freetds()) {
     421             :                 /* MS driver cannot read this, used internally by FreeTDS */
     422           2 :                 ptr = GET_DESC_FIELD(IMP, 1, SQL_DESC_DATA_PTR, SQLPOINTER);
     423           2 :                 printf("Internal pointer %p\n", ptr);
     424             :         }
     425             : 
     426             :         /* indicator should be the pointer to number of rows */
     427           2 :         ptr = GET_DESC_FIELD(APP, 1, SQL_DESC_INDICATOR_PTR, SQLPOINTER);
     428           2 :         CHECK_COND((ptr == &numRows, "SQL_DESC_INDICATOR_PTR expected %p got %p", &numRows, ptr));
     429           2 :         if (odbc_driver_is_freetds()) {
     430           2 :                 ptr = GET_DESC_FIELD(IMP, 1, SQL_DESC_INDICATOR_PTR, SQLPOINTER);
     431           2 :                 CHECK_COND((ptr == NULL, "SQL_DESC_INDICATOR_PTR expected %p got %p", NULL, ptr));
     432             :         }
     433             : 
     434             :         /* octect length pointer should be the pointer to number of rows */
     435           2 :         ptr = GET_DESC_FIELD(APP, 1, SQL_DESC_OCTET_LENGTH_PTR, SQLPOINTER);
     436           2 :         CHECK_COND((ptr == &numRows, "SQL_DESC_OCTET_LENGTH_PTR expected %p got %p", &numRows, ptr));
     437           2 :         if (odbc_driver_is_freetds()) {
     438           2 :                 ptr = GET_DESC_FIELD(IMP, 1, SQL_DESC_OCTET_LENGTH_PTR, SQLPOINTER);
     439           2 :                 CHECK_COND((ptr == NULL, "SQL_DESC_OCTET_LENGTH_PTR expected %p got %p", NULL, ptr));
     440             :         }
     441             : 
     442             :         /* this should be then length of tableName passed to SQLBindParameter */
     443           2 :         len = GET_DESC_FIELD(APP, 1, SQL_DESC_OCTET_LENGTH, SQLLEN);
     444           2 :         CHECK_COND((len == SQL_NTS, "SQL_DESC_OCTET_LENGTH expected %ld got %ld", (long) SQL_NTS, (long) len));
     445           2 :         len = GET_DESC_FIELD(IMP, 1, SQL_DESC_OCTET_LENGTH, SQLLEN);
     446           2 :         CHECK_COND((len == 0, "SQL_DESC_OCTET_LENGTH expected %ld got %ld", (long) 0, (long) len));
     447             : 
     448           2 :         type = GET_DESC_FIELD(APP, 1, SQL_DESC_CONCISE_TYPE, SQLSMALLINT);
     449           2 :         CHECK_COND((type == SQL_C_BINARY, "SQL_DESC_CONCISE_TYPE expected %d got %d", SQL_C_BINARY, type));
     450           2 :         type = GET_DESC_FIELD(IMP, 1, SQL_DESC_CONCISE_TYPE, SQLSMALLINT);
     451           2 :         CHECK_COND((type == SQL_SS_TABLE, "SQL_DESC_CONCISE_TYPE expected %d got %d", SQL_SS_TABLE, type));
     452             : 
     453             :         /* setting parameter focus should move to different descriptors */
     454           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER, "S");
     455             : 
     456           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     457           2 :         CHECK_COND((count == 0, "count %d == 0", (int) count));
     458           2 :         count = GET_DESC_FIELD(IMP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     459           2 :         CHECK_COND((count == 0, "count %d == 0", (int) count));
     460             : 
     461           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_ARRAY_SIZE, SQLULEN);
     462           2 :         CHECK_COND((count == 5, "count %d == 5", (int) count));
     463             : 
     464             :         /* modify descriptors */
     465           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     466           2 :         CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, MAX_STRING_LENGTH, 0, strCol, MAX_STRING_LENGTH, lStrCol, "S");
     467             : 
     468           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     469           2 :         CHECK_COND((count == 2, "count %d == 2", (int) count));
     470           2 :         count = GET_DESC_FIELD(IMP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     471           2 :         CHECK_COND((count == 2, "count %d == 2", (int) count));
     472             : 
     473             :         /* switch back to main descriptors */
     474           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER, "S");
     475             : 
     476           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     477           2 :         CHECK_COND((count == 1, "count %d == 1", (int) count));
     478           2 :         count = GET_DESC_FIELD(IMP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     479           2 :         CHECK_COND((count == 1, "count %d == 1", (int) count));
     480             : 
     481             :         /* switch back to table */
     482           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER, "S");
     483             : 
     484             :         /* this should fail, cannot set table inside a table */
     485           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "E");
     486             : 
     487           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER, "S");
     488             : 
     489             :         /* reset parameters, we should reset TVP */
     490           2 :         CHKFreeStmt(SQL_RESET_PARAMS, "S");
     491             : 
     492           2 :         count = GET_DESC_FIELD(APP, 0, SQL_DESC_COUNT, SQLSMALLINT);
     493           2 :         CHECK_COND((count == 0, "count %d == 0", (int) count));
     494             : 
     495           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     496             : 
     497           2 :         assert(!failed);
     498             : 
     499           2 :         odbc_reset_statement();
     500           2 : }
     501             : 
     502             : #ifdef MEMORY_TESTS
     503             : static size_t
     504           8 : memory_usage(void)
     505             : {
     506           8 :         size_t ret = 0;
     507             : 
     508             :         /* mallinfo does not work on Valgrind, ignore */
     509           8 :         if (RUNNING_ON_VALGRIND > 0)
     510             :                 return ret;
     511             : 
     512             : #if defined(HAVE__HEAPWALK)
     513             :         {
     514             :                 _HEAPINFO hinfo;
     515             :                 int heapstatus;
     516             : 
     517             :                 hinfo._pentry = NULL;
     518             :                 while ((heapstatus = _heapwalk(&hinfo)) == _HEAPOK) {
     519             :                         if (hinfo._useflag == _USEDENTRY)
     520             :                                 ret += hinfo._size;
     521             :                 }
     522             :                 assert(heapstatus == _HEAPEMPTY || heapstatus == _HEAPEND);
     523             :         }
     524             : #elif defined(HAVE_MALLINFO2)
     525             :         ret = mallinfo2().uordblks;
     526             : #else
     527           0 :         ret = (size_t) (mallinfo().uordblks);
     528             : #endif
     529           0 :         return ret;
     530             : }
     531             : 
     532             : static void
     533           2 : TestInitializeLeak(void)
     534             : {
     535             :         SQLWCHAR *tableName;
     536             :         SQLLEN numRows;
     537             :         size_t initial_memory;
     538             :         int i;
     539             : 
     540           2 :         tableName = odbc_get_sqlwchar(&odbc_buf, "TVPType");
     541             : 
     542           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     543             : 
     544           2 :         CHKSetStmtAttr(SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER, "S");
     545             : 
     546           2 :         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     547             : 
     548             :         /* try to repeat binding column */
     549           2 :         initial_memory = memory_usage();
     550        2050 :         for (i = 0; i < 1024; ++i)
     551        2048 :                 CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, intCol, sizeof(SQLINTEGER), lIntCol, "S");
     552             : 
     553             :         /* memory should not increase a lot */
     554           2 :         assert(memory_usage() < initial_memory + 10240);
     555             : 
     556           2 :         odbc_reset_statement();
     557             : 
     558             :         /* check we don't leak binding table multiple times */
     559             :         /* this leak memory on MS driver */
     560           2 :         if (odbc_driver_is_freetds()) {
     561           2 :                 CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     562             : 
     563             :                 /* try to repeat set of the table */
     564           2 :                 initial_memory = memory_usage();
     565        2050 :                 for (i = 0; i < 1024; ++i)
     566        2048 :                         CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, MAX_ROWS, 0, tableName, SQL_NTS, &numRows, "S");
     567             : 
     568             :                 /* memory should not increase a lot */
     569           2 :                 assert(memory_usage() < initial_memory + 10240);
     570             : 
     571           2 :                 odbc_reset_statement();
     572             :         }
     573           2 : }
     574             : #endif
     575             : 
     576             : int
     577           8 : main(void)
     578             : {
     579           8 :         odbc_use_version3 = 1;
     580             : 
     581           8 :         setup();
     582             : 
     583           8 :         odbc_connect();
     584             : 
     585           8 :         if (odbc_tds_version() < 0x703) {
     586           6 :                 odbc_disconnect();
     587           6 :                 printf("TVP data is supported since protocol 7.3, MSSQL only.\n");
     588           6 :                 odbc_test_skipped();
     589           0 :                 return 0;
     590             :         }
     591             : 
     592           2 :         TestTVPInsert();
     593           2 :         TestTVPInsert2();
     594           2 :         TestTVPMemoryManagement();
     595           2 :         TestErrors();
     596           2 :         TestDescriptorValues();
     597             : 
     598             : #ifdef MEMORY_TESTS
     599           2 :         TestInitializeLeak();
     600             : #endif
     601             : 
     602           2 :         odbc_disconnect();
     603             : 
     604           2 :         return 0;
     605             : }

Generated by: LCOV version 1.13