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 : }
|