int copy_and_convert_field()

in sql-odbc/src/sqlodbc/convert.c [973:1652]


int copy_and_convert_field(StatementClass *stmt, OID field_type, int atttypmod,
                           void *valuei, SQLSMALLINT fCType, int precision,
                           PTR rgbValue, SQLLEN cbValueMax, SQLLEN *pcbValue,
                           SQLLEN *pIndicator) {
    CSTR func = "copy_and_convert_field";
    const char *value = valuei;
    ARDFields *opts = SC_get_ARDF(stmt);
    GetDataInfo *gdata = SC_get_GDTI(stmt);
    SQLLEN len = 0;
    SIMPLE_TIME std_time;
#ifdef HAVE_LOCALTIME_R
    struct tm tm;
#endif /* HAVE_LOCALTIME_R */
    SQLLEN pcbValueOffset, rgbValueOffset;
    char *rgbValueBindRow = NULL;
    SQLLEN *pcbValueBindRow = NULL, *pIndicatorBindRow = NULL;
    SQLSETPOSIROW bind_row = stmt->bind_row;
    int bind_size = opts->bind_size;
    int result = COPY_OK;
    const ConnectionClass *conn = SC_get_conn(stmt);
    BOOL text_bin_handling;
    const char *neut_str = value;
    char booltemp[3];
    char midtemp[64];
    GetDataClass *esdc;

    if (stmt->current_col >= 0) {
        if (stmt->current_col >= opts->allocated) {
            return SQL_ERROR;
        }
        if (gdata->allocated != opts->allocated)
            extend_getdata_info(gdata, opts->allocated, TRUE);
        esdc = &gdata->gdata[stmt->current_col];
        if (esdc->data_left == -2)
            esdc->data_left = (cbValueMax > 0) ? 0 : -1; /* This seems to be *
                                                          * needed by ADO ? */
        if (esdc->data_left == 0) {
            if (esdc->ttlbuf != NULL) {
                free(esdc->ttlbuf);
                esdc->ttlbuf = NULL;
                esdc->ttlbuflen = 0;
            }
            esdc->data_left = -2; /* needed by ADO ? */
            return COPY_NO_DATA_FOUND;
        }
    }
    /*---------
     *	rgbValueOffset is *ONLY* for character and binary data.
     *	pcbValueOffset is for computing any pcbValue location
     *---------
     */

    if (bind_size > 0)
        pcbValueOffset = rgbValueOffset = (bind_size * bind_row);
    else {
        pcbValueOffset = bind_row * sizeof(SQLLEN);
        rgbValueOffset = bind_row * cbValueMax;
    }
    /*
     *	The following is applicable in case bind_size > 0
     *	or the fCType is of variable length.
     */
    if (rgbValue)
        rgbValueBindRow = (char *)rgbValue + rgbValueOffset;
    if (pcbValue)
        pcbValueBindRow = LENADDR_SHIFT(pcbValue, pcbValueOffset);
    if (pIndicator) {
        pIndicatorBindRow = (SQLLEN *)((char *)pIndicator + pcbValueOffset);
        *pIndicatorBindRow = 0;
    }

    memset(&std_time, 0, sizeof(SIMPLE_TIME));

    MYLOG(OPENSEARCH_DEBUG,
          "field_type = %d, fctype = %d, value = '%s', cbValueMax=" FORMAT_LEN
          "\n",
          field_type, fCType, (value == NULL) ? "<NULL>" : value, cbValueMax);

    if (!value) {
        /*
         * handle a null just by returning SQL_NULL_DATA in pcbValue, and
         * doing nothing to the buffer.
         */
        if (pIndicator) {
            *pIndicatorBindRow = SQL_NULL_DATA;
            return COPY_OK;
        } else {
            SC_set_error(stmt, STMT_RETURN_NULL_WITHOUT_INDICATOR,
                         "StrLen_or_IndPtr was a null pointer and NULL data "
                         "was retrieved",
                         func);
            return SQL_ERROR;
        }
    }

    if (stmt->hdbc->DataSourceToDriver != NULL) {
        size_t length = strlen(value);

        stmt->hdbc->DataSourceToDriver(stmt->hdbc->translation_option, SQL_CHAR,
                                       valuei, (SDWORD)length, valuei,
                                       (SDWORD)length, NULL, NULL, 0, NULL);
    }

    /*
     * First convert any specific OpenSearch types into more useable data.
     *
     * NOTE: Conversions from ES char/varchar of a date/time/timestamp value
     * to SQL_C_DATE,SQL_C_TIME, SQL_C_TIMESTAMP not supported
     */
    switch (field_type) {
            /*
             * $$$ need to add parsing for date/time/timestamp strings in
             * OPENSEARCH_TYPE_CHAR,VARCHAR $$$
             */
        case OPENSEARCH_TYPE_DATE:
            sscanf(value, "%4d-%2d-%2d", &std_time.y, &std_time.m, &std_time.d);
            break;

        case OPENSEARCH_TYPE_TIME: {
            BOOL bZone = FALSE; /* time zone stuff is unreliable */
            int zone;
            timestamp2stime(value, &std_time, &bZone, &zone);
        } break;

        case OPENSEARCH_TYPE_ABSTIME:
        case OPENSEARCH_TYPE_DATETIME:
        case OPENSEARCH_TYPE_TIMESTAMP_NO_TMZONE:
        case OPENSEARCH_TYPE_TIMESTAMP:
            std_time.fr = 0;
            std_time.infinity = 0;
            if (strnicmp(value, INFINITY_STRING, 8) == 0) {
                std_time.infinity = 1;
                std_time.m = 12;
                std_time.d = 31;
                std_time.y = 9999;
                std_time.hh = 23;
                std_time.mm = 59;
                std_time.ss = 59;
            }
            if (strnicmp(value, MINFINITY_STRING, 9) == 0) {
                std_time.infinity = -1;
                std_time.m = 1;
                std_time.d = 1;
                // std_time.y = -4713;
                std_time.y = -9999;
                std_time.hh = 0;
                std_time.mm = 0;
                std_time.ss = 0;
            }
            if (strnicmp(value, "invalid", 7) != 0) {
                BOOL bZone = field_type != OPENSEARCH_TYPE_TIMESTAMP_NO_TMZONE;
                int zone;

                /*
                 * sscanf(value, "%4d-%2d-%2d %2d:%2d:%2d", &std_time.y,
                 * &std_time.m, &std_time.d, &std_time.hh, &std_time.mm,
                 * &std_time.ss);
                 */
                bZone = FALSE; /* time zone stuff is unreliable */
                timestamp2stime(value, &std_time, &bZone, &zone);
                MYLOG(OPENSEARCH_ALL, "2stime fr=%d\n", std_time.fr);
            } else {
                /*
                 * The timestamp is invalid so set something conspicuous,
                 * like the epoch
                 */
                struct tm *tim;
                time_t t = 0;
#ifdef HAVE_LOCALTIME_R
                tim = localtime_r(&t, &tm);
#else
                tim = localtime(&t);
#endif /* HAVE_LOCALTIME_R */
                std_time.m = tim->tm_mon + 1;
                std_time.d = tim->tm_mday;
                std_time.y = tim->tm_year + 1900;
                std_time.hh = tim->tm_hour;
                std_time.mm = tim->tm_min;
                std_time.ss = tim->tm_sec;
            }
            break;

        case OPENSEARCH_TYPE_BOOL: { /* change T/F to 1/0 */
            switch (((char *)value)[0]) {
                case 'f':
                case 'F':
                case 'n':
                case 'N':
                case '0':
                    STRCPY_FIXED(booltemp, "0");
                    break;
                default:
                    STRCPY_FIXED(booltemp, "1");
            }
            neut_str = booltemp;
        } break;

            /* This is for internal use by SQLStatistics() */
        case OPENSEARCH_TYPE_INT2VECTOR:
            if (SQL_C_DEFAULT == fCType) {
                int i, nval, maxc;
                const char *vp;
                /* this is an array of eight integers */
                short *short_array = (short *)rgbValueBindRow, shortv;

                maxc = 0;
                if (NULL != short_array)
                    maxc = (int)cbValueMax / sizeof(short);
                vp = value;
                nval = 0;
                MYLOG(OPENSEARCH_DEBUG, "index=(");
                for (i = 0;; i++) {
                    if (sscanf(vp, "%hi", &shortv) != 1)
                        break;
                    MYPRINTF(0, " %hi", shortv);
                    nval++;
                    if (nval < maxc)
                        short_array[i + 1] = shortv;

                    /* skip the current token */
                    while (IS_NOT_SPACE(*vp))
                        vp++;
                    /* and skip the space to the next token */
                    while ((*vp != '\0') && (isspace(*vp)))
                        vp++;
                    if (*vp == '\0')
                        break;
                }
                MYPRINTF(0, ") nval = %i\n", nval);
                if (maxc > 0)
                    short_array[0] = (short)nval;

                /* There is no corresponding fCType for this. */
                len = (nval + 1) * sizeof(short);
                if (pcbValue)
                    *pcbValueBindRow = len;

                if (len <= cbValueMax)
                    return COPY_OK; /* dont go any further or the data will be
                                     * trashed */
                else
                    return COPY_RESULT_TRUNCATED;
            }
            break;

            /*
             * This is a large object OID, which is used to store
             * LONGVARBINARY objects.
             */
        case OPENSEARCH_TYPE_LO_UNDEFINED:

            return convert_lo(stmt, value, fCType, rgbValueBindRow, cbValueMax,
                              pcbValueBindRow);

        case 0:
            break;

        default:
            if (field_type
                    == (OID)stmt->hdbc
                           ->lobj_type /* hack until permanent type available */
                || (OPENSEARCH_TYPE_OID == field_type && SQL_C_BINARY == fCType
                    && conn->lo_is_domain))
                return convert_lo(stmt, value, fCType, rgbValueBindRow,
                                  cbValueMax, pcbValueBindRow);
    }

    /* Change default into something useable */
    if (fCType == SQL_C_DEFAULT) {
        fCType = opensearchtype_attr_to_ctype(conn, field_type, atttypmod);
#ifdef UNICODE_SUPPORT
        if (fCType == SQL_C_WCHAR && CC_default_is_c(conn))
            fCType = SQL_C_CHAR;
#endif

        MYLOG(OPENSEARCH_DEBUG, ", SQL_C_DEFAULT: fCType = %d\n", fCType);
    }

    text_bin_handling = FALSE;
    switch (fCType) {
        case INTERNAL_ASIS_TYPE:
#ifdef UNICODE_SUPPORT
        case SQL_C_WCHAR:
#endif /* UNICODE_SUPPORT */
        case SQL_C_CHAR:
            text_bin_handling = TRUE;
            break;
        case SQL_C_BINARY:
            switch (field_type) {
                case OPENSEARCH_TYPE_UNKNOWN:
                case OPENSEARCH_TYPE_BPCHAR:
                case OPENSEARCH_TYPE_VARCHAR:
                case OPENSEARCH_TYPE_TEXT:
                case OPENSEARCH_TYPE_XML:
                case OPENSEARCH_TYPE_BPCHARARRAY:
                case OPENSEARCH_TYPE_VARCHARARRAY:
                case OPENSEARCH_TYPE_TEXTARRAY:
                case OPENSEARCH_TYPE_XMLARRAY:
                case OPENSEARCH_TYPE_BYTEA:
                    text_bin_handling = TRUE;
                    break;
            }
            break;
    }

    if (text_bin_handling) {
        BOOL pre_convert = TRUE;
        int midsize = sizeof(midtemp);
        int i;

        /* Special character formatting as required */

        /*
         * These really should return error if cbValueMax is not big
         * enough.
         */
        switch (field_type) {
            case OPENSEARCH_TYPE_DATE:
                len = SPRINTF_FIXED(midtemp, "%.4d-%.2d-%.2d", std_time.y,
                                    std_time.m, std_time.d);
                break;

            case OPENSEARCH_TYPE_TIME:
                len = SPRINTF_FIXED(midtemp, "%.2d:%.2d:%.2d", std_time.hh,
                                    std_time.mm, std_time.ss);
                if (std_time.fr > 0) {
                    int wdt;
                    int fr = effective_fraction(std_time.fr, &wdt);

                    char *fraction = NULL;
                    len = sprintf(fraction, ".%0*d", wdt, fr);
                    strcat(midtemp, fraction);
                }
                break;

            case OPENSEARCH_TYPE_ABSTIME:
            case OPENSEARCH_TYPE_DATETIME:
            case OPENSEARCH_TYPE_TIMESTAMP_NO_TMZONE:
            case OPENSEARCH_TYPE_TIMESTAMP:
                len = stime2timestamp(&std_time, midtemp, midsize, FALSE,
                                      (int)(midsize - 19 - 2));
                break;

            case OPENSEARCH_TYPE_UUID:
                len = strlen(neut_str);
                for (i = 0; i < len && i < midsize - 2; i++)
                    midtemp[i] = (char)toupper((UCHAR)neut_str[i]);
                midtemp[i] = '\0';
                MYLOG(OPENSEARCH_DEBUG, "OPENSEARCH_TYPE_UUID: rgbValueBindRow = '%s'\n",
                      rgbValueBindRow);
                break;

                /*
                 * Currently, data is SILENTLY TRUNCATED for BYTEA and
                 * character data types if there is not enough room in
                 * cbValueMax because the driver can't handle multiple
                 * calls to SQLGetData for these, yet.	Most likely, the
                 * buffer passed in will be big enough to handle the
                 * maximum limit of OpenSearch, anyway.
                 *
                 * LongVarBinary types are handled correctly above, observing
                 * truncation and all that stuff since there is
                 * essentially no limit on the large object used to store
                 * those.
                 */
            case OPENSEARCH_TYPE_BYTEA: /* convert binary data to hex strings
                                 * (i.e, 255 = "FF") */

            default:
                pre_convert = FALSE;
        }
        if (pre_convert)
            neut_str = midtemp;
        result = convert_text_field_to_sql_c(
            gdata, stmt->current_col, neut_str, field_type, fCType,
            rgbValueBindRow, cbValueMax, conn, &len);
    } else {
        SQLGUID g;

        /*
         * for SQL_C_CHAR, it's probably ok to leave currency symbols in.
         * But to convert to numeric types, it is necessary to get rid of
         * those.
         */
        if (field_type == OPENSEARCH_TYPE_MONEY) {
            if (convert_money(neut_str, midtemp, sizeof(midtemp)))
                neut_str = midtemp;
            else {
                MYLOG(OPENSEARCH_DEBUG, "couldn't convert money type to %d\n", fCType);
                return COPY_UNSUPPORTED_TYPE;
            }
        }

        switch (fCType) {
            case SQL_C_DATE:
            case SQL_C_TYPE_DATE: /* 91 */
                len = 6;
                {
                    DATE_STRUCT *ds;
                    struct tm *tim;

                    if (bind_size > 0)
                        ds = (DATE_STRUCT *)rgbValueBindRow;
                    else
                        ds = (DATE_STRUCT *)rgbValue + bind_row;

                    /*
                     * Initialize date in case conversion destination
                     * expects date part from this source time data.
                     * A value may be partially set here, so do some
                     * sanity checks on the existing values before
                     * setting them.
                     */
                    tim = SC_get_localtime(stmt);
                    if (std_time.m == 0)
                        std_time.m = tim->tm_mon + 1;
                    if (std_time.d == 0)
                        std_time.d = tim->tm_mday;
                    if (std_time.y == 0)
                        std_time.y = tim->tm_year + 1900;
                    ds->year = (SQLSMALLINT)std_time.y;
                    ds->month = (SQLUSMALLINT)std_time.m;
                    ds->day = (SQLUSMALLINT)std_time.d;
                }
                break;

            case SQL_C_TIME:
            case SQL_C_TYPE_TIME: /* 92 */
                len = 6;
                {
                    TIME_STRUCT *ts;

                    if (bind_size > 0)
                        ts = (TIME_STRUCT *)rgbValueBindRow;
                    else
                        ts = (TIME_STRUCT *)rgbValue + bind_row;
                    ts->hour = (SQLUSMALLINT)std_time.hh;
                    ts->minute = (SQLUSMALLINT)std_time.mm;
                    ts->second = (SQLUSMALLINT)std_time.ss;
                }
                break;

            case SQL_C_TIMESTAMP:
            case SQL_C_TYPE_TIMESTAMP: /* 93 */
                len = 16;
                {
                    struct tm *tim;
                    TIMESTAMP_STRUCT *ts;

                    if (bind_size > 0)
                        ts = (TIMESTAMP_STRUCT *)rgbValueBindRow;
                    else
                        ts = (TIMESTAMP_STRUCT *)rgbValue + bind_row;

                    /*
                     * Initialize date in case conversion destination
                     * expects date part from this source time data.
                     * A value may be partially set here, so do some
                     * sanity checks on the existing values before
                     * setting them.
                     */
                    tim = SC_get_localtime(stmt);
                    if (std_time.m == 0)
                        std_time.m = tim->tm_mon + 1;
                    if (std_time.d == 0)
                        std_time.d = tim->tm_mday;
                    if (std_time.y == 0)
                        std_time.y = tim->tm_year + 1900;

                    ts->year = (SQLSMALLINT)std_time.y;
                    ts->month = (SQLUSMALLINT)std_time.m;
                    ts->day = (SQLUSMALLINT)std_time.d;
                    ts->hour = (SQLUSMALLINT)std_time.hh;
                    ts->minute = (SQLUSMALLINT)std_time.mm;
                    ts->second = (SQLUSMALLINT)std_time.ss;
                    ts->fraction = (SQLUINTEGER)std_time.fr;
                }
                break;

            case SQL_C_BIT:
                len = 1;
                if (bind_size > 0)
                    *((UCHAR *)rgbValueBindRow) = (UCHAR)atoi(neut_str);
                else
                    *((UCHAR *)rgbValue + bind_row) = (UCHAR)atoi(neut_str);

                MYLOG(99,
                      "SQL_C_BIT: bind_row = " FORMAT_POSIROW
                      " val = %d, cb = " FORMAT_LEN ", rgb=%d\n",
                      bind_row, atoi(neut_str), cbValueMax,
                      *((UCHAR *)rgbValue));
                break;

            case SQL_C_STINYINT:
            case SQL_C_TINYINT:
                len = 1;
                if (bind_size > 0)
                    *((SCHAR *)rgbValueBindRow) = (SCHAR)atoi(neut_str);
                else
                    *((SCHAR *)rgbValue + bind_row) = (SCHAR)atoi(neut_str);
                break;

            case SQL_C_UTINYINT:
                len = 1;
                if (bind_size > 0)
                    *((UCHAR *)rgbValueBindRow) = (UCHAR)atoi(neut_str);
                else
                    *((UCHAR *)rgbValue + bind_row) = (UCHAR)atoi(neut_str);
                break;

            case SQL_C_FLOAT:
                set_client_decimal_point((char *)neut_str);
                len = 4;
                if (bind_size > 0)
                    *((SFLOAT *)rgbValueBindRow) =
                        (SFLOAT)get_double_value(neut_str);
                else
                    *((SFLOAT *)rgbValue + bind_row) =
                        (SFLOAT)get_double_value(neut_str);
                break;

            case SQL_C_DOUBLE:
                set_client_decimal_point((char *)neut_str);
                len = 8;
                if (bind_size > 0)
                    *((SDOUBLE *)rgbValueBindRow) =
                        (SDOUBLE)get_double_value(neut_str);
                else
                    *((SDOUBLE *)rgbValue + bind_row) =
                        (SDOUBLE)get_double_value(neut_str);
                break;

            case SQL_C_NUMERIC: {
                SQL_NUMERIC_STRUCT *ns;
                BOOL overflowed;

                if (bind_size > 0)
                    ns = (SQL_NUMERIC_STRUCT *)rgbValueBindRow;
                else
                    ns = (SQL_NUMERIC_STRUCT *)rgbValue + bind_row;

                parse_to_numeric_struct(neut_str, ns, &overflowed);
                if (overflowed)
                    result = COPY_RESULT_TRUNCATED;
            } break;

            case SQL_C_SSHORT:
            case SQL_C_SHORT:
                len = 2;
                if (bind_size > 0)
                    *((SQLSMALLINT *)rgbValueBindRow) =
                        (SQLSMALLINT)atoi(neut_str);
                else
                    *((SQLSMALLINT *)rgbValue + bind_row) =
                        (SQLSMALLINT)atoi(neut_str);
                break;

            case SQL_C_USHORT:
                len = 2;
                if (bind_size > 0)
                    *((SQLUSMALLINT *)rgbValueBindRow) =
                        (SQLUSMALLINT)atoi(neut_str);
                else
                    *((SQLUSMALLINT *)rgbValue + bind_row) =
                        (SQLUSMALLINT)atoi(neut_str);
                break;

            case SQL_C_SLONG:
            case SQL_C_LONG:
                len = 4;
                if (bind_size > 0)
                    *((SQLINTEGER *)rgbValueBindRow) = atol(neut_str);
                else
                    *((SQLINTEGER *)rgbValue + bind_row) = atol(neut_str);
                break;

            case SQL_C_ULONG:
                len = 4;
                if (bind_size > 0)
                    *((SQLUINTEGER *)rgbValueBindRow) = ATOI32U(neut_str);
                else
                    *((SQLUINTEGER *)rgbValue + bind_row) = ATOI32U(neut_str);
                break;

#ifdef ODBCINT64
            case SQL_C_SBIGINT:
                len = 8;
                if (bind_size > 0)
                    *((SQLBIGINT *)rgbValueBindRow) = ATOI64(neut_str);
                else
                    *((SQLBIGINT *)rgbValue + bind_row) = ATOI64(neut_str);
                break;

            case SQL_C_UBIGINT:
                len = 8;
                if (bind_size > 0)
                    *((SQLUBIGINT *)rgbValueBindRow) = ATOI64U(neut_str);
                else
                    *((SQLUBIGINT *)rgbValue + bind_row) = ATOI64U(neut_str);
                break;

#endif /* ODBCINT64 */
            case SQL_C_BINARY:
                /* The following is for SQL_C_VARBOOKMARK */
                if (OPENSEARCH_TYPE_INT4 == field_type) {
                    UInt4 ival = ATOI32U(neut_str);

                    MYLOG(OPENSEARCH_ALL, "SQL_C_VARBOOKMARK value=%d\n", ival);
                    if (pcbValue)
                        *pcbValueBindRow = sizeof(ival);
                    if (cbValueMax >= (SQLLEN)sizeof(ival)) {
                        memcpy(rgbValueBindRow, &ival, sizeof(ival));
                        return COPY_OK;
                    } else
                        return COPY_RESULT_TRUNCATED;
                } else if (OPENSEARCH_TYPE_UUID == field_type) {
                    int rtn = char2guid(neut_str, &g);

                    if (COPY_OK != rtn)
                        return rtn;
                    if (pcbValue)
                        *pcbValueBindRow = sizeof(g);
                    if (cbValueMax >= (SQLLEN)sizeof(g)) {
                        memcpy(rgbValueBindRow, &g, sizeof(g));
                        return COPY_OK;
                    } else
                        return COPY_RESULT_TRUNCATED;
                } else {
                    MYLOG(OPENSEARCH_DEBUG,
                          "couldn't convert the type %d to SQL_C_BINARY\n",
                          field_type);
                    return COPY_UNSUPPORTED_TYPE;
                }
                break;
            case SQL_C_GUID:

                result = char2guid(neut_str, &g);
                if (COPY_OK != result) {
                    MYLOG(OPENSEARCH_DEBUG, "Could not convert to SQL_C_GUID\n");
                    return COPY_UNSUPPORTED_TYPE;
                }
                len = sizeof(g);
                if (bind_size > 0)
                    *((SQLGUID *)rgbValueBindRow) = g;
                else
                    *((SQLGUID *)rgbValue + bind_row) = g;
                break;
            case SQL_C_INTERVAL_YEAR:
            case SQL_C_INTERVAL_MONTH:
            case SQL_C_INTERVAL_YEAR_TO_MONTH:
            case SQL_C_INTERVAL_DAY:
            case SQL_C_INTERVAL_HOUR:
            case SQL_C_INTERVAL_DAY_TO_HOUR:
            case SQL_C_INTERVAL_MINUTE:
            case SQL_C_INTERVAL_HOUR_TO_MINUTE:
            case SQL_C_INTERVAL_SECOND:
            case SQL_C_INTERVAL_DAY_TO_SECOND:
            case SQL_C_INTERVAL_HOUR_TO_SECOND:
            case SQL_C_INTERVAL_MINUTE_TO_SECOND:
                interval2istruct(
                    fCType, precision, neut_str,
                    bind_size > 0 ? (SQL_INTERVAL_STRUCT *)rgbValueBindRow
                                  : (SQL_INTERVAL_STRUCT *)rgbValue + bind_row);
                break;

            default:
                MYLOG(OPENSEARCH_DEBUG, "conversion to the type %d isn't supported\n",
                      fCType);
                return COPY_UNSUPPORTED_TYPE;
        }
    }

    /* store the length of what was copied, if there's a place for it */
    if (pcbValue)
        *pcbValueBindRow = len;

    if (result == COPY_OK && stmt->current_col >= 0)
        gdata->gdata[stmt->current_col].data_left = 0;
    return result;
}