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)
 
 ----