LCOV - code coverage report
Current view: top level - src/tds - bulk.c (source / functions) Hit Total Coverage
Test: FreeTDS coverage Lines: 490 579 84.6 %
Date: 2026-02-12 05:14:38 Functions: 21 22 95.5 %

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

Generated by: LCOV version 1.13