LCOV - code coverage report
Current view: top level - src/ctlib/unittests - blk_textxfer.c (source / functions) Hit Total Coverage
Test: FreeTDS coverage Lines: 207 228 90.8 %
Date: 2026-11-07 23:58:45 Functions: 15 15 100.0 %

          Line data    Source code
       1             : /*
       2             :  * Test bulk copy in using blk_textxfer.
       3             :  * Text/image columns bound with a NULL buffer are sent separately
       4             :  * using blk_textxfer, possibly in multiple chunks.
       5             :  * Expected behaviour is taken from Sybase/SAP Open Client.
       6             :  */
       7             : 
       8             : #include "common.h"
       9             : 
      10             : #include <bkpublic.h>
      11             : 
      12             : static const char table_name[] = "blk_textxfer_test";
      13             : 
      14             : #define MAX_BLOB 100000
      15             : 
      16             : static CS_CONNECTION *conn;
      17             : static CS_COMMAND *cmd;
      18             : static CS_BLKDESC *blkdesc;
      19             : 
      20             : /* bound variables */
      21             : static CS_INT row_id, row_id_len = CS_UNUSED;
      22             : static CS_INT text_len, image_len, var_len;
      23             : static CS_SMALLINT text_ind, image_ind;
      24             : static char var_data[11];
      25             : static CS_BYTE text_data[MAX_BLOB];
      26             : 
      27             : static CS_BYTE blob_buf[MAX_BLOB];
      28             : 
      29             : /* expected table content, filled while sending data */
      30             : typedef struct
      31             : {
      32             :         int id;
      33             :         int text_len;           /* -1 for NULL */
      34             :         int image_len;          /* -1 for NULL */
      35             : } expected_row;
      36             : 
      37             : static expected_row expected[16];
      38             : static int num_expected;
      39             : 
      40             : /* message numbers, layer 1 (blk), origin 1 (user), severity 1 */
      41             : #define BLK_MSG(n) (0x01010100 | (n))
      42             : #define BLK_STATE_ERROR BLK_MSG(1)
      43             : #define BLK_TOO_MUCH_DATA BLK_MSG(7)
      44             : #define BLK_TOO_MANY_TEXTXFER BLK_MSG(49)
      45             : 
      46             : /* generate data for a given row and column */
      47             : static void
      48             : fill_blob(CS_BYTE *buf, int len, int id, int col)
      49             : {
      50             :         int i;
      51             : 
      52     4973696 :         for (i = 0; i < len; i++)
      53     4973520 :                 buf[i] = (CS_BYTE) ('a' + (i * 7 + id * 3 + col) % 26);
      54             : }
      55             : 
      56             : static void
      57         144 : add_expected(int id, int text_len, int image_len)
      58             : {
      59         144 :         assert(num_expected < (int) (sizeof(expected) / sizeof(expected[0])));
      60         144 :         expected[num_expected].id = id;
      61         144 :         expected[num_expected].text_len = text_len;
      62         144 :         expected[num_expected].image_len = image_len;
      63         144 :         ++num_expected;
      64         144 : }
      65             : 
      66             : /*
      67             :  * Start a bulk copy.
      68             :  * Bind text column with a NULL buffer if text_deferred is set,
      69             :  * image column is always bound with a NULL buffer.
      70             :  */
      71             : static void
      72          64 : start_in(bool text_deferred)
      73             : {
      74             :         CS_DATAFMT fmt;
      75             : 
      76             :         /*
      77             :          * Use a new descriptor for each test, SAP library does not
      78             :          * handle NULL values correctly reusing a descriptor.
      79             :          */
      80          64 :         if (blkdesc)
      81          54 :                 blk_drop(blkdesc);
      82          64 :         check_call(blk_alloc, (conn, BLK_VERSION_100, &blkdesc));
      83             : 
      84          64 :         check_call(blk_init, (blkdesc, CS_BLK_IN, (char *) table_name, CS_NULLTERM));
      85             : 
      86          64 :         memset(&fmt, 0, sizeof(fmt));
      87          64 :         fmt.count = 1;
      88          64 :         fmt.datatype = CS_INT_TYPE;
      89          64 :         fmt.maxlength = sizeof(row_id);
      90          64 :         check_call(blk_bind, (blkdesc, 1, &fmt, &row_id, &row_id_len, NULL));
      91             : 
      92          64 :         memset(&fmt, 0, sizeof(fmt));
      93          64 :         fmt.count = 1;
      94          64 :         fmt.datatype = CS_TEXT_TYPE;
      95          64 :         fmt.maxlength = MAX_BLOB;
      96          64 :         check_call(blk_bind, (blkdesc, 2, &fmt, text_deferred ? NULL : text_data, &text_len, &text_ind));
      97             : 
      98          64 :         memset(&fmt, 0, sizeof(fmt));
      99          64 :         fmt.count = 1;
     100          64 :         fmt.datatype = CS_IMAGE_TYPE;
     101          64 :         fmt.maxlength = MAX_BLOB;
     102          64 :         check_call(blk_bind, (blkdesc, 3, &fmt, NULL, &image_len, &image_ind));
     103             : 
     104          64 :         memset(&fmt, 0, sizeof(fmt));
     105          64 :         fmt.count = 1;
     106             :         fmt.datatype = CS_CHAR_TYPE;
     107          64 :         fmt.maxlength = sizeof(var_data) - 1;
     108          64 :         check_call(blk_bind, (blkdesc, 4, &fmt, var_data, &var_len, NULL));
     109          64 : }
     110             : 
     111             : /* set bound variables for a row, length -1 means NULL */
     112             : static void
     113         112 : set_row(int id, int tlen, int ilen)
     114             : {
     115         144 :         row_id = id;
     116         144 :         text_len = tlen < 0 ? 0 : tlen;
     117         144 :         text_ind = tlen < 0 ? -1 : 0;
     118         144 :         image_len = ilen < 0 ? 0 : ilen;
     119         144 :         image_ind = ilen < 0 ? -1 : 0;
     120         144 :         var_len = sprintf(var_data, "v%d", id);
     121         112 :         if (tlen > 0)
     122             :                 fill_blob(text_data, tlen, id, 2);
     123         112 : }
     124             : 
     125             : static void
     126         576 : text_xfer(const CS_BYTE *buf, int len, CS_RETCODE expected_ret)
     127             : {
     128             :         CS_RETCODE ret;
     129         576 :         CS_INT outlen = 0;
     130             : 
     131         576 :         ret = blk_textxfer(blkdesc, (CS_BYTE *) buf, len, &outlen);
     132         576 :         if (ret != expected_ret) {
     133           0 :                 fprintf(stderr, "blk_textxfer(%d) returned %d (%s), expected %d (%s)\n",
     134             :                         len, (int) ret, res_type_str(ret), (int) expected_ret, res_type_str(expected_ret));
     135           0 :                 exit(1);
     136             :         }
     137         576 : }
     138             : 
     139             : /* send a column using blk_textxfer in chunks of given size */
     140             : static void
     141         160 : send_blob(int id, int col, int len, int chunk)
     142             : {
     143             :         int pos;
     144             : 
     145             :         fill_blob(blob_buf, len, id, col);
     146         560 :         for (pos = 0; pos < len; pos += chunk) {
     147         560 :                 int n = len - pos < chunk ? len - pos : chunk;
     148             : 
     149         560 :                 text_xfer(blob_buf + pos, n, pos + n >= len ? CS_END_DATA : CS_SUCCEED);
     150             :         }
     151         160 : }
     152             : 
     153             : static void
     154          64 : done(CS_INT type, CS_INT expected_rows)
     155             : {
     156          64 :         CS_INT rows = -1;
     157             : 
     158          64 :         check_call(blk_done, (blkdesc, type, &rows));
     159          64 :         if (rows != expected_rows) {
     160           0 :                 fprintf(stderr, "blk_done(%d) returned %d rows, expected %d\n", (int) type, (int) rows, (int) expected_rows);
     161           0 :                 exit(1);
     162             :         }
     163          64 : }
     164             : 
     165             : static void
     166         256 : check_blob(int id, int col, const CS_BYTE *data, CS_INT len, CS_SMALLINT ind, int expected_len)
     167             : {
     168         256 :         if (expected_len < 0) {
     169          80 :                 if (ind != -1) {
     170           0 :                         fprintf(stderr, "row %d column %d: expected NULL\n", id, col);
     171           0 :                         exit(1);
     172             :                 }
     173             :                 return;
     174             :         }
     175         176 :         if (ind == -1 || len != expected_len) {
     176           0 :                 fprintf(stderr, "row %d column %d: got length %d (ind %d), expected %d\n", id, col, (int) len, (int) ind,
     177             :                         expected_len);
     178           0 :                 exit(1);
     179             :         }
     180         176 :         fill_blob(blob_buf, expected_len, id, col);
     181         176 :         if (memcmp(data, blob_buf, expected_len) != 0) {
     182           0 :                 fprintf(stderr, "row %d column %d: wrong data\n", id, col);
     183           0 :                 exit(1);
     184             :         }
     185             : }
     186             : 
     187             : /* check table content against expected rows, also checks connection is usable */
     188             : static void
     189          64 : check_table(void)
     190             : {
     191             :         char sql[128];
     192             :         CS_DATAFMT fmt;
     193             :         CS_INT result_type, count, id, tlen, ilen, vlen;
     194             :         CS_SMALLINT tind, iind, vind;
     195             :         static CS_BYTE tdata[MAX_BLOB], idata[MAX_BLOB];
     196             :         char vdata[11];
     197             :         CS_RETCODE ret;
     198          64 :         int num_rows = 0;
     199             : 
     200          64 :         sprintf(sql, "select id, t, i, v from %s order by id", table_name);
     201          64 :         check_call(ct_command, (cmd, CS_LANG_CMD, sql, CS_NULLTERM, CS_UNUSED));
     202          64 :         check_call(ct_send, (cmd));
     203          64 :         while ((ret = ct_results(cmd, &result_type)) == CS_SUCCEED) {
     204         128 :                 switch (result_type) {
     205          64 :                 case CS_ROW_RESULT:
     206          64 :                         memset(&fmt, 0, sizeof(fmt));
     207          64 :                         fmt.count = 1;
     208          64 :                         fmt.datatype = CS_INT_TYPE;
     209          64 :                         fmt.maxlength = sizeof(id);
     210          64 :                         check_call(ct_bind, (cmd, 1, &fmt, &id, NULL, NULL));
     211          64 :                         fmt.datatype = CS_TEXT_TYPE;
     212          64 :                         fmt.maxlength = MAX_BLOB;
     213          64 :                         check_call(ct_bind, (cmd, 2, &fmt, tdata, &tlen, &tind));
     214          64 :                         fmt.datatype = CS_IMAGE_TYPE;
     215          64 :                         check_call(ct_bind, (cmd, 3, &fmt, idata, &ilen, &iind));
     216          64 :                         fmt.datatype = CS_CHAR_TYPE;
     217          64 :                         fmt.maxlength = sizeof(vdata) - 1;
     218          64 :                         check_call(ct_bind, (cmd, 4, &fmt, vdata, &vlen, &vind));
     219         256 :                         while ((ret = ct_fetch(cmd, CS_UNUSED, CS_UNUSED, CS_UNUSED, &count)) == CS_SUCCEED) {
     220             :                                 const expected_row *row;
     221             :                                 char expected_var[16];
     222             : 
     223         128 :                                 if (num_rows >= num_expected) {
     224           0 :                                         fprintf(stderr, "too many rows in table\n");
     225           0 :                                         exit(1);
     226             :                                 }
     227         128 :                                 row = &expected[num_rows++];
     228         128 :                                 if (id != row->id) {
     229           0 :                                         fprintf(stderr, "got row id %d, expected %d\n", (int) id, row->id);
     230           0 :                                         exit(1);
     231             :                                 }
     232         128 :                                 check_blob(id, 2, tdata, tlen, tind, row->text_len);
     233         128 :                                 check_blob(id, 3, idata, ilen, iind, row->image_len);
     234         128 :                                 sprintf(expected_var, "v%d", (int) id);
     235         128 :                                 if (vind == -1 || vlen != (CS_INT) strlen(expected_var) || memcmp(vdata, expected_var, vlen) != 0) {
     236           0 :                                         fprintf(stderr, "row %d: wrong varchar column\n", (int) id);
     237           0 :                                         exit(1);
     238             :                                 }
     239             :                         }
     240          64 :                         assert(ret == CS_END_DATA);
     241             :                         break;
     242             :                 case CS_CMD_SUCCEED:
     243             :                 case CS_CMD_DONE:
     244             :                         break;
     245           0 :                 default:
     246           0 :                         fprintf(stderr, "unexpected result type %d\n", (int) result_type);
     247           0 :                         exit(1);
     248             :                 }
     249             :         }
     250          64 :         assert(ret == CS_END_RESULTS);
     251          64 :         if (num_rows != num_expected) {
     252           0 :                 fprintf(stderr, "got %d rows, expected %d\n", num_rows, num_expected);
     253           0 :                 exit(1);
     254             :         }
     255          64 :         printf("table content correct, %d rows\n", num_rows);
     256          64 : }
     257             : 
     258             : static void
     259          48 : clear_table(void)
     260             : {
     261             :         char sql[128];
     262             : 
     263          48 :         sprintf(sql, "delete from %s", table_name);
     264          48 :         check_call(run_command, (cmd, sql));
     265          48 :         num_expected = 0;
     266          48 : }
     267             : 
     268             : static void
     269          16 : test_chunks(void)
     270             : {
     271          16 :         printf("multiple rows and chunks\n");
     272          16 :         start_in(true);
     273             : 
     274          16 :         set_row(1, 2500, 1500);
     275          16 :         check_call(blk_rowxfer, (blkdesc));
     276          16 :         send_blob(1, 2, 2500, 1000);
     277          16 :         send_blob(1, 3, 1500, 1000);
     278          16 :         add_expected(1, 2500, 1500);
     279             : 
     280             :         /* large data, more packets needed */
     281          16 :         set_row(2, 70000, 40000);
     282          16 :         check_call(blk_rowxfer, (blkdesc));
     283          16 :         send_blob(2, 2, 70000, 7000);
     284          16 :         send_blob(2, 3, 40000, 4096);
     285          16 :         add_expected(2, 70000, 40000);
     286          16 :         done(CS_BLK_BATCH, 2);
     287             : 
     288             :         /* single chunk */
     289          16 :         set_row(3, 10, 20);
     290          16 :         check_call(blk_rowxfer, (blkdesc));
     291          16 :         send_blob(3, 2, 10, 10);
     292          16 :         send_blob(3, 3, 20, 20);
     293          16 :         add_expected(3, 10, 20);
     294          16 :         done(CS_BLK_ALL, 1);
     295             : 
     296          16 :         check_table();
     297          16 :         clear_table();
     298          16 : }
     299             : 
     300             : static void
     301          16 : test_nulls(void)
     302             : {
     303          16 :         printf("NULL values\n");
     304          16 :         start_in(true);
     305             : 
     306             :         /* NULL values do not require blk_textxfer */
     307          16 :         set_row(1, -1, 10);
     308          16 :         check_call(blk_rowxfer, (blkdesc));
     309          16 :         send_blob(1, 3, 10, 10);
     310          16 :         add_expected(1, -1, 10);
     311             : 
     312          16 :         set_row(2, -1, -1);
     313          16 :         check_call(blk_rowxfer, (blkdesc));
     314          16 :         add_expected(2, -1, -1);
     315             : 
     316          16 :         set_row(3, 5, -1);
     317          16 :         check_call(blk_rowxfer, (blkdesc));
     318          16 :         send_blob(3, 2, 5, 5);
     319             :         /* no more columns to send */
     320          16 :         check_fail(blk_textxfer, (blkdesc, blob_buf, 5, NULL));
     321          16 :         check_last_message(CTMSG_CLIENT, BLK_TOO_MANY_TEXTXFER, NULL);
     322          16 :         add_expected(3, 5, -1);
     323             : 
     324          16 :         done(CS_BLK_ALL, 3);
     325             : 
     326          16 :         check_table();
     327          16 :         clear_table();
     328          16 : }
     329             : 
     330             : static void
     331          16 : test_mixed(void)
     332             : {
     333          16 :         printf("text bound, image using blk_textxfer\n");
     334          16 :         start_in(false);
     335             : 
     336          16 :         set_row(1, 3000, 2000);
     337          16 :         check_call(blk_rowxfer, (blkdesc));
     338          16 :         send_blob(1, 3, 2000, 900);
     339          16 :         add_expected(1, 3000, 2000);
     340             : 
     341          16 :         set_row(2, 100, -1);
     342          16 :         check_call(blk_rowxfer, (blkdesc));
     343          16 :         add_expected(2, 100, -1);
     344             : 
     345          16 :         done(CS_BLK_ALL, 2);
     346             : 
     347          16 :         check_table();
     348          16 :         clear_table();
     349          16 : }
     350             : 
     351             : static void
     352          16 : test_errors(void)
     353             : {
     354          16 :         printf("wrong calls\n");
     355          16 :         start_in(true);
     356             : 
     357             :         /* no row sent yet */
     358          16 :         check_fail(blk_textxfer, (blkdesc, blob_buf, 10, NULL));
     359          16 :         check_last_message(CTMSG_CLIENT, BLK_STATE_ERROR, NULL);
     360             : 
     361          16 :         set_row(1, 10, 20);
     362          16 :         check_call(blk_rowxfer, (blkdesc));
     363             : 
     364             :         /* more data than declared, nothing is sent */
     365          16 :         check_fail(blk_textxfer, (blkdesc, blob_buf, 15, NULL));
     366          16 :         check_last_message(CTMSG_CLIENT, BLK_TOO_MUCH_DATA, NULL);
     367          16 :         send_blob(1, 2, 10, 4);
     368          16 :         add_expected(1, 10, 20);
     369             : 
     370             :         /* still data to send for the row */
     371          16 :         fill_blob(blob_buf, 20, 1, 3);
     372          16 :         text_xfer(blob_buf, 5, CS_SUCCEED);
     373          16 :         check_fail(blk_rowxfer, (blkdesc));
     374          16 :         check_last_message(CTMSG_CLIENT, BLK_STATE_ERROR, NULL);
     375          16 :         check_fail(blk_done, (blkdesc, CS_BLK_ALL, NULL));
     376          16 :         check_last_message(CTMSG_CLIENT, BLK_STATE_ERROR, NULL);
     377             : 
     378             :         /* cancel discards everything */
     379          16 :         check_call(blk_done, (blkdesc, CS_BLK_CANCEL, NULL));
     380          16 :         num_expected = 0;
     381          16 :         check_table();
     382          16 : }
     383             : 
     384             : static void
     385          20 : run_tests(const char *create_sql)
     386             : {
     387             :         char sql[512];
     388             : 
     389          20 :         sprintf(sql, "if exists (select 1 from sysobjects where type = 'U' and name = '%s') drop table %s", table_name, table_name);
     390          20 :         check_call(run_command, (cmd, sql));
     391             : 
     392          20 :         sprintf(sql, create_sql, table_name);
     393          20 :         if (run_command(cmd, sql) != CS_SUCCEED) {
     394           4 :                 printf("table not supported, skipping\n");
     395           4 :                 return;
     396             :         }
     397          16 :         printf("testing with %s\n", sql);
     398             : 
     399          16 :         test_chunks();
     400          16 :         test_nulls();
     401          16 :         test_mixed();
     402          16 :         test_errors();
     403             : 
     404          16 :         sprintf(sql, "drop table %s", table_name);
     405          16 :         check_call(run_command, (cmd, sql));
     406             : }
     407             : 
     408          10 : TEST_MAIN()
     409             : {
     410             :         CS_CONTEXT *ctx;
     411          10 :         bool verbose = false;
     412             : 
     413          10 :         printf("%s: Testing blk_textxfer\n", __FILE__);
     414          10 :         check_call(try_ctlogin, (&ctx, &conn, &cmd, verbose));
     415             : 
     416          10 :         check_call(run_command, (cmd, "set textsize 1000000"));
     417             : 
     418          10 :         run_tests("create table %s (id int not null, t text null, i image null, v varchar(10) null)");
     419             :         /* MSSQL 2005+, sent as PLP using TDS 7.2+ */
     420          10 :         run_tests("create table %s (id int not null, t varchar(max) null, i varbinary(max) null, v varchar(10) null)");
     421             : 
     422          10 :         if (blkdesc)
     423          10 :                 blk_drop(blkdesc);
     424             : 
     425          10 :         printf("done\n");
     426             : 
     427          10 :         check_call(try_ctlogout, (ctx, conn, cmd, verbose));
     428             : 
     429          10 :         return 0;
     430             : }

Generated by: LCOV version 1.13