Line data Source code
1 : /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2 : * Copyright (C) 2008-2010 Frediano Ziglio
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Library General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Library General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Library General Public
15 : * License along with this library; if not, write to the
16 : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 : * Boston, MA 02111-1307, USA.
18 : */
19 :
20 : /**
21 : * \file
22 : * \brief Handle bulk copy
23 : */
24 :
25 : #include <config.h>
26 :
27 : #if HAVE_STRING_H
28 : #include <string.h>
29 : #endif /* HAVE_STRING_H */
30 :
31 : #if HAVE_ERRNO_H
32 : #include <errno.h>
33 : #endif /* HAVE_ERRNO_H */
34 :
35 : #if HAVE_STDLIB_H
36 : #include <stdlib.h>
37 : #endif /* HAVE_STDLIB_H */
38 :
39 : #include <assert.h>
40 :
41 : #include <freetds/tds.h>
42 : #include <freetds/tds/checks.h>
43 : #include <freetds/bytes.h>
44 : #include <freetds/tds/iconv.h>
45 : #include <freetds/tds/stream.h>
46 : #include <freetds/tds/convert.h>
47 : #include <freetds/utils/string.h>
48 : #include <freetds/utils/ascii.h>
49 : #include <freetds/replacements.h>
50 : #include <freetds/enum_cap.h>
51 :
52 : /**
53 : * Holds clause buffer
54 : */
55 : typedef struct tds_pbcb
56 : {
57 : /** buffer */
58 : char *pb;
59 : /** buffer length */
60 : unsigned int cb;
61 : /** true is buffer came from malloc */
62 : bool from_malloc;
63 : } TDSPBCB;
64 :
65 : static TDSRET tds7_bcp_send_colmetadata(TDSSOCKET *tds, TDSBCPINFO *bcpinfo);
66 : static TDSRET tds_bcp_start_insert_stmt(TDSSOCKET *tds, TDSBCPINFO *bcpinfo);
67 : static int tds5_bcp_add_fixed_columns(TDSBCPINFO *bcpinfo, tds_bcp_get_col_data get_col_data, tds_bcp_null_error null_error,
68 : int offset, unsigned char * rowbuffer, int start);
69 : static int tds5_bcp_add_variable_columns(TDSBCPINFO *bcpinfo, tds_bcp_get_col_data get_col_data, tds_bcp_null_error null_error,
70 : int offset, TDS_UCHAR *rowbuffer, int start, int *pncols);
71 : static void tds_bcp_row_free(TDSRESULTINFO* result, unsigned char *row);
72 : static TDSRET tds5_process_insert_bulk_reply(TDSSOCKET * tds, TDSBCPINFO *bcpinfo);
73 : static TDSRET probe_sap_locking(TDSSOCKET *tds, TDSBCPINFO *bcpinfo);
74 : static TDSRET tds5_get_col_data_or_dflt(tds_bcp_get_col_data get_col_data,
75 : TDSBCPINFO * bulk, TDSCOLUMN * bcpcol, int offset, int colnum);
76 :
77 : /**
78 : * Initialize BCP information.
79 : * Query structure of the table to server.
80 : * \tds
81 : * \param bcpinfo BCP information to initialize. Structure should be allocate
82 : * and table name and direction should be already set.
83 : */
84 : TDSRET
85 532 : tds_bcp_init(TDSSOCKET *tds, TDSBCPINFO *bcpinfo)
86 : {
87 : TDSRESULTINFO *resinfo;
88 532 : TDSRESULTINFO *bindinfo = NULL;
89 : TDSCOLUMN *curcol;
90 : TDS_INT result_type;
91 : int i;
92 : TDSRET rc;
93 : const char *fmt;
94 :
95 : /* FIXME don't leave state in processing state */
96 :
97 : /* Check table locking type. Do this first, because the code in bcp_init() which
98 : * calls us seems to depend on the information from the columns query still being
99 : * active in the context.
100 : */
101 532 : probe_sap_locking(tds, bcpinfo);
102 :
103 : /* TODO quote tablename if needed */
104 532 : if (bcpinfo->direction != TDS_BCP_QUERYOUT)
105 : fmt = "SET FMTONLY ON select * from %s SET FMTONLY OFF";
106 : else
107 10 : fmt = "SET FMTONLY ON %s SET FMTONLY OFF";
108 :
109 1064 : if (TDS_FAILED(rc=tds_submit_queryf(tds, fmt, tds_dstr_cstr(&bcpinfo->tablename))))
110 : /* TODO return an error ?? */
111 : /* Attempt to use Bulk Copy with a non-existent Server table (might be why ...) */
112 : return rc;
113 :
114 : /* TODO possibly stop at ROWFMT and copy before going to idle */
115 : /* TODO check what happen if table is not present, cleanup on error */
116 2660 : while ((rc = tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_RESULTS))
117 : == TDS_SUCCESS)
118 2128 : continue;
119 532 : TDS_PROPAGATE(rc);
120 :
121 : /* copy the results info from the TDS socket */
122 532 : if (!tds->res_info)
123 : return TDS_FAIL;
124 :
125 532 : resinfo = tds->res_info;
126 532 : if ((bindinfo = tds_alloc_results(resinfo->num_cols)) == NULL) {
127 : rc = TDS_FAIL;
128 : goto cleanup;
129 : }
130 :
131 532 : bindinfo->row_size = resinfo->row_size;
132 :
133 : /* Copy the column metadata */
134 532 : rc = TDS_FAIL;
135 3900 : for (i = 0; i < bindinfo->num_cols; i++) {
136 :
137 3368 : curcol = bindinfo->columns[i];
138 :
139 : /*
140 : * TODO use memcpy ??
141 : * curcol and resinfo->columns[i] are both TDSCOLUMN.
142 : * Why not "curcol = resinfo->columns[i];"? Because the rest of TDSCOLUMN (below column_timestamp)
143 : * isn't being used. Perhaps this "upper" part of TDSCOLUMN should be a substructure.
144 : * Or, see if the "lower" part is unused (and zeroed out) at this point, and just do one assignment.
145 : */
146 3368 : curcol->funcs = resinfo->columns[i]->funcs;
147 3368 : curcol->column_type = resinfo->columns[i]->column_type;
148 3368 : curcol->column_usertype = resinfo->columns[i]->column_usertype;
149 3368 : curcol->column_flags = resinfo->columns[i]->column_flags;
150 3368 : if (curcol->column_varint_size == 0)
151 3368 : curcol->column_cur_size = resinfo->columns[i]->column_cur_size;
152 : else
153 0 : curcol->column_cur_size = -1;
154 3368 : curcol->column_size = resinfo->columns[i]->column_size;
155 3368 : curcol->column_varint_size = resinfo->columns[i]->column_varint_size;
156 3368 : curcol->column_prec = resinfo->columns[i]->column_prec;
157 3368 : curcol->column_scale = resinfo->columns[i]->column_scale;
158 3368 : curcol->on_server = resinfo->columns[i]->on_server;
159 3368 : curcol->char_conv = resinfo->columns[i]->char_conv;
160 3368 : if (!tds_dstr_dup(&curcol->column_name, &resinfo->columns[i]->column_name))
161 : goto cleanup;
162 3368 : if (!tds_dstr_dup(&curcol->table_column_name, &resinfo->columns[i]->table_column_name))
163 : goto cleanup;
164 3368 : curcol->column_nullable = resinfo->columns[i]->column_nullable;
165 3368 : curcol->column_identity = resinfo->columns[i]->column_identity;
166 3368 : curcol->column_timestamp = resinfo->columns[i]->column_timestamp;
167 3368 : curcol->column_computed = resinfo->columns[i]->column_computed;
168 :
169 3368 : memcpy(curcol->column_collation, resinfo->columns[i]->column_collation, 5);
170 3368 : curcol->use_iconv_out = 0;
171 :
172 : /* From MS documentation:
173 : * Note that for INSERT BULK operations, XMLTYPE is to be sent as NVARCHAR(N) or NVARCHAR(MAX)
174 : * data type. An error is produced if XMLTYPE is specified.
175 : */
176 3368 : if (curcol->on_server.column_type == SYBMSXML) {
177 8 : curcol->on_server.column_type = XSYBNVARCHAR;
178 8 : curcol->column_type = SYBVARCHAR;
179 8 : memcpy(curcol->column_collation, tds->conn->collation, 5);
180 : }
181 :
182 3368 : if (is_numeric_type(curcol->column_type)) {
183 232 : curcol->bcp_column_data = tds_alloc_bcp_column_data(sizeof(TDS_NUMERIC));
184 232 : ((TDS_NUMERIC *) curcol->bcp_column_data->data)->precision = curcol->column_prec;
185 232 : ((TDS_NUMERIC *) curcol->bcp_column_data->data)->scale = curcol->column_scale;
186 : } else {
187 3136 : curcol->bcp_column_data =
188 3136 : tds_alloc_bcp_column_data(TDS_MAX(curcol->column_size,curcol->on_server.column_size));
189 : }
190 3368 : if (!curcol->bcp_column_data)
191 : goto cleanup;
192 : }
193 :
194 532 : if (!IS_TDS7_PLUS(tds->conn)) {
195 92 : bindinfo->current_row = tds_new(unsigned char, bindinfo->row_size);
196 92 : if (!bindinfo->current_row)
197 : goto cleanup;
198 92 : bindinfo->row_free = tds_bcp_row_free;
199 : }
200 :
201 532 : if (bcpinfo->identity_insert_on) {
202 :
203 0 : rc = tds_submit_queryf(tds, "set identity_insert %s on", tds_dstr_cstr(&bcpinfo->tablename));
204 0 : if (TDS_FAILED(rc))
205 : goto cleanup;
206 :
207 : /* TODO use tds_process_simple_query */
208 0 : while ((rc = tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_RESULTS))
209 : == TDS_SUCCESS) {
210 : }
211 0 : if (rc != TDS_NO_MORE_RESULTS)
212 : goto cleanup;
213 : }
214 :
215 532 : bcpinfo->bindinfo = bindinfo;
216 532 : bcpinfo->bind_count = 0;
217 :
218 532 : return TDS_SUCCESS;
219 :
220 0 : cleanup:
221 0 : tds_free_results(bindinfo);
222 0 : return rc;
223 : }
224 :
225 : /**
226 : * Detect if table we're writing to uses 'datarows' lockmode.
227 : * \tds
228 : * \param bcpinfo BCP information already prepared
229 : * \return TDS_SUCCESS or TDS_FAIL.
230 : */
231 : static TDSRET
232 532 : probe_sap_locking(TDSSOCKET *tds, TDSBCPINFO *bcpinfo)
233 : {
234 : TDSRET rc;
235 : unsigned int value;
236 : bool value_found;
237 : TDS_INT resulttype;
238 : const char *full_tablename, *rdot, *tablename;
239 :
240 : /* Only needed for inward data */
241 532 : if (bcpinfo->direction != TDS_BCP_IN)
242 : return TDS_SUCCESS;
243 :
244 : /* Only needed for SAP ASE versions which support datarows-locking. */
245 410 : if (!TDS_IS_SYBASE(tds) || !tds_capability_has_req(tds->conn, TDS_REQ_DOL_BULK))
246 : return TDS_SUCCESS;
247 :
248 : /* A request to probe database.owner.tablename needs to check database.owner.sysobjects for tablename
249 : * (it doesn't work to check sysobjects for database.owner.tablename) */
250 124 : full_tablename = tds_dstr_cstr(&bcpinfo->tablename);
251 62 : rdot = strrchr(full_tablename, '.');
252 :
253 62 : if (rdot != NULL)
254 2 : tablename = rdot + 1;
255 : else
256 : tablename = full_tablename;
257 :
258 62 : TDS_PROPAGATE(tds_submit_queryf(tds, "select sysstat2 from %.*ssysobjects where type='U' and name='%s'",
259 : (int) (rdot ? (rdot - full_tablename + 1) : 0), full_tablename, tablename));
260 :
261 : value = 0;
262 : value_found = false;
263 :
264 186 : while ((rc = tds_process_tokens(tds, &resulttype, NULL, TDS_TOKEN_RESULTS)) == TDS_SUCCESS) {
265 124 : const unsigned int stop_mask = TDS_RETURN_DONE | TDS_RETURN_ROW;
266 :
267 124 : if (resulttype != TDS_ROW_RESULT)
268 88 : continue;
269 :
270 : /* We must keep processing result tokens (even if we've found what we're looking for) so that the
271 : * stream is ready for subsequent queries. */
272 72 : while ((rc = tds_process_tokens(tds, &resulttype, NULL, stop_mask)) == TDS_SUCCESS) {
273 : TDSCOLUMN *col;
274 : TDS_SERVER_TYPE ctype;
275 : CONV_RESULT dres;
276 : TDS_INT res;
277 :
278 72 : if (resulttype != TDS_ROW_RESULT)
279 : break;
280 :
281 : /* Get INT4 from column 0 */
282 36 : if (!tds->current_results || tds->current_results->num_cols < 1)
283 0 : continue;
284 :
285 36 : col = tds->current_results->columns[0];
286 36 : if (col->column_cur_size < 0)
287 0 : continue;
288 :
289 36 : ctype = tds_get_conversion_type(col->column_type, col->column_size);
290 36 : res = tds_convert(tds_get_ctx(tds), ctype, col->column_data, col->column_cur_size, SYBINT4, &dres);
291 36 : if (res < 0)
292 0 : continue;
293 :
294 36 : value = dres.i;
295 36 : value_found = true;
296 : }
297 : }
298 62 : TDS_PROPAGATE(rc);
299 :
300 : /* No valid result - Treat this as meaning the feature is lacking; it could be an old Sybase version for example */
301 62 : if (!value_found) {
302 26 : tdsdump_log(TDS_DBG_INFO1, "[DOL BULK] No valid result returned by probe.\n");
303 : return TDS_SUCCESS;
304 : }
305 :
306 : /* Log and analyze result.
307 : * Sysstat2 flag values:
308 : * https://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.help.ase.16.0/doc/html/title.html
309 : * 0x08000 - datarows locked
310 : * 0x20000 - clustered index present. (Not recommended for performance reasons to datarows-lock with clustered index) */
311 36 : tdsdump_log(TDS_DBG_INFO1, "%x = sysstat2 for '%s'", value, full_tablename);
312 :
313 36 : if (0x8000 & value) {
314 2 : bcpinfo->datarows_locking = true;
315 2 : tdsdump_log(TDS_DBG_INFO1, "Table has datarows-locking; enabling DOL BULK format.\n");
316 :
317 2 : if (0x20000 & value)
318 0 : tdsdump_log(TDS_DBG_WARN, "Table also has clustered index: bulk insert performance may be degraded.\n");
319 : }
320 : return TDS_SUCCESS;
321 : }
322 :
323 : /**
324 : * Help to build query to be sent to server.
325 : * Append column declaration to the query.
326 : * Only for TDS 7.0+.
327 : * \tds
328 : * \param[out] clause output string
329 : * \param bcpcol column to append
330 : * \param first true if column is the first
331 : * \return TDS_SUCCESS or TDS_FAIL.
332 : */
333 : static TDSRET
334 1776 : tds7_build_bulk_insert_stmt(TDSSOCKET * tds, TDSPBCB * clause, TDSCOLUMN * bcpcol, int first)
335 : {
336 : char column_type[128];
337 :
338 1776 : tdsdump_log(TDS_DBG_FUNC, "tds7_build_bulk_insert_stmt(%p, %p, %p, %d)\n", tds, clause, bcpcol, first);
339 :
340 1776 : if (TDS_FAILED(tds_get_column_declaration(tds, bcpcol, column_type))) {
341 0 : tdserror(tds_get_ctx(tds), tds, TDSEBPROBADTYP, errno);
342 0 : tdsdump_log(TDS_DBG_FUNC, "error: cannot build bulk insert statement. unrecognized server datatype %d\n",
343 : bcpcol->on_server.column_type);
344 : return TDS_FAIL;
345 : }
346 :
347 1776 : if (IS_TDS71_PLUS(tds->conn) && bcpcol->char_conv) {
348 630 : strcat(column_type, " COLLATE ");
349 630 : strcat(column_type, tds_canonical_collate_name(bcpcol->char_conv->to.charset.canonic));
350 : }
351 :
352 3552 : if (clause->cb < strlen(clause->pb)
353 5328 : + tds_quote_id(tds, NULL, tds_dstr_cstr(&bcpcol->column_name), tds_dstr_len(&bcpcol->column_name))
354 1776 : + strlen(column_type)
355 1776 : + ((first) ? 2u : 4u)) {
356 0 : char *temp = tds_new(char, 2 * clause->cb);
357 :
358 0 : if (!temp) {
359 0 : tdserror(tds_get_ctx(tds), tds, TDSEMEM, errno);
360 0 : return TDS_FAIL;
361 : }
362 0 : strcpy(temp, clause->pb);
363 0 : if (clause->from_malloc)
364 0 : free(clause->pb);
365 0 : clause->from_malloc = true;
366 0 : clause->pb = temp;
367 0 : clause->cb *= 2;
368 : }
369 :
370 1776 : if (!first)
371 1490 : strcat(clause->pb, ", ");
372 :
373 5328 : tds_quote_id(tds, strchr(clause->pb, 0), tds_dstr_cstr(&bcpcol->column_name), tds_dstr_len(&bcpcol->column_name));
374 1776 : strcat(clause->pb, " ");
375 1776 : strcat(clause->pb, column_type);
376 :
377 1776 : return TDS_SUCCESS;
378 : }
379 :
380 : static const char *
381 2 : uc_str(const char *s)
382 : {
383 2 : size_t n = strcspn(s, "abcdefghijklmnopqrstuvwxyz");
384 :
385 2 : if (s[n] == '\0')
386 : return s; /* already all uppercase */
387 :
388 0 : return tds_ascii_strupr(strdup(s));
389 : }
390 :
391 : /**
392 : * Prepare the query to be sent to server to request BCP information
393 : * \tds
394 : * \param bcpinfo BCP information
395 : */
396 : static TDSRET
397 348 : tds_bcp_start_insert_stmt(TDSSOCKET * tds, TDSBCPINFO * bcpinfo)
398 : {
399 : char *query;
400 :
401 348 : if (IS_TDS7_PLUS(tds->conn)) {
402 : int i, firstcol, erc;
403 : char *hint;
404 : TDSCOLUMN *bcpcol;
405 : TDSPBCB colclause;
406 286 : char clause_buffer[4096] = { 0 };
407 572 : bool triggers_checked = tds_dstr_isempty(&bcpinfo->hint); /* Done on demand */
408 :
409 286 : colclause.pb = clause_buffer;
410 286 : colclause.cb = sizeof(clause_buffer);
411 286 : colclause.from_malloc = false;
412 :
413 : /* TODO avoid asprintf, use always malloc-ed buffer */
414 286 : firstcol = 1;
415 :
416 2062 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
417 1776 : bcpcol = bcpinfo->bindinfo->columns[i];
418 :
419 1776 : if (bcpcol->column_timestamp)
420 0 : continue;
421 1776 : if (!bcpinfo->identity_insert_on && bcpcol->column_identity)
422 0 : continue;
423 1776 : if (bcpcol->column_computed) {
424 2 : if (!triggers_checked) {
425 4 : const char *uc_hint = uc_str(tds_dstr_cstr(&bcpinfo->hint));
426 :
427 2 : if (strstr(uc_hint, "FIRE_TRIGGERS"))
428 2 : bcpinfo->with_triggers = true;
429 :
430 4 : if (uc_hint != tds_dstr_cstr(&bcpinfo->hint))
431 0 : free((char *) uc_hint);
432 :
433 : triggers_checked = true;
434 : }
435 2 : if (!bcpinfo->with_triggers)
436 0 : continue;
437 : }
438 1776 : tds7_build_bulk_insert_stmt(tds, &colclause, bcpcol, firstcol);
439 1776 : firstcol = 0;
440 : }
441 :
442 572 : if (!tds_dstr_isempty(&bcpinfo->hint)) {
443 172 : if (asprintf(&hint, " with (%s)", tds_dstr_cstr(&bcpinfo->hint)) < 0)
444 0 : hint = NULL;
445 : } else {
446 200 : hint = strdup("");
447 : }
448 286 : if (!hint) {
449 0 : if (colclause.from_malloc)
450 0 : TDS_ZERO_FREE(colclause.pb);
451 0 : return TDS_FAIL;
452 : }
453 :
454 572 : erc = asprintf(&query, "insert bulk %s (%s)%s", tds_dstr_cstr(&bcpinfo->tablename), colclause.pb, hint);
455 :
456 286 : free(hint);
457 286 : if (colclause.from_malloc)
458 0 : TDS_ZERO_FREE(colclause.pb); /* just for good measure; not used beyond this point */
459 :
460 286 : if (erc < 0)
461 : return TDS_FAIL;
462 : } else {
463 : /* NOTE: if we use "with nodescribe" for following inserts server do not send describe */
464 124 : if (asprintf(&query, "insert bulk %s", tds_dstr_cstr(&bcpinfo->tablename)) < 0)
465 : return TDS_FAIL;
466 : }
467 :
468 : /* save the statement for later... */
469 348 : bcpinfo->insert_stmt = query;
470 :
471 348 : return TDS_SUCCESS;
472 : }
473 :
474 : static TDSRET
475 898 : tds7_send_record(TDSSOCKET *tds, TDSBCPINFO *bcpinfo,
476 : tds_bcp_get_col_data get_col_data, tds_bcp_null_error null_error, int offset)
477 : {
478 : int i;
479 :
480 898 : tds_put_byte(tds, TDS_ROW_TOKEN); /* 0xd1 */
481 12774 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
482 :
483 : TDS_INT save_size;
484 : unsigned char *save_data;
485 : TDSBLOB blob;
486 : TDSCOLUMN *bindcol;
487 : TDSRET rc;
488 :
489 11892 : bindcol = bcpinfo->bindinfo->columns[i];
490 :
491 : /*
492 : * Don't send the (meta)data for timestamp columns or
493 : * identity columns unless indentity_insert is enabled.
494 : */
495 :
496 11892 : if ((!bcpinfo->identity_insert_on && bindcol->column_identity) || bindcol->column_timestamp
497 11892 : || (bindcol->column_computed && !bcpinfo->with_triggers)) {
498 0 : continue;
499 : }
500 :
501 11892 : rc = get_col_data(bcpinfo, bindcol, i, offset);
502 11892 : if (TDS_FAILED(rc)) {
503 16 : tdsdump_log(TDS_DBG_INFO1, "get_col_data (column %d) failed\n", i + 1);
504 16 : return rc;
505 : }
506 11876 : tdsdump_log(TDS_DBG_INFO1, "gotten column %d length %d null %d\n",
507 : i + 1, bindcol->bcp_column_data->datalen, bindcol->bcp_column_data->is_null);
508 :
509 11876 : save_size = bindcol->column_cur_size;
510 11876 : save_data = bindcol->column_data;
511 11876 : assert(bindcol->column_data == NULL);
512 11876 : if (bindcol->bcp_column_data->is_null) {
513 4544 : if (!bindcol->column_nullable && !is_nullable_type(bindcol->on_server.column_type)) {
514 0 : if (null_error)
515 0 : null_error(bcpinfo, i, offset);
516 : return TDS_FAIL;
517 : }
518 4544 : bindcol->column_cur_size = -1;
519 7332 : } else if (is_blob_col(bindcol)) {
520 108 : bindcol->column_cur_size = bindcol->bcp_column_data->datalen;
521 108 : memset(&blob, 0, sizeof(blob));
522 108 : blob.textvalue = (TDS_CHAR *) bindcol->bcp_column_data->data;
523 108 : bindcol->column_data = (unsigned char *) &blob;
524 : } else {
525 7224 : bindcol->column_cur_size = bindcol->bcp_column_data->datalen;
526 7224 : bindcol->column_data = bindcol->bcp_column_data->data;
527 : }
528 11876 : rc = bindcol->funcs->put_data(tds, bindcol, true);
529 11876 : bindcol->column_cur_size = save_size;
530 11876 : bindcol->column_data = save_data;
531 :
532 11876 : TDS_PROPAGATE(rc);
533 : }
534 : return TDS_SUCCESS;
535 : }
536 :
537 : static TDSRET
538 222 : tds5_send_record(TDSSOCKET *tds, TDSBCPINFO *bcpinfo,
539 : tds_bcp_get_col_data get_col_data, tds_bcp_null_error null_error, int offset)
540 : {
541 222 : int row_pos = 0;
542 : int row_sz_pos;
543 222 : int blob_cols = 0;
544 : int var_cols_pos;
545 222 : int var_cols_written = 0;
546 222 : TDS_INT old_record_size = bcpinfo->bindinfo->row_size;
547 222 : unsigned char *record = bcpinfo->bindinfo->current_row;
548 : int i;
549 :
550 222 : memset(record, '\0', old_record_size); /* zero the rowbuffer */
551 :
552 : /* SAP ASE Datarows-locked tables expect additional 4 blank bytes before everything else */
553 222 : if (bcpinfo->datarows_locking)
554 20 : row_pos += 4;
555 :
556 : /*
557 : * offset 0 = number of var columns
558 : * offset 1 = row number. zeroed (datasever assigns)
559 : */
560 222 : var_cols_pos = row_pos;
561 222 : row_pos += 2;
562 :
563 222 : if ((row_pos = tds5_bcp_add_fixed_columns(bcpinfo, get_col_data, null_error, offset, record, row_pos)) < 0)
564 : return TDS_FAIL;
565 :
566 216 : row_sz_pos = row_pos;
567 :
568 : /* potential variable columns to write */
569 :
570 216 : row_pos = tds5_bcp_add_variable_columns(bcpinfo, get_col_data, null_error, offset, record, row_pos, &var_cols_written);
571 216 : if (row_pos < 0)
572 : return TDS_FAIL;
573 :
574 :
575 216 : if (var_cols_written) {
576 190 : TDS_PUT_UA2LE(&record[row_sz_pos], row_pos);
577 190 : record[var_cols_pos] = var_cols_written;
578 : }
579 :
580 216 : tdsdump_log(TDS_DBG_INFO1, "old_record_size = %d new size = %d \n", old_record_size, row_pos);
581 :
582 216 : tds_put_smallint(tds, row_pos);
583 216 : tds_put_n(tds, record, row_pos);
584 :
585 : /* row is done, now handle any text/image data */
586 :
587 216 : blob_cols = 0;
588 :
589 3622 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
590 3410 : TDSCOLUMN *bindcol = bcpinfo->bindinfo->columns[i];
591 3410 : if (type_has_textptr(bindcol->on_server.column_type)) {
592 : TDSRET rc;
593 : /* Elide trailing NULLs */
594 20 : if (bindcol->bcp_column_data->is_null) {
595 : int j;
596 :
597 8 : for (j = i + 1; j < bcpinfo->bindinfo->num_cols; ++j) {
598 4 : TDSCOLUMN *bindcol2 = bcpinfo->bindinfo->columns[j];
599 :
600 4 : if (type_has_textptr(bindcol2->column_type) && !bindcol2->bcp_column_data->is_null)
601 : break;
602 : }
603 6 : if (j == bcpinfo->bindinfo->num_cols)
604 : break;
605 : }
606 :
607 16 : rc = tds5_get_col_data_or_dflt(get_col_data, bcpinfo, bindcol, offset, i);
608 16 : TDS_PROPAGATE(rc);
609 :
610 : /* unknown but zero */
611 16 : tds_put_smallint(tds, 0);
612 16 : TDS_PUT_BYTE(tds, bindcol->on_server.column_type);
613 16 : tds_put_byte(tds, 0xff - blob_cols);
614 : /*
615 : * offset of txptr we stashed during variable
616 : * column processing
617 : */
618 16 : tds_put_smallint(tds, bindcol->column_textpos);
619 16 : tds_put_int(tds, bindcol->bcp_column_data->datalen);
620 16 : tds_put_n(tds, bindcol->bcp_column_data->data, bindcol->bcp_column_data->datalen);
621 16 : blob_cols++;
622 :
623 : }
624 : }
625 : return TDS_SUCCESS;
626 : }
627 :
628 : /**
629 : * Send one row of data to server
630 : * \tds
631 : * \param bcpinfo BCP information
632 : * \param get_col_data function to call to retrieve data to be sent
633 : * \param ignored function to call if we try to send NULL if not allowed (not used)
634 : * \param offset passed to get_col_data and null_error to specify the row to get
635 : * \return TDS_SUCCESS or TDS_FAIL.
636 : */
637 : TDSRET
638 1120 : tds_bcp_send_record(TDSSOCKET *tds, TDSBCPINFO *bcpinfo,
639 : tds_bcp_get_col_data get_col_data, tds_bcp_null_error null_error, int offset)
640 : {
641 : TDSRET rc;
642 : TDSFREEZE row;
643 :
644 1120 : tdsdump_log(TDS_DBG_FUNC, "tds_bcp_send_bcp_record(%p, %p, %p, %p, %d)\n", tds, bcpinfo, get_col_data, null_error, offset);
645 :
646 1120 : if (tds->out_flag != TDS_BULK || tds_set_state(tds, TDS_WRITING) != TDS_WRITING)
647 : return TDS_FAIL;
648 :
649 1120 : tds_freeze(tds, &row, 0);
650 1120 : if (IS_TDS7_PLUS(tds->conn))
651 898 : rc = tds7_send_record(tds, bcpinfo, get_col_data, null_error, offset);
652 : else
653 222 : rc = tds5_send_record(tds, bcpinfo, get_col_data, null_error, offset);
654 :
655 1120 : if (TDS_SUCCEED(rc)) {
656 1098 : bcpinfo->rows_sent++;
657 1098 : tds_freeze_close(&row);
658 : } else {
659 22 : tds_freeze_abort(&row);
660 : }
661 :
662 1120 : tds_set_state(tds, TDS_SENDING);
663 1120 : return rc;
664 : }
665 :
666 : static inline void
667 : tds5_swap_data(const TDSCOLUMN *col TDS_UNUSED, void *p TDS_UNUSED)
668 : {
669 : #ifdef WORDS_BIGENDIAN
670 : tds_swap_datatype(tds_get_conversion_type(col->on_server.column_type, col->column_size), p);
671 : #endif
672 : }
673 :
674 : /**
675 : * Add fixed size columns to the row
676 : * \param bcpinfo BCP information
677 : * \param get_col_data function to call to retrieve data to be sent
678 : * \param ignored function to call if we try to send NULL if not allowed (not used)
679 : * \param offset passed to get_col_data and null_error to specify the row to get
680 : * \param rowbuffer row buffer to write to
681 : * \param start row buffer last end position
682 : * \returns new row length or -1 on error.
683 : */
684 : static int
685 222 : tds5_bcp_add_fixed_columns(TDSBCPINFO *bcpinfo, tds_bcp_get_col_data get_col_data, tds_bcp_null_error null_error,
686 : int offset, unsigned char * rowbuffer, int start)
687 : {
688 : TDS_NUMERIC *num;
689 222 : int row_pos = start;
690 : int cpbytes;
691 : int i;
692 222 : int bitleft = 0, bitpos = 0;
693 :
694 222 : assert(bcpinfo);
695 222 : assert(rowbuffer);
696 :
697 222 : tdsdump_log(TDS_DBG_FUNC, "tds5_bcp_add_fixed_columns(%p, %p, %p, %d, %p, %d)\n",
698 : bcpinfo, get_col_data, null_error, offset, rowbuffer, start);
699 :
700 3414 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
701 :
702 3420 : TDSCOLUMN *const bcpcol = bcpinfo->bindinfo->columns[i];
703 3420 : const TDS_INT column_size = bcpcol->on_server.column_size;
704 :
705 : /* if possible check information from server */
706 3420 : if (bcpinfo->sybase_count > i) {
707 3420 : if (bcpinfo->sybase_colinfo[i].offset < 0)
708 1836 : continue;
709 : } else {
710 0 : if (is_nullable_type(bcpcol->on_server.column_type) || bcpcol->column_nullable)
711 0 : continue;
712 : }
713 :
714 1584 : tdsdump_log(TDS_DBG_FUNC, "tds5_bcp_add_fixed_columns column %d (%s) is a fixed column\n", i + 1,
715 : tds_dstr_cstr(&bcpcol->column_name));
716 :
717 1584 : if (TDS_FAILED(tds5_get_col_data_or_dflt(get_col_data, bcpinfo, bcpcol, offset, i))) {
718 6 : tdsdump_log(TDS_DBG_INFO1, "get_col_data (column %d) failed\n", i + 1);
719 : return -1;
720 : }
721 :
722 : /* We have no way to send a NULL at this point, return error to client */
723 1578 : if (bcpcol->bcp_column_data->is_null) {
724 0 : tdsdump_log(TDS_DBG_ERROR, "tds5_bcp_add_fixed_columns column %d is a null column\n", i + 1);
725 : /* No value or default value available and NULL not allowed. */
726 0 : if (null_error)
727 0 : null_error(bcpinfo, i, offset);
728 : return -1;
729 : }
730 :
731 1578 : if (is_numeric_type(bcpcol->on_server.column_type)) {
732 220 : num = (TDS_NUMERIC *) bcpcol->bcp_column_data->data;
733 220 : cpbytes = tds_numeric_bytes_per_prec[num->precision];
734 220 : memcpy(&rowbuffer[row_pos], num->array, cpbytes);
735 1358 : } else if (bcpcol->column_type == SYBBIT) {
736 : /* all bit are collapsed together */
737 178 : if (!bitleft) {
738 126 : bitpos = row_pos++;
739 126 : bitleft = 8;
740 126 : rowbuffer[bitpos] = 0;
741 : }
742 178 : if (bcpcol->bcp_column_data->data[0])
743 146 : rowbuffer[bitpos] |= 256 >> bitleft;
744 178 : --bitleft;
745 178 : continue;
746 : } else {
747 1180 : cpbytes = bcpcol->bcp_column_data->datalen > column_size ?
748 : column_size : bcpcol->bcp_column_data->datalen;
749 1180 : memcpy(&rowbuffer[row_pos], bcpcol->bcp_column_data->data, cpbytes);
750 1180 : tds5_swap_data(bcpcol, &rowbuffer[row_pos]);
751 :
752 : /* CHAR data may need padding out to the database length with blanks */
753 : /* TODO check binary !!! */
754 1180 : if (bcpcol->column_type == SYBCHAR && cpbytes < column_size)
755 118 : memset(rowbuffer + row_pos + cpbytes, ' ', column_size - cpbytes);
756 : }
757 :
758 1400 : row_pos += column_size;
759 : }
760 : return row_pos;
761 : }
762 :
763 : /**
764 : * Add variable size columns to the row
765 : *
766 : * \param bcpinfo BCP information already prepared
767 : * \param get_col_data function to call to retrieve data to be sent
768 : * \param null_error function to call if we try to send NULL if not allowed
769 : * \param offset passed to get_col_data and null_error to specify the row to get
770 : * \param rowbuffer The row image that will be sent to the server.
771 : * \param start Where to begin copying data into the rowbuffer.
772 : * \param pncols Address of output variable holding the count of columns added to the rowbuffer.
773 : *
774 : * \return length of (potentially modified) rowbuffer, or -1.
775 : */
776 : static int
777 216 : tds5_bcp_add_variable_columns(TDSBCPINFO *bcpinfo, tds_bcp_get_col_data get_col_data, tds_bcp_null_error null_error,
778 : int offset, TDS_UCHAR* rowbuffer, int start, int *pncols)
779 : {
780 : TDS_USMALLINT offsets[256];
781 : unsigned int i, row_pos;
782 216 : unsigned int ncols = 0;
783 :
784 216 : assert(bcpinfo);
785 216 : assert(rowbuffer);
786 216 : assert(pncols);
787 :
788 216 : tdsdump_log(TDS_DBG_FUNC, "%4s %8s %18s %18s %8s\n", "col",
789 : "type",
790 : "is_nullable_type",
791 : "column_nullable",
792 : "is null" );
793 3412 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
794 3412 : TDSCOLUMN *bcpcol = bcpinfo->bindinfo->columns[i];
795 3412 : tdsdump_log(TDS_DBG_FUNC, "%4d %8d %18s %18s %8s\n", i,
796 : bcpcol->on_server.column_type,
797 : is_nullable_type(bcpcol->on_server.column_type)? "yes" : "no",
798 : bcpcol->column_nullable? "yes" : "no",
799 : bcpcol->bcp_column_data->is_null? "yes" : "no" );
800 : }
801 :
802 : /* the first two bytes of the rowbuffer are reserved to hold the entire record length */
803 216 : row_pos = start + 2;
804 216 : offsets[0] = row_pos;
805 :
806 216 : tdsdump_log(TDS_DBG_FUNC, "%4s %8s %8s %8s\n", "col", "ncols", "row_pos", "cpbytes");
807 :
808 3412 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
809 3412 : unsigned int cpbytes = 0;
810 3412 : TDSCOLUMN *bcpcol = bcpinfo->bindinfo->columns[i];
811 :
812 : /*
813 : * Is this column of "variable" type, i.e. NULLable
814 : * or naturally variable length e.g. VARCHAR
815 : */
816 3412 : if (bcpinfo->sybase_count > (TDS_INT) i) {
817 3412 : if (bcpinfo->sybase_colinfo[i].offset >= 0)
818 1576 : continue;
819 : } else {
820 0 : if (!is_nullable_type(bcpcol->on_server.column_type) && !bcpcol->column_nullable)
821 0 : continue;
822 : }
823 :
824 1836 : tdsdump_log(TDS_DBG_FUNC, "%4d %8d %8d %8d\n", i, ncols, row_pos, cpbytes);
825 :
826 1836 : if (TDS_FAILED(tds5_get_col_data_or_dflt(get_col_data, bcpinfo, bcpcol, offset, i)))
827 : return -1;
828 :
829 : /* If it's a NOT NULL column, and we have no data, throw an error.
830 : * This is the behavior for Sybase, this function is only used for Sybase */
831 1836 : if (!bcpcol->column_nullable && bcpcol->bcp_column_data->is_null) {
832 : /* No value or default value available and NULL not allowed. */
833 0 : if (null_error)
834 0 : null_error(bcpinfo, i, offset);
835 : return -1;
836 : }
837 :
838 : /* move the column buffer into the rowbuffer */
839 1836 : if (!bcpcol->bcp_column_data->is_null) {
840 476 : if (type_has_textptr(bcpcol->on_server.column_type)) {
841 14 : cpbytes = 16;
842 14 : bcpcol->column_textpos = row_pos; /* save for data write */
843 462 : } else if (is_numeric_type(bcpcol->on_server.column_type)) {
844 24 : TDS_NUMERIC *num = (TDS_NUMERIC *) bcpcol->bcp_column_data->data;
845 24 : cpbytes = tds_numeric_bytes_per_prec[num->precision];
846 24 : memcpy(&rowbuffer[row_pos], num->array, cpbytes);
847 438 : } else if ((bcpcol->column_type == SYBVARCHAR || bcpcol->column_type == SYBCHAR)
848 314 : && bcpcol->bcp_column_data->datalen == 0) {
849 20 : cpbytes = 1;
850 20 : rowbuffer[row_pos] = ' ';
851 : } else {
852 836 : cpbytes = bcpcol->bcp_column_data->datalen > bcpcol->column_size ?
853 418 : bcpcol->column_size : bcpcol->bcp_column_data->datalen;
854 418 : memcpy(&rowbuffer[row_pos], bcpcol->bcp_column_data->data, cpbytes);
855 418 : tds5_swap_data(bcpcol, &rowbuffer[row_pos]);
856 : }
857 1360 : } else if (type_has_textptr(bcpcol->column_type)) {
858 8 : bcpcol->column_textpos = row_pos;
859 : }
860 :
861 1836 : row_pos += cpbytes;
862 1836 : offsets[++ncols] = row_pos;
863 1836 : tdsdump_dump_buf(TDS_DBG_NETWORK, "BCP row buffer so far", rowbuffer, row_pos);
864 : }
865 :
866 216 : tdsdump_log(TDS_DBG_FUNC, "%4d %8d %8d\n", i, ncols, row_pos);
867 :
868 : /*
869 : * The rowbuffer ends with an offset table and, optionally, an adjustment table.
870 : * The offset table has 1-byte elements that describe the locations of the start of each column in
871 : * the rowbuffer. If the largest offset is greater than 255, another table -- the adjustment table --
872 : * is inserted just before the offset table. It holds the high bytes.
873 : *
874 : * Both tables are laid out in reverse:
875 : * #elements, offset N+1, offset N, offset N-1, ... offset 0
876 : * E.g. for 2 columns you have 4 data points:
877 : * 1. How many elements (4)
878 : * 2. Start of column 3 (non-existent, "one off the end")
879 : * 3. Start of column 2
880 : * 4. Start of column 1
881 : * The length of each column is computed by subtracting its start from the its successor's start.
882 : *
883 : * The algorithm below computes both tables. If the adjustment table isn't needed, the
884 : * effect is to overwrite it with the offset table.
885 : */
886 1574 : while (ncols && offsets[ncols] == offsets[ncols-1])
887 : ncols--; /* trailing NULL columns are not sent and are not included in the offset table */
888 :
889 386 : if (ncols && !bcpinfo->datarows_locking) {
890 170 : TDS_UCHAR *poff = rowbuffer + row_pos;
891 170 : unsigned int pfx_top = offsets[ncols] >> 8;
892 :
893 170 : tdsdump_log(TDS_DBG_FUNC, "ncols=%u poff=%p [%u]\n", ncols, poff, offsets[ncols]);
894 :
895 170 : if (offsets[ncols] / 256 == offsets[ncols - 1] / 256)
896 168 : *poff++ = ncols + 1;
897 : /* this is some kind of run-length-prefix encoding */
898 180 : while (pfx_top) {
899 : unsigned int n_pfx = 1;
900 :
901 142 : for (i = 0; i <= ncols ; ++i)
902 142 : if ((offsets[i] >> 8) < pfx_top)
903 90 : ++n_pfx;
904 10 : *poff++ = n_pfx;
905 10 : --pfx_top;
906 : }
907 :
908 170 : tdsdump_log(TDS_DBG_FUNC, "poff=%p\n", poff);
909 :
910 628 : for (i=0; i <= ncols; i++)
911 628 : *poff++ = offsets[ncols-i] & 0xFF;
912 170 : row_pos = (unsigned int)(poff - rowbuffer);
913 : } else { /* Datarows-locking */
914 : unsigned int col;
915 :
916 66 : for (col = ncols; col-- > 0;) {
917 : /* The DOL BULK format has a much simpler row table -- it's just a 2-byte length for every
918 : * non-fixed column (does not have the extra "offset after the end" that the basic format has) */
919 20 : rowbuffer[row_pos++] = offsets[col] % 256;
920 20 : rowbuffer[row_pos++] = offsets[col] / 256;
921 :
922 20 : tdsdump_log(TDS_DBG_FUNC, "[DOL BULK offset table] col=%u offset=%u\n", col, offsets[col]);
923 : }
924 : }
925 :
926 216 : tdsdump_log(TDS_DBG_FUNC, "%4d %8d %8d\n", i, ncols, row_pos);
927 216 : tdsdump_dump_buf(TDS_DBG_NETWORK, "BCP row buffer", rowbuffer, row_pos);
928 :
929 216 : *pncols = ncols;
930 :
931 216 : return ncols == 0? start : row_pos;
932 : }
933 :
934 : /**
935 : * Send BCP metadata to server.
936 : * Only for TDS 7.0+.
937 : * \tds
938 : * \param bcpinfo BCP information
939 : * \return TDS_SUCCESS or TDS_FAIL.
940 : */
941 : static TDSRET
942 314 : tds7_bcp_send_colmetadata(TDSSOCKET *tds, TDSBCPINFO *bcpinfo)
943 : {
944 : TDSCOLUMN *bcpcol;
945 : int i, num_cols;
946 :
947 314 : tdsdump_log(TDS_DBG_FUNC, "tds7_bcp_send_colmetadata(%p, %p)\n", tds, bcpinfo);
948 314 : assert(tds && bcpinfo);
949 :
950 314 : if (tds->out_flag != TDS_BULK || tds_set_state(tds, TDS_WRITING) != TDS_WRITING)
951 : return TDS_FAIL;
952 :
953 : /*
954 : * Deep joy! For TDS 7 we have to send a colmetadata message followed by row data
955 : */
956 314 : tds_put_byte(tds, TDS7_RESULT_TOKEN); /* 0x81 */
957 :
958 314 : num_cols = 0;
959 2802 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
960 2488 : bcpcol = bcpinfo->bindinfo->columns[i];
961 2488 : if ((!bcpinfo->identity_insert_on && bcpcol->column_identity) || bcpcol->column_timestamp
962 2488 : || (bcpcol->column_computed && !bcpinfo->with_triggers)) {
963 0 : continue;
964 : }
965 2488 : num_cols++;
966 : }
967 :
968 314 : tds_put_smallint(tds, num_cols);
969 :
970 2802 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
971 : size_t converted_len;
972 : const char *converted_name;
973 :
974 2488 : bcpcol = bcpinfo->bindinfo->columns[i];
975 :
976 : /*
977 : * dont send the (meta)data for timestamp columns, or
978 : * identity columns (unless indentity_insert is enabled
979 : */
980 :
981 2488 : if ((!bcpinfo->identity_insert_on && bcpcol->column_identity) || bcpcol->column_timestamp
982 2488 : || (bcpcol->column_computed && !bcpinfo->with_triggers)) {
983 0 : continue;
984 : }
985 :
986 2488 : if (IS_TDS72_PLUS(tds->conn))
987 1288 : tds_put_int(tds, bcpcol->column_usertype);
988 : else
989 1200 : tds_put_smallint(tds, bcpcol->column_usertype);
990 2488 : tds_put_smallint(tds, bcpcol->column_flags);
991 2488 : TDS_PUT_BYTE(tds, bcpcol->on_server.column_type);
992 :
993 2488 : assert(bcpcol->funcs);
994 2488 : bcpcol->funcs->put_info(tds, bcpcol);
995 :
996 : /* TODO put this in put_info. It seems that parameter format is
997 : * different from BCP format
998 : */
999 2488 : if (type_has_textptr(bcpcol->on_server.column_type)) {
1000 180 : converted_name = tds_convert_string(tds, tds->conn->char_convs[client2ucs2],
1001 60 : tds_dstr_cstr(&bcpinfo->tablename),
1002 120 : (int) tds_dstr_len(&bcpinfo->tablename), &converted_len);
1003 60 : if (!converted_name) {
1004 0 : tds_connection_close(tds->conn);
1005 0 : return TDS_FAIL;
1006 : }
1007 :
1008 : /* UTF-16 length is always size / 2 even for 4 byte letters (yes, 1 letter of length 2) */
1009 60 : TDS_PUT_SMALLINT(tds, converted_len / 2);
1010 60 : tds_put_n(tds, converted_name, converted_len);
1011 :
1012 120 : tds_convert_string_free(tds_dstr_cstr(&bcpinfo->tablename), converted_name);
1013 : }
1014 :
1015 7464 : converted_name = tds_convert_string(tds, tds->conn->char_convs[client2ucs2],
1016 2488 : tds_dstr_cstr(&bcpcol->column_name),
1017 4976 : (int) tds_dstr_len(&bcpcol->column_name), &converted_len);
1018 2488 : if (!converted_name) {
1019 0 : tds_connection_close(tds->conn);
1020 0 : return TDS_FAIL;
1021 : }
1022 :
1023 : /* UTF-16 length is always size / 2 even for 4 byte letters (yes, 1 letter of length 2) */
1024 2488 : TDS_PUT_BYTE(tds, converted_len / 2);
1025 2488 : tds_put_n(tds, converted_name, converted_len);
1026 :
1027 4976 : tds_convert_string_free(tds_dstr_cstr(&bcpcol->column_name), converted_name);
1028 : }
1029 :
1030 314 : tds_set_state(tds, TDS_SENDING);
1031 314 : return TDS_SUCCESS;
1032 : }
1033 :
1034 : /**
1035 : * Tell we finished sending BCP data to server
1036 : * \tds
1037 : * \param[out] rows_copied number of rows copied to server
1038 : */
1039 : TDSRET
1040 392 : tds_bcp_done(TDSSOCKET *tds, int *rows_copied)
1041 : {
1042 392 : tdsdump_log(TDS_DBG_FUNC, "tds_bcp_done(%p, %p)\n", tds, rows_copied);
1043 :
1044 392 : if (tds->out_flag != TDS_BULK || tds_set_state(tds, TDS_WRITING) != TDS_WRITING)
1045 : return TDS_FAIL;
1046 :
1047 382 : tds_flush_packet(tds);
1048 :
1049 382 : tds_set_state(tds, TDS_PENDING);
1050 :
1051 382 : TDS_PROPAGATE(tds_process_simple_query(tds));
1052 :
1053 374 : if (rows_copied)
1054 374 : *rows_copied = tds->rows_affected;
1055 :
1056 : return TDS_SUCCESS;
1057 : }
1058 :
1059 : /**
1060 : * Start sending BCP data to server.
1061 : * Initialize stream to accept data.
1062 : * \tds
1063 : * \param bcpinfo BCP information already prepared
1064 : */
1065 : TDSRET
1066 382 : tds_bcp_start(TDSSOCKET *tds, TDSBCPINFO *bcpinfo)
1067 : {
1068 : TDSRET rc;
1069 :
1070 382 : tdsdump_log(TDS_DBG_FUNC, "tds_bcp_start(%p, %p)\n", tds, bcpinfo);
1071 :
1072 382 : if (!IS_TDS50_PLUS(tds->conn))
1073 : return TDS_FAIL;
1074 :
1075 382 : TDS_PROPAGATE(tds_submit_query(tds, bcpinfo->insert_stmt));
1076 :
1077 : /* set we want to switch to bulk state */
1078 382 : tds->bulk_query = true;
1079 :
1080 : /*
1081 : * In TDS 5 we get the column information as a result set from the "insert bulk" command.
1082 : */
1083 382 : if (IS_TDS50(tds->conn))
1084 68 : rc = tds5_process_insert_bulk_reply(tds, bcpinfo);
1085 : else
1086 314 : rc = tds_process_simple_query(tds);
1087 382 : TDS_PROPAGATE(rc);
1088 :
1089 382 : tds->out_flag = TDS_BULK;
1090 382 : if (tds_set_state(tds, TDS_SENDING) != TDS_SENDING)
1091 : return TDS_FAIL;
1092 :
1093 382 : if (IS_TDS7_PLUS(tds->conn))
1094 314 : tds7_bcp_send_colmetadata(tds, bcpinfo);
1095 :
1096 : return TDS_SUCCESS;
1097 : }
1098 :
1099 : enum {
1100 : /* list of columns we need, 0-nnn */
1101 : BULKCOL_colcnt,
1102 : BULKCOL_colid,
1103 : BULKCOL_type,
1104 : BULKCOL_length,
1105 : BULKCOL_status,
1106 : BULKCOL_offset,
1107 : BULKCOL_dflt,
1108 :
1109 : /* number of columns needed */
1110 : BULKCOL_COUNT,
1111 :
1112 : /* bitmask to have them all */
1113 : BULKCOL_ALL = (1 << BULKCOL_COUNT) -1,
1114 : };
1115 :
1116 : static int
1117 820 : tds5_bulk_insert_column(const char *name)
1118 : {
1119 : #define BULKCOL(n) do {\
1120 : if (strcmp(name, #n) == 0) \
1121 : return BULKCOL_ ## n; \
1122 : } while(0)
1123 :
1124 820 : switch (name[0]) {
1125 136 : case 'c':
1126 136 : BULKCOL(colcnt);
1127 68 : BULKCOL(colid);
1128 : break;
1129 68 : case 'd':
1130 68 : BULKCOL(dflt);
1131 : break;
1132 68 : case 't':
1133 68 : BULKCOL(type);
1134 : break;
1135 68 : case 'l':
1136 68 : BULKCOL(length);
1137 : break;
1138 136 : case 's':
1139 136 : BULKCOL(status);
1140 : break;
1141 68 : case 'o':
1142 68 : BULKCOL(offset);
1143 : break;
1144 : }
1145 : #undef BULKCOL
1146 344 : return -1;
1147 : }
1148 :
1149 : static void
1150 2 : tds5_read_bulk_defaults(TDSRESULTINFO *res_info, TDSBCPINFO *bcpinfo)
1151 : {
1152 : int i;
1153 2 : TDS5COLINFO *syb_info = bcpinfo->sybase_colinfo;
1154 2 : TDS5COLINFO *const syb_info_end = syb_info + bcpinfo->sybase_count;
1155 :
1156 6 : for (i = 0; i < res_info->num_cols; ++i, ++syb_info) {
1157 4 : TDSCOLUMN *col = res_info->columns[i];
1158 4 : TDS_UCHAR *src = col->column_data;
1159 4 : TDS_INT len = col->column_cur_size;
1160 :
1161 4 : if (type_has_textptr(col->column_type))
1162 0 : src = (unsigned char *) ((TDSBLOB *) src)->textvalue;
1163 :
1164 : /* find next column having a default */
1165 : for (;;) {
1166 : /* avoid overflows */
1167 8 : if (syb_info >= syb_info_end)
1168 : return;
1169 :
1170 8 : if (syb_info->dflt)
1171 : break;
1172 4 : ++syb_info;
1173 : }
1174 :
1175 4 : syb_info->dflt_size = len;
1176 4 : if (TDS_RESIZE(syb_info->dflt_value, len))
1177 4 : memcpy(syb_info->dflt_value, src, len);
1178 : }
1179 : }
1180 :
1181 : static TDSRET
1182 68 : tds5_process_insert_bulk_reply(TDSSOCKET * tds, TDSBCPINFO *bcpinfo)
1183 : {
1184 : TDS_INT res_type;
1185 : TDS_INT done_flags;
1186 : TDSRET rc;
1187 68 : TDSRET ret = TDS_SUCCESS;
1188 68 : bool row_match = false;
1189 : TDSRESULTINFO *res_info;
1190 : int icol;
1191 : unsigned col_flags;
1192 : /* position of the columns in the row */
1193 : int cols_pos[BULKCOL_COUNT];
1194 : int cols_values[BULKCOL_COUNT];
1195 : TDS5COLINFO *colinfo;
1196 :
1197 : #if ENABLE_EXTRA_CHECKS
1198 68 : int num_defs = 0;
1199 : #endif
1200 :
1201 68 : CHECK_TDS_EXTRA(tds);
1202 :
1203 896 : while ((rc = tds_process_tokens(tds, &res_type, &done_flags, TDS_RETURN_DONE|TDS_RETURN_ROWFMT|TDS_RETURN_ROW)) == TDS_SUCCESS) {
1204 760 : switch (res_type) {
1205 70 : case TDS_ROWFMT_RESULT:
1206 : /* check if it's the resultset with column information and save column positions */
1207 70 : row_match = false;
1208 70 : col_flags = 0;
1209 70 : res_info = tds->current_results;
1210 70 : if (!res_info)
1211 0 : continue;
1212 820 : for (icol = 0; icol < res_info->num_cols; ++icol) {
1213 820 : const TDSCOLUMN *col = res_info->columns[icol];
1214 1640 : int scol = tds5_bulk_insert_column(tds_dstr_cstr(&col->column_name));
1215 820 : if (scol < 0)
1216 344 : continue;
1217 476 : cols_pos[scol] = icol;
1218 476 : col_flags |= 1 << scol;
1219 : }
1220 70 : if (col_flags == BULKCOL_ALL)
1221 68 : row_match = true;
1222 : break;
1223 620 : case TDS_ROW_RESULT:
1224 : /* get the results */
1225 620 : col_flags = 0;
1226 620 : res_info = tds->current_results;
1227 620 : if (!res_info)
1228 0 : continue;
1229 620 : if (!row_match) {
1230 2 : tds_extra_assert(res_info->num_cols == num_defs);
1231 2 : tds5_read_bulk_defaults(res_info, bcpinfo);
1232 2 : continue;
1233 : }
1234 4326 : for (icol = 0; icol < BULKCOL_COUNT; ++icol) {
1235 4326 : const TDSCOLUMN *col = res_info->columns[cols_pos[icol]];
1236 4326 : int ctype = tds_get_conversion_type(col->on_server.column_type, col->column_size);
1237 4326 : unsigned char *src = col->column_data;
1238 4326 : int srclen = col->column_cur_size;
1239 : CONV_RESULT dres;
1240 :
1241 4326 : if (tds_convert(tds_get_ctx(tds), ctype, src, srclen, SYBINT4, &dres) < 0)
1242 : break;
1243 4326 : col_flags |= 1 << icol;
1244 4326 : cols_values[icol] = dres.i;
1245 : }
1246 : /* save information */
1247 1236 : if (col_flags != BULKCOL_ALL ||
1248 1236 : cols_values[BULKCOL_colcnt] < 1 ||
1249 618 : cols_values[BULKCOL_colcnt] > 4096 || /* limit of columns accepted */
1250 1236 : cols_values[BULKCOL_colid] < 1 ||
1251 : cols_values[BULKCOL_colid] > cols_values[BULKCOL_colcnt]) {
1252 : rc = TDS_FAIL;
1253 : break;
1254 : }
1255 618 : if (bcpinfo->sybase_colinfo == NULL) {
1256 62 : bcpinfo->sybase_colinfo = calloc(cols_values[BULKCOL_colcnt], sizeof(*bcpinfo->sybase_colinfo));
1257 62 : if (bcpinfo->sybase_colinfo == NULL) {
1258 : rc = TDS_FAIL;
1259 : break;
1260 : }
1261 62 : bcpinfo->sybase_count = cols_values[BULKCOL_colcnt];
1262 : }
1263 : /* bound check, colcnt could have changed from row to row */
1264 618 : if (cols_values[BULKCOL_colid] > bcpinfo->sybase_count) {
1265 : rc = TDS_FAIL;
1266 : break;
1267 : }
1268 618 : colinfo = &bcpinfo->sybase_colinfo[cols_values[BULKCOL_colid] - 1];
1269 618 : colinfo->type = cols_values[BULKCOL_type];
1270 618 : colinfo->status = cols_values[BULKCOL_status];
1271 618 : colinfo->offset = cols_values[BULKCOL_offset];
1272 618 : colinfo->length = cols_values[BULKCOL_length];
1273 618 : colinfo->dflt = !!cols_values[BULKCOL_dflt];
1274 : #if ENABLE_EXTRA_CHECKS
1275 618 : if (colinfo->dflt)
1276 4 : ++num_defs;
1277 : #endif
1278 618 : tdsdump_log(TDS_DBG_INFO1, "gotten row information %d type %d length %d status %d offset %d\n",
1279 : cols_values[BULKCOL_colid], colinfo->type, colinfo->length, colinfo->status, colinfo->offset);
1280 : break;
1281 70 : case TDS_DONE_RESULT:
1282 : case TDS_DONEPROC_RESULT:
1283 : case TDS_DONEINPROC_RESULT:
1284 70 : if ((done_flags & TDS_DONE_ERROR) != 0)
1285 0 : ret = TDS_FAIL;
1286 : default:
1287 : break;
1288 : }
1289 : }
1290 68 : if (TDS_FAILED(rc))
1291 0 : ret = rc;
1292 :
1293 68 : return ret;
1294 : }
1295 :
1296 : static TDSRET
1297 3436 : tds5_get_col_data_or_dflt(tds_bcp_get_col_data get_col_data, TDSBCPINFO *bulk, TDSCOLUMN *bcpcol, int offset, int colnum)
1298 : {
1299 : TDSRET ret;
1300 : BCPCOLDATA *coldata;
1301 :
1302 3436 : ret = get_col_data(bulk, bcpcol, colnum, offset);
1303 3436 : coldata = bcpcol->bcp_column_data;
1304 3436 : if (coldata->is_null && bulk->sybase_colinfo != NULL && !type_has_textptr(bcpcol->column_type)) {
1305 1360 : const TDS5COLINFO *syb_info = &bulk->sybase_colinfo[colnum];
1306 :
1307 1360 : if (!syb_info->dflt) {
1308 : /* leave to NULL */
1309 1354 : if (!bcpcol->column_nullable)
1310 : return TDS_FAIL;
1311 : } else {
1312 6 : if (!TDS_RESIZE(coldata->data, syb_info->dflt_size))
1313 : return TDS_FAIL;
1314 :
1315 6 : memcpy(coldata->data, syb_info->dflt_value, syb_info->dflt_size);
1316 6 : coldata->datalen = syb_info->dflt_size;
1317 6 : coldata->is_null = false;
1318 : }
1319 : return TDS_SUCCESS;
1320 : }
1321 : return ret;
1322 : }
1323 :
1324 : /**
1325 : * Free row data allocated in the result set.
1326 : */
1327 : static void
1328 92 : tds_bcp_row_free(TDSRESULTINFO* result, unsigned char *row TDS_UNUSED)
1329 : {
1330 92 : result->row_size = 0;
1331 92 : TDS_ZERO_FREE(result->current_row);
1332 92 : }
1333 :
1334 : /**
1335 : * Start bulk copy to server
1336 : * \tds
1337 : * \param bcpinfo BCP information already prepared
1338 : */
1339 : TDSRET
1340 348 : tds_bcp_start_copy_in(TDSSOCKET *tds, TDSBCPINFO *bcpinfo)
1341 : {
1342 : TDSCOLUMN *bcpcol;
1343 : int i;
1344 348 : int fixed_col_len_tot = 0;
1345 348 : int variable_col_len_tot = 0;
1346 348 : int column_bcp_data_size = 0;
1347 348 : int bcp_record_size = 0;
1348 : TDSRET rc;
1349 : TDS_INT var_cols;
1350 :
1351 348 : tdsdump_log(TDS_DBG_FUNC, "tds_bcp_start_copy_in(%p, %p)\n", tds, bcpinfo);
1352 :
1353 348 : TDS_PROPAGATE(tds_bcp_start_insert_stmt(tds, bcpinfo));
1354 :
1355 348 : rc = tds_bcp_start(tds, bcpinfo);
1356 348 : if (TDS_FAILED(rc)) {
1357 : /* TODO, in CTLib was _ctclient_msg(blkdesc->con, "blk_rowxfer", 2, 5, 1, 140, ""); */
1358 : return rc;
1359 : }
1360 :
1361 : /*
1362 : * Work out the number of "variable" columns. These are either nullable or of
1363 : * varying length type e.g. varchar.
1364 : */
1365 348 : var_cols = 0;
1366 :
1367 348 : if (IS_TDS50(tds->conn)) {
1368 448 : for (i = 0; i < bcpinfo->bindinfo->num_cols; i++) {
1369 :
1370 448 : bcpcol = bcpinfo->bindinfo->columns[i];
1371 :
1372 : /*
1373 : * work out storage required for this datatype
1374 : * blobs always require 16, numerics vary, the
1375 : * rest can be taken from the server
1376 : */
1377 :
1378 448 : if (type_has_textptr(bcpcol->on_server.column_type))
1379 : column_bcp_data_size = 16;
1380 438 : else if (is_numeric_type(bcpcol->on_server.column_type))
1381 42 : column_bcp_data_size = tds_numeric_bytes_per_prec[bcpcol->column_prec];
1382 : else
1383 396 : column_bcp_data_size = bcpcol->column_size;
1384 :
1385 : /*
1386 : * now add that size into either fixed or variable
1387 : * column totals...
1388 : */
1389 :
1390 448 : if (is_nullable_type(bcpcol->on_server.column_type) || bcpcol->column_nullable) {
1391 250 : var_cols++;
1392 250 : variable_col_len_tot += column_bcp_data_size;
1393 : } else {
1394 198 : fixed_col_len_tot += column_bcp_data_size;
1395 : }
1396 : }
1397 :
1398 : /* this formula taken from sybase manual... */
1399 :
1400 124 : bcp_record_size = 4 +
1401 62 : fixed_col_len_tot +
1402 62 : variable_col_len_tot +
1403 124 : ( (int)(variable_col_len_tot / 256 ) + 1 ) +
1404 62 : (var_cols + 1) +
1405 : 2;
1406 :
1407 62 : tdsdump_log(TDS_DBG_FUNC, "current_record_size = %d\n", bcpinfo->bindinfo->row_size);
1408 62 : tdsdump_log(TDS_DBG_FUNC, "bcp_record_size = %d\n", bcp_record_size);
1409 :
1410 62 : if (bcp_record_size > bcpinfo->bindinfo->row_size) {
1411 26 : if (!TDS_RESIZE(bcpinfo->bindinfo->current_row, bcp_record_size)) {
1412 0 : tdsdump_log(TDS_DBG_FUNC, "could not realloc current_row\n");
1413 : return TDS_FAIL;
1414 : }
1415 26 : bcpinfo->bindinfo->row_free = tds_bcp_row_free;
1416 26 : bcpinfo->bindinfo->row_size = bcp_record_size;
1417 : }
1418 : }
1419 :
1420 : return TDS_SUCCESS;
1421 : }
1422 :
1423 : /** \cond HIDDEN_SYMBOLS */
1424 : #if defined(_WIN32) && defined(HAVE__LOCK_FILE) && defined(HAVE__UNLOCK_FILE)
1425 : #define TDS_HAVE_STDIO_LOCKED 1
1426 : #define flockfile(s) _lock_file(s)
1427 : #define funlockfile(s) _unlock_file(s)
1428 : #define getc_unlocked(s) _getc_nolock(s)
1429 : #define feof_unlocked(s) _feof_nolock(s)
1430 : #endif
1431 :
1432 : #ifndef TDS_HAVE_STDIO_LOCKED
1433 : #undef getc_unlocked
1434 : #undef feof_unlocked
1435 : #undef flockfile
1436 : #undef funlockfile
1437 : #define getc_unlocked(s) getc(s)
1438 : #define feof_unlocked(s) feof(s)
1439 : #define flockfile(s) do { } while(0)
1440 : #define funlockfile(s) do { } while(0)
1441 : #endif
1442 : /** \endcond */
1443 :
1444 : /**
1445 : * Reads a chunk of data from file stream checking for terminator
1446 : * \param stream file stream
1447 : * \param ptr buffer where to read data
1448 : * \param len length of buffer
1449 : */
1450 : static int
1451 5344 : tds_file_stream_read(TDSINSTREAM *stream, void *ptr, size_t len)
1452 : {
1453 5344 : TDSFILESTREAM *s = (TDSFILESTREAM *) stream;
1454 : int c;
1455 5344 : char *p = (char *) ptr;
1456 :
1457 1901280 : while (len) {
1458 : /* Check if the circular buffer contains a terminator */
1459 1895486 : if (memcmp(s->cbuf, s->cbuf + 2 * s->term_len - s->cpos, s->term_len) == 0)
1460 4894 : return p - (char *) ptr;
1461 :
1462 : /* It didn't; output from the circular buffer and input a new character */
1463 3781184 : c = getc_unlocked(s->f);
1464 1890592 : if (c == EOF)
1465 : return -1;
1466 :
1467 1890592 : *p++ = s->cbuf[s->cpos];
1468 1890592 : --len;
1469 :
1470 1890592 : s->cbuf[s->cpos++] = c;
1471 1890592 : if (s->cpos == s->term_len)
1472 1068112 : s->cpos = 0;
1473 : }
1474 450 : return p - (char *) ptr;
1475 : }
1476 :
1477 : TDSRET
1478 0 : tds_file_stream_init(TDSFILESTREAM *stream, FILE *f)
1479 : {
1480 2716 : memset(stream, 0, sizeof(*stream));
1481 2716 : stream->f = f;
1482 2716 : stream->stream.read = tds_file_stream_read;
1483 0 : return TDS_SUCCESS;
1484 : }
1485 :
1486 : TDSRET
1487 0 : tds_file_stream_destroy(TDSFILESTREAM *stream)
1488 : {
1489 2562 : free(stream->cbuf);
1490 2562 : stream->cbuf = NULL;
1491 0 : return TDS_SUCCESS;
1492 : }
1493 :
1494 : /** Sets the terminator and also performs an initial population of the circular buffer */
1495 : TDSRET
1496 2716 : tds_file_stream_set_terminator(TDSFILESTREAM *stream, const char *term, size_t term_len)
1497 : {
1498 : size_t readed;
1499 : void *newbuf;
1500 :
1501 2716 : if (!term_len) {
1502 0 : stream->term_len = 0;
1503 0 : return TDS_SUCCESS;
1504 : }
1505 :
1506 : /* Allocate the circular buffer */
1507 2716 : newbuf = realloc(stream->cbuf, term_len * 3);
1508 2716 : if (!newbuf)
1509 : return TDS_FAIL;
1510 :
1511 : /* Have to have initial data to populate the circular buffer (if the file contains
1512 : * less data than the length of 1 expected terminator, it means file is corrupt)
1513 : */
1514 2716 : readed = fread(newbuf, 1, term_len, stream->f);
1515 2716 : if (readed != term_len) {
1516 154 : free(newbuf);
1517 154 : if (readed == 0 && feof(stream->f))
1518 : return TDS_NO_MORE_RESULTS;
1519 : return TDS_FAIL;
1520 : }
1521 :
1522 : /* Finish setting up the stream structure */
1523 2562 : stream->cpos = 0;
1524 2562 : stream->cbuf = newbuf;
1525 2562 : stream->term_len = term_len;
1526 2562 : memcpy(stream->cbuf + term_len, term, term_len);
1527 2562 : memcpy(stream->cbuf + term_len * 2, term, term_len);
1528 :
1529 2562 : return TDS_SUCCESS;
1530 : }
1531 :
1532 : /**
1533 : * Read a data file, passing the data through iconv().
1534 : * \retval TDS_SUCCESS success
1535 : * \retval TDS_FAIL error reading the column
1536 : * \retval TDS_NO_MORE_RESULTS end of file detected
1537 : */
1538 : TDSRET
1539 2716 : tds_bcp_fread(TDSSOCKET * tds, TDSICONV * char_conv, FILE * stream, const char *terminator, size_t term_len, char **outbuf, size_t * outbytes)
1540 : {
1541 : TDSRET res;
1542 : TDSFILESTREAM r;
1543 : TDSDYNAMICSTREAM w;
1544 :
1545 : /* prepare streams */
1546 2716 : TDS_PROPAGATE(tds_file_stream_init(&r, stream));
1547 2716 : res = tds_file_stream_set_terminator(&r, terminator, term_len);
1548 2716 : if (res != TDS_SUCCESS) /* Also return if TDS_NO_MORE_RESULTS */
1549 : return res;
1550 :
1551 2562 : res = tds_dynamic_stream_init(&w, (void **) outbuf, 0);
1552 2562 : if (TDS_FAILED(res)) {
1553 0 : tds_file_stream_destroy(&r);
1554 0 : return res;
1555 : }
1556 :
1557 : /* convert/copy from input stream to output one */
1558 2562 : flockfile(stream);
1559 2562 : if (char_conv == NULL)
1560 964 : res = tds_copy_stream(&r.stream, &w.stream);
1561 : else
1562 1598 : res = tds_convert_stream(tds, char_conv, to_server, &r.stream, &w.stream);
1563 2562 : funlockfile(stream);
1564 2562 : tds_file_stream_destroy(&r);
1565 :
1566 2562 : TDS_PROPAGATE(res);
1567 :
1568 2562 : *outbytes = w.size;
1569 :
1570 : /* terminate buffer */
1571 2562 : if (!w.stream.buf_len)
1572 : return TDS_FAIL;
1573 :
1574 2562 : ((char *) w.stream.buffer)[0] = 0;
1575 2562 : w.stream.write(&w.stream, 1);
1576 :
1577 2562 : return res;
1578 : }
1579 :
1580 : /**
1581 : * Start writing writetext request.
1582 : * This request start a bulk session.
1583 : * \tds
1584 : * \param objname table name
1585 : * \param textptr TEXTPTR (see sql documentation)
1586 : * \param timestamp data timestamp
1587 : * \param with_log is log is enabled during insert
1588 : * \param size bytes to be inserted
1589 : */
1590 : TDSRET
1591 42 : tds_writetext_start(TDSSOCKET *tds, const char *objname, const char *textptr, const char *timestamp, int with_log, TDS_UINT size)
1592 : {
1593 : TDSRET rc;
1594 :
1595 : /* TODO mssql does not like timestamp */
1596 42 : rc = tds_submit_queryf(tds,
1597 : "writetext bulk %s 0x%s timestamp = 0x%s%s",
1598 : objname, textptr, timestamp, with_log ? " with log" : "");
1599 42 : TDS_PROPAGATE(rc);
1600 :
1601 : /* set we want to switch to bulk state */
1602 42 : tds->bulk_query = true;
1603 :
1604 : /* read the end token */
1605 42 : TDS_PROPAGATE(tds_process_simple_query(tds));
1606 :
1607 42 : tds->out_flag = TDS_BULK;
1608 42 : if (tds_set_state(tds, TDS_WRITING) != TDS_WRITING)
1609 : return TDS_FAIL;
1610 :
1611 42 : tds_put_int(tds, size);
1612 :
1613 42 : tds_set_state(tds, TDS_SENDING);
1614 42 : return TDS_SUCCESS;
1615 : }
1616 :
1617 : /**
1618 : * Send some data in the writetext request started by tds_writetext_start.
1619 : * You should write in total (with multiple calls to this function) all
1620 : * bytes declared calling tds_writetext_start.
1621 : * \tds
1622 : * \param text data to write
1623 : * \param size data size in bytes
1624 : */
1625 : TDSRET
1626 312 : tds_writetext_continue(TDSSOCKET *tds, const TDS_UCHAR *text, TDS_UINT size)
1627 : {
1628 312 : if (tds->out_flag != TDS_BULK || tds_set_state(tds, TDS_WRITING) != TDS_WRITING)
1629 : return TDS_FAIL;
1630 :
1631 : /* TODO check size left */
1632 312 : tds_put_n(tds, text, size);
1633 :
1634 312 : tds_set_state(tds, TDS_SENDING);
1635 312 : return TDS_SUCCESS;
1636 : }
1637 :
1638 : /**
1639 : * Finish sending writetext data.
1640 : * \tds
1641 : */
1642 : TDSRET
1643 42 : tds_writetext_end(TDSSOCKET *tds)
1644 : {
1645 42 : if (tds->out_flag != TDS_BULK || tds_set_state(tds, TDS_WRITING) != TDS_WRITING)
1646 : return TDS_FAIL;
1647 :
1648 42 : tds_flush_packet(tds);
1649 42 : tds_set_state(tds, TDS_PENDING);
1650 42 : return TDS_SUCCESS;
1651 : }
|