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

Generated by: LCOV version 1.13