Page 1 of 1

ZPlainMySQL5.pas: wrong definition for MYSQL_BIND record

Posted: 24.10.2007, 00:11
by _lmz
The definition of the MYSQL_BIND structure in ZPlainMySQL5.pas is wrong and leads to access violations when executing statements with bound parameters.

Current definition (SVN trunk):

Code: Select all

  MYSQL_BIND = record
    length:           PLongInt;
    is_null:          PByte;
    buffer:           PChar;
    buffer_type:      Cardinal;
    buffer_length:    LongInt;
    inter_buffer:     PByte;
    offset:           LongInt;
    internal_length:  LongInt;
    param_number:     Cardinal;
    long_data_used:   Byte;
    binary_data:      Byte;
    null_field:       Byte;
    internal_is_null: Byte;
    store_param_func: procedure(_net: NET; param: PMYSQL_BIND);
    fetch_result:     procedure(param: PMYSQL_BIND; row: PMYSQL_ROW);
  end;
Actual C definition

Code: Select all

typedef struct st_mysql_bind
{
  unsigned long	*length;          /* output length pointer */
  my_bool       *is_null;	  /* Pointer to null indicator */
  void		*buffer;	  /* buffer to get/put data */
  /* set this if you want to track data truncations happened during fetch */
  my_bool       *error;
  enum enum_field_types buffer_type;	/* buffer type */
  /* output buffer length, must be set when fetching str/binary */
  unsigned long buffer_length;
  unsigned char *row_ptr;         /* for the current data position */
  unsigned long offset;           /* offset position for char/binary fetch */
  unsigned long	length_value;     /* Used if length is 0 */
  unsigned int	param_number;	  /* For null count and error messages */
  unsigned int  pack_length;	  /* Internal length for packed data */
  my_bool       error_value;      /* used if error is 0 */
  my_bool       is_unsigned;      /* set if integer type is unsigned */
  my_bool	long_data_used;	  /* If used with mysql_send_long_data */
  my_bool	is_null_value;    /* Used if is_null is 0 */
  void (*store_param_func)(NET *net, struct st_mysql_bind *param);
  void (*fetch_result)(struct st_mysql_bind *, MYSQL_FIELD *,
                       unsigned char **row);
  void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *,
		      unsigned char **row);
} MYSQL_BIND;
Fixed definition:

Code: Select all

  MYSQL_BIND = record
    length:           PLongInt;
    is_null:          PByte;
    buffer:           PChar;
    error :           PByte; // new
    buffer_type:      Cardinal;
    buffer_length:    LongInt;
    inter_buffer:     PByte;
    offset:           LongInt;
    internal_length:  LongInt;
    param_number:     Cardinal;
    pack_length :     Cardinal; // new
    error_value :     Byte; // new
    is_unsigned:   Byte;
    long_data_used:      Byte;
    null_field:       Byte;
    store_param_func: procedure(_net: NET; param: PMYSQL_BIND); cdecl;
    fetch_result:     procedure(param: PMYSQL_BIND; fld : PMYSQL_FIELD; row: PMYSQL_ROW); cdecl;
    skip_result :     procedure(param: PMYSQL_BIND; fld : PMYSQL_FIELD; row: PMYSQL_ROW); cdecl;
  end;

Sample program (tested with server 5.0.24-community-nt) :

Code: Select all

program dbc;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Windows,
  ZClasses,
  ZDbcIntfs,
  ZCompatibility,
  ZDbcMySql,
  ZPlainMySQLDriver,
  ZPlainMySQL5;

var
  conn : IZConnection;
  stmt : IZStatement;
  // mysql specific
  drv : IZMySQLPlainDriver;
  conn_handle : Pointer;
  pstmt_handle : Pointer;
  binds : array [0..1] of MYSQL_BIND;
  buf : array [0..1023] of char;
  str_param_length : Integer;
  // end mysql specific
  i : Integer;
  query : string;
begin
try
  conn := DriverManager.GetConnection('zdbc:mysql-5://localhost/db?UID=uid;PWD=pwd');
  stmt := conn.CreateStatement;
  stmt.ExecuteUpdate('DROP TABLE IF EXISTS t');
  stmt.ExecuteUpdate('CREATE TABLE t ( x INTEGER, y VARCHAR(50) ) ENGINE=MEMORY');
  stmt.Close;
  stmt := nil;

  // get mysql specific bits
  drv := (conn as IZMySQLConnection).GetPlainDriver;
  conn_handle := (conn as IZMySQLConnection).GetConnectionHandle;

  pstmt_handle := drv.InitializePrepStmt(conn_handle);
  ZeroMemory(@binds, SizeOf(binds));
  query := 'INSERT INTO t (y, x) VALUES (?, ?)';
  drv.PrepareStmt(pstmt_handle, PAnsiChar(query), Length(query));
  binds[1].buffer_type := FIELD_TYPE_LONG;
  binds[1].buffer := PAnsiChar(@i);
  binds[1].is_null := nil;
  binds[1].length := nil;
  binds[0].buffer_type := FIELD_TYPE_STRING;
  binds[0].buffer := buf;
  binds[0].buffer_length := 1024;
  binds[0].is_null := nil;
  binds[0].length := @str_param_length;
  writeln('markers: ', drv.GetPreparedBindMarkers(pstmt_handle));
  drv.BindParameters(pstmt_handle, @binds);
  for i := 1 to 3 do begin
    buf := 'xyzzy';
    str_param_length := 5;
    drv.ExecuteStmt(pstmt_handle);
  end;
  drv.ClosePrepStmt(pstmt_handle);

  conn.Close;
  conn := nil;
  writeln('press any key');
  readln;
except
on e: Exception do begin writeln(e.message); readln; end;
end;
end.
Hopefully in the future zeos will support real bind parameter for MySQL.

Posted: 24.10.2007, 08:44
by mdaems
Hi _lmz,

If you would use SVN Testing branch with the right zeos.inc settings you could experiment with the brand new 'prepared' statement implementation. I admit, it's just the old implementation using the newe API functions. Statements aren't reused yet. Problem is that zeoslib general design isn't prepared for really prepared statements.
We are looking for people whow want to help building/testing this feature. First step is finding a mysql user with some experience who wants to test the current implementation a little. I know it works for some queries, but I also know it doesn't work for all queries.

About the patch you've sent : use MYSQL_BIND2 for mysql5. Unfortunately the mysql developers changed the layout of the bind structure (version 4.1, I believe)

I tried your sample project in Delphi7. Didn't get an AV but the records didn't get inserted either.
Changing 'ZPlainMySQL5' into 'ZPlainMySQLConstants' and 'binds : array [0..1] of MYSQL_BIND;' to 'binds : array [0..1] of MYSQL_BIND2;' did the trick...