Index: driver/execute.c =================================================================== --- driver/execute.c (revision 250) +++ driver/execute.c (working copy) @@ -320,6 +320,18 @@ { return add_to_buffer(net,to,"NULL",4); } + /* + We may see SQL_COLUMN_IGNORE from bulk INSERT operations, where we + may have been told to ignore a column in one particular row. So we + try to insert DEFAULT, or NULL for really old servers. + */ + else if ( *(param->actual_len) == SQL_COLUMN_IGNORE ) + { + if (is_minimum_version(mysql->server_version, "4.0.3", 5)) + return add_to_buffer(net,to,"DEFAULT",7); + else + return add_to_buffer(net,to,"NULL",4); + } else if ( *param->actual_len == SQL_DATA_AT_EXEC || *param->actual_len <= SQL_LEN_DATA_AT_EXEC_OFFSET ) { Index: driver/cursor.c =================================================================== --- driver/cursor.c (revision 250) +++ driver/cursor.c (working copy) @@ -358,7 +358,8 @@ { SQLCHAR *orig_to= *to; MYSQL mysql= stmt->dbc->mysql; - SQLUINTEGER length= *(param.actual_len)+1; + /* Negative length means either NULL or DEFAULT, so we need 7 chars. */ + SQLUINTEGER length= (*param.actual_len > 0 ? *param.actual_len + 1 : 7); if ( !(*to= (SQLCHAR *) extend_buffer(*net,(char*) *to,length)) ) return set_error(stmt,MYERR_S1001,NULL,4001); @@ -1111,13 +1112,13 @@ SQLUINTEGER insert_count= 1; /* num rows to insert - will be real value when row is 0 (all) */ SQLUINTEGER count= 0; /* current row */ SQLLEN length; - NET *net; + MYSQL mysql= stmt->dbc->mysql; + NET *net= &mysql.net; SQLUSMALLINT ncol; SQLCHAR *to; ulong query_length= 0; /* our original query len so we can reset pos if break_insert */ my_bool break_insert= FALSE; /* true if we are to exceed max data size for transmission but this seems to be misused */ - MYSQL mysql= stmt->dbc->mysql ; PARAM_BIND param; /* determine the number of rows to insert when irow = 0 */ @@ -1137,24 +1138,37 @@ /* "break_insert=FALSE" here? */ } - /* - for each row build and execute INSERT statement - */ - while ( count < insert_count ) + /* For each row, build the value list from its columns */ + while (count < insert_count) { - net= &mysql.net; - to = net->buff; + to= net->buff; - /* - for each *relevant* column append to INSERT statement - */ + /* Append values for each column. */ dynstr_append_mem(ext_query,"(", 1); for ( ncol= 0; ncol < result->field_count; ncol++ ) { - ulong transfer_length,precision,display_size; + ulong transfer_length,precision,display_size; MYSQL_FIELD *field= mysql_fetch_field_direct(result,ncol); BIND *bind= stmt->bind+ncol; + SQLINTEGER binding_offset= 0, element_size= 0; + SQLLEN ind_or_len; + if (stmt->stmt_options.bind_type != SQL_BIND_BY_COLUMN && + stmt->stmt_options.bind_offset) + binding_offset= *(stmt->stmt_options.bind_offset); + + if (stmt->stmt_options.bind_type != SQL_BIND_BY_COLUMN) + element_size= stmt->stmt_options.bind_type; + + if (bind->pcbValue) + ind_or_len= *(SQLLEN *)((gptr)bind->pcbValue + + binding_offset + + count * (element_size ? + element_size : + sizeof(SQLLEN))); + else + ind_or_len= bind->cbValueMax; + param.SqlType= unireg_to_sql_datatype(stmt, field, 0, @@ -1162,35 +1176,33 @@ &precision, &display_size); param.CType = bind->fCType; - param.buffer= (gptr) bind->rgbValue+count*(stmt->stmt_options.bind_type); + param.buffer= ((gptr)bind->rgbValue + + binding_offset + + count * (element_size ? + element_size : + bind_length(bind->fCType, + bind->cbValueMax))); - if ( !( bind->pcbValue && ( *bind->pcbValue == SQL_COLUMN_IGNORE ) ) ) - { - if ( param.buffer ) - { - if ( bind->pcbValue ) - { - if ( *bind->pcbValue == SQL_NTS ) - length= strlen(param.buffer); - else if ( *bind->pcbValue == SQL_COLUMN_IGNORE ) - { - /* should not happen see CSC-3985 */ - length= SQL_NULL_DATA; - } - else - length= *bind->pcbValue; - } - else - length= bind->cbValueMax; - } - else - length= SQL_NULL_DATA; + switch (ind_or_len) { + case SQL_NTS: + if (param.buffer) + length= strlen(param.buffer); + break; + /* + We pass through SQL_COLUMN_IGNORE and SQL_NULL_DATA, + because the insert_data() that is eventually called knows + how to deal with them. + */ + case SQL_COLUMN_IGNORE: + case SQL_NULL_DATA: + default: + length= ind_or_len; + } - param.actual_len= &length; + param.actual_len= &length; - if ( copy_rowdata(stmt,param,&net,&to) != SQL_SUCCESS ) - return(SQL_ERROR); - } + if (copy_rowdata(stmt, param, &net, &to) != SQL_SUCCESS) + return SQL_ERROR; } /* END OF for (ncol= 0; ncol < result->field_count; ncol++) */ @@ -1199,9 +1211,11 @@ dynstr_append_mem(ext_query, "),", 2); count++; - /* We have a limited capacity to shove data across the wire. - but we handle this by sending in multiple calls to exec_stmt_query(). */ - if ( ext_query->length + length >= (SQLLEN) net_buffer_length ) + /* + We have a limited capacity to shove data across the wire, but + we handle this by sending in multiple calls to exec_stmt_query() + */ + if (ext_query->length + length >= (SQLLEN) net_buffer_length) { break_insert= TRUE; break; @@ -1227,7 +1241,7 @@ stmt->stmt_options.rowStatusPtr[count]= SQL_ROW_ADDED; } - return(SQL_SUCCESS); + return SQL_SUCCESS; } @@ -1365,24 +1379,17 @@ dynstr_append_quoted_name(&dynQuery,table_name); dynstr_append_mem(&dynQuery,"(",1); - /* - for each *relevant* column append name to INSERT statement - */ - for ( nCol= 0; nCol < result->field_count; nCol++ ) + /* build list of column names */ + for (nCol= 0; nCol < result->field_count; nCol++) { - MYSQL_FIELD * field = mysql_fetch_field_direct( result, nCol ); - BIND * bind = stmt->bind + nCol; - - if ( !( bind->pcbValue && ( *bind->pcbValue == SQL_COLUMN_IGNORE ) ) ) - { - dynstr_append_quoted_name( &dynQuery, field->name ); - dynstr_append_mem( &dynQuery, ",", 1 ); - } + MYSQL_FIELD *field= mysql_fetch_field_direct(result, nCol); + dynstr_append_quoted_name(&dynQuery, field->name); + dynstr_append_mem(&dynQuery, ",", 1); } dynQuery.length--; /* Remove last ',' */ dynstr_append_mem(&dynQuery,") VALUES ",9); - /* process row(s) using our INSERT as base */ + /* process row(s) using our INSERT as base */ sqlRet= batch_insert(stmt, irow, &dynQuery); dynstr_free(&dynQuery); Index: test/bulk/my_bulk.c =================================================================== --- test/bulk/my_bulk.c (revision 250) +++ test/bulk/my_bulk.c (working copy) @@ -1,19 +1,24 @@ -/*************************************************************************** - my_bulk.c - description - ------------------- - begin : Fri Nov 16 2001 - copyright : (C) MySQL AB 1997-2001 - author : venu ( venu@mysql.com ) - ***************************************************************************/ +/* + Copyright (C) 1997-2007 MySQL AB -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + There are special exceptions to the terms and conditions of the GPL + as it is applied to this software. View the full text of the exception + in file LICENSE.exceptions in the top-level directory of this software + distribution. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ #include "mytest3.h" #define MAX_INSERT_COUNT 800 @@ -159,6 +164,175 @@ } /** + Bug #24306: SQLBulkOperations always uses indicator varables' values from + the first record +*/ +void t_bulk_insert_indicator(SQLHDBC hdbc, SQLHSTMT hstmt) +{ + SQLRETURN rc; + SQLINTEGER id[4], nData; + SQLLEN indicator[4], nLen; + + SQLExecDirect(hstmt, "DROP TABLE IF EXISTS my_bulk", SQL_NTS); + + rc= SQLExecDirect(hstmt, "CREATE TABLE my_bulk (id int default 5)", + SQL_NTS); + mystmt(hstmt, rc); + + SQLFreeStmt(hstmt, SQL_CLOSE); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_STATIC, 0); + mystmt(hstmt, rc); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)3, 0); + mystmt(hstmt, rc); + + rc= SQLBindCol(hstmt, 1, SQL_C_LONG, id, 0, indicator); + mystmt(hstmt, rc); + + rc= SQLExecDirect(hstmt, "SELECT id FROM my_bulk", SQL_NTS); + mystmt(hstmt, rc); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt_err(hstmt, rc == SQL_NO_DATA_FOUND, rc); + + id[0]= 1; indicator[0]= SQL_COLUMN_IGNORE; + id[1]= 2; indicator[1]= SQL_NULL_DATA; + id[2]= 3; indicator[2]= 0; + + rc= SQLBulkOperations(hstmt, SQL_ADD); + mystmt(hstmt, rc); + + SQLFreeStmt(hstmt, SQL_UNBIND); + SQLFreeStmt(hstmt, SQL_CLOSE); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)1, 0); + mystmt(hstmt, rc); + + rc= SQLExecDirect(hstmt, "SELECT id FROM my_bulk", SQL_NTS); + mystmt(hstmt, rc); + + rc= SQLBindCol(hstmt, 1, SQL_C_LONG, &nData, 0, &nLen); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt, rc); + my_assert(nData == 5 && nLen != SQL_NULL_DATA); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt, rc); + my_assert(nLen == SQL_NULL_DATA); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt, rc); + my_assert(nData == 3 && nLen != SQL_NULL_DATA); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt_err(hstmt, rc == SQL_NO_DATA_FOUND, rc); + + SQLFreeStmt(hstmt, SQL_CLOSE); +} + +/** + Simple structure for a row (just one element) plus an indicator column. +*/ +typedef struct { + SQLINTEGER val; + SQLLEN ind; +} row; + + +/** + This is related to the fix for Bug #24306 -- handling of row-wise binding, + plus handling of SQL_ATTR_ROW_BIND_OFFSET_PTR, within the context of + SQLBulkOperations(hstmt, SQL_ADD). +*/ +void t_bulk_insert_rows(SQLHDBC hdbc, SQLHSTMT hstmt) +{ + SQLRETURN rc; + row rows[3]; + SQLINTEGER nData, offset; + SQLLEN nLen; + + SQLExecDirect(hstmt, "DROP TABLE IF EXISTS my_bulk", SQL_NTS); + + rc= SQLExecDirect(hstmt, "CREATE TABLE my_bulk (id int default 5)", + SQL_NTS); + mystmt(hstmt, rc); + + SQLFreeStmt(hstmt, SQL_CLOSE); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_STATIC, 0); + mystmt(hstmt, rc); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)3, 0); + mystmt(hstmt, rc); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)sizeof(row), 0); + mystmt(hstmt, rc); + + rc= SQLBindCol(hstmt, 1, SQL_C_LONG, &rows[0].val, 0, &rows[0].ind); + mystmt(hstmt, rc); + + rc= SQLExecDirect(hstmt, "SELECT id FROM my_bulk", SQL_NTS); + mystmt(hstmt, rc); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt_err(hstmt, rc == SQL_NO_DATA_FOUND, rc); + + rows[0].val= 1; rows[0].ind= SQL_COLUMN_IGNORE; + rows[1].val= 2; rows[1].ind= SQL_NULL_DATA; + rows[2].val= 3; rows[2].ind= 0; + + rc= SQLBulkOperations(hstmt, SQL_ADD); + mystmt(hstmt, rc); + + /* Now re-insert the last row using SQL_ATTR_ROW_BIND_OFFSET_PTR */ + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)1, 0); + mystmt(hstmt, rc); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, + (SQLPOINTER)&offset, 0); + mystmt(hstmt, rc); + + offset= 2 * sizeof(row); + + rc= SQLBulkOperations(hstmt, SQL_ADD); + mystmt(hstmt, rc); + + SQLFreeStmt(hstmt, SQL_UNBIND); + SQLFreeStmt(hstmt, SQL_CLOSE); + + rc= SQLExecDirect(hstmt, "SELECT id FROM my_bulk", SQL_NTS); + mystmt(hstmt, rc); + + rc= SQLBindCol(hstmt, 1, SQL_C_LONG, &nData, 0, &nLen); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt, rc); + my_assert(nData == 5 && nLen != SQL_NULL_DATA); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt, rc); + my_assert(nLen == SQL_NULL_DATA); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt, rc); + my_assert(nData == 3 && nLen != SQL_NULL_DATA); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt, rc); + my_assert(nData == 3 && nLen != SQL_NULL_DATA); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt_err(hstmt, rc == SQL_NO_DATA_FOUND, rc); + + SQLFreeStmt(hstmt, SQL_CLOSE); +} + + +/** MAIN ROUTINE... */ int main(int argc, char *argv[]) @@ -183,7 +357,7 @@ else if ( narg == 3 ) mypwd = argv[3]; - } + } myconnect(&henv,&hdbc,&hstmt); if (driver_supports_setpos(hdbc)) @@ -192,6 +366,8 @@ t_bulk_insert(hdbc,hstmt); /* bulk inserts */ t_bulk_insert(hdbc,hstmt); /* bulk inserts */ t_bulk_insert(hdbc,hstmt); /* bulk inserts */ + t_bulk_insert_indicator(hdbc, hstmt); + t_bulk_insert_rows(hdbc, hstmt); } mydisconnect(&henv,&hdbc,&hstmt); Index: ChangeLog =================================================================== --- ChangeLog (revision 250) +++ ChangeLog (working copy) @@ -3,6 +3,8 @@ Functionality added or changed: Bugs fixed: + * SQLBulkOperations() and SQLSetPos() used the wrong indicator variable + values when batch-inserting rows. (Bug #24306) ----