LCOV - code coverage report
Current view: top level - src/tds - bulk.c (source / functions) Hit Total Coverage
Test: FreeTDS coverage Lines: 515 578 89.1 %
Date: 2026-01-21 08:20:56 Functions: 21 21 100.0 %

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

Generated by: LCOV version 1.13