zeoslib  UNKNOWN
 All Files
ZDbcMySqlStatement.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { MySQL Database Connectivity Classes }
5 { }
6 { Originally written by Sergey Seroukhov }
7 { }
8 {*********************************************************}
9 
10 {@********************************************************}
11 { Copyright (c) 1999-2012 Zeos Development Group }
12 { }
13 { License Agreement: }
14 { }
15 { This library is distributed in the hope that it will be }
16 { useful, but WITHOUT ANY WARRANTY; without even the }
17 { implied warranty of MERCHANTABILITY or FITNESS FOR }
18 { A PARTICULAR PURPOSE. See the GNU Lesser General }
19 { Public License for more details. }
20 { }
21 { The source code of the ZEOS Libraries and packages are }
22 { distributed under the Library GNU General Public }
23 { License (see the file COPYING / COPYING.ZEOS) }
24 { with the following modification: }
25 { As a special exception, the copyright holders of this }
26 { library give you permission to link this library with }
27 { independent modules to produce an executable, }
28 { regardless of the license terms of these independent }
29 { modules, and to copy and distribute the resulting }
30 { executable under terms of your choice, provided that }
31 { you also meet, for each linked independent module, }
32 { the terms and conditions of the license of that module. }
33 { An independent module is a module which is not derived }
34 { from or based on this library. If you modify this }
35 { library, you may extend this exception to your version }
36 { of the library, but you are not obligated to do so. }
37 { If you do not wish to do so, delete this exception }
38 { statement from your version. }
39 { }
40 { }
41 { The project web site is located on: }
42 { http://zeos.firmos.at (FORUM) }
43 { http://sourceforge.net/p/zeoslib/tickets/ (BUGTRACKER)}
44 { svn://svn.code.sf.net/p/zeoslib/code-0/trunk (SVN) }
45 { }
46 { http://www.sourceforge.net/projects/zeoslib. }
47 { }
48 { }
49 { Zeos Development Group. }
50 {********************************************************@}
51 
52 unit ZDbcMySqlStatement;
53 
54 interface
55 
56 {$I ZDbc.inc}
57 
58 uses
59  Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} SysUtils,
60  ZClasses, ZDbcIntfs, ZDbcStatement, ZDbcMySql, ZVariant, ZPlainMySqlDriver,
61  ZPlainMySqlConstants, ZCompatibility, ZDbcLogging;
62 
63 type
64 
65  {** Represents a MYSQL specific connection interface. }
66  IZMySQLStatement = interface (IZStatement)
67  ['{A05DB91F-1E40-46C7-BF2E-25D74978AC83}']
68 
69  function IsUseResult: Boolean;
70  function IsPreparedStatement: Boolean;
71  function GetStmtHandle: PZMySqlPrepStmt;
72  end;
73 
74  {** Represents a MYSQL prepared Statement specific connection interface. }
75  IZMySQLPreparedStatement = interface (IZMySQLStatement)
76  ['{A05DB91F-1E40-46C7-BF2E-25D74978AC83}']
77  end;
78 
79  {** Implements Generic MySQL Statement. }
80  TZMySQLStatement = class(TZAbstractStatement, IZMySQLStatement)
81  private
82  FHandle: PZMySQLConnect;
83  FPlainDriver: IZMySQLPlainDriver;
84  FUseResult: Boolean;
85 
86  function CreateResultSet(const SQL: string): IZResultSet;
87  function GetStmtHandle : PZMySqlPrepStmt;
88  public
89  constructor Create(PlainDriver: IZMySQLPlainDriver;
90  Connection: IZConnection; Info: TStrings; Handle: PZMySQLConnect);
91 
92  function ExecuteQuery(const SQL: RawByteString): IZResultSet; override;
93  function ExecuteUpdate(const SQL: RawByteString): Integer; override;
94  function Execute(const SQL: RawByteString): Boolean; override;
95 
96  function GetMoreResults: Boolean; override;
97 
98  function IsUseResult: Boolean;
99  function IsPreparedStatement: Boolean;
100  end;
101 
102  {** Implements Prepared SQL Statement. }
103  TZMySQLEmulatedPreparedStatement = class(TZEmulatedPreparedStatement)
104  private
105  FHandle: PZMySQLConnect;
106  FPlainDriver: IZMySQLPlainDriver;
107  FUseDefaults: Boolean;
108  protected
109  function CreateExecStatement: IZStatement; override;
110  function PrepareAnsiSQLParam(ParamIndex: Integer): RawByteString; override;
111  public
112  constructor Create(PlainDriver: IZMySQLPlainDriver;
113  Connection: IZConnection; const SQL: string; Info: TStrings;
114  Handle: PZMySQLConnect);
115  end;
116 
117  TZMysqlColumnBuffer = Array of PDOBindRecord2;
118  { TZMySQLBindBuffer }
119  {** Encapsulates a MySQL bind buffer. }
120  TZMySQLAbstractBindBuffer = class(TZAbstractObject)
121  protected
122  FAddedColumnCount : Integer;
123  FBindOffsets: MYSQL_BINDOFFSETS;
124  FBindArray: Array of byte;
125  FPColumnArray: ^TZMysqlColumnBuffer;
126  public
127  constructor Create(PlainDriver:IZMysqlPlainDriver;
128  const BindCount : Integer; var ColumnArray:TZMysqlColumnBuffer);
129  function GetColumnArray : TZMysqlColumnBuffer;
130  function GetBufferAddress : Pointer;
131  function GetBufferType(ColumnIndex: Integer): TMysqlFieldTypes;
132  function GetBufferIsSigned(ColumnIndex: Integer): Boolean;
133  end;
134 
135  {** Encapsulates a MySQL bind buffer for ResultSets. }
136  TZMySQLResultSetBindBuffer = class(TZMySQLAbstractBindBuffer)
137  public
138  procedure AddColumn(PlainDriver: IZMysqlPlainDriver; const FieldHandle: PZMySQLField);
139  end;
140 
141  {** Encapsulates a MySQL bind buffer for updates. }
142  TZMySQLParamBindBuffer = class(TZMySQLAbstractBindBuffer)
143  public
144  procedure AddColumn(buffertype: TMysqlFieldTypes; const field_length: integer;
145  const largeblobparameter: boolean);
146  end;
147  {** Implements Prepared SQL Statement. }
148 
149  { TZMySQLPreparedStatement }
150 
151  TZMySQLPreparedStatement = class(TZAbstractPreparedStatement,IZMySQLPreparedStatement)
152  private
153  FHandle: PZMySQLConnect;
154  FMySQLConnection: IZMySQLConnection;
155  FStmtHandle: PZMySqlPrepStmt;
156  FPlainDriver: IZMySQLPlainDriver;
157  FUseResult: Boolean;
158  FUseDefaults: Boolean;
159 
160  FColumnArray: TZMysqlColumnBuffer;
161  FParamBindBuffer: TZMySQLParamBindBuffer;
162 
163  function CreateResultSet(const SQL: string): IZResultSet;
164 
165  function getFieldType (testVariant: TZVariant): TMysqlFieldTypes;
166  protected
167  function GetStmtHandle : PZMySqlPrepStmt;
168  procedure BindInParameters; override;
169  procedure UnPrepareInParameters; override;
170  public
171  property StmtHandle: PZMySqlPrepStmt read GetStmtHandle;
172  constructor Create(PlainDriver: IZMysqlPlainDriver; Connection: IZConnection;
173  const SQL: string; Info: TStrings);
174  procedure Prepare; override;
175  destructor Destroy; override;
176 
177  function ExecuteQueryPrepared: IZResultSet; override;
178  function ExecuteUpdatePrepared: Integer; override;
179  function ExecutePrepared: Boolean; override;
180 
181  function IsUseResult: Boolean;
182  function IsPreparedStatement: Boolean;
183  end;
184 
185  {** Implements callable Postgresql Statement. }
186  TZMySQLCallableStatement = class(TZAbstractCallableStatement, IZMySQLStatement,
187  IZParamNamedCallableStatement)
188  private
189  FPlainDriver: IZMysqlPlainDriver;
190  FHandle: PZMySQLConnect;
191  FQueryHandle: PZMySQLResult;
192  FUseResult: Boolean;
193  FParamNames: array [0..1024] of String;
194  FParamTypeNames: array [0..1024] of String;
195  FUseDefaults: Boolean;
196  function GetCallSQL: RawByteString;
197  function GetOutParamSQL: String;
198  function GetSelectFunctionSQL: RawByteString;
199  function PrepareAnsiSQLParam(ParamIndex: Integer): RawByteString;
200  function GetStmtHandle : PZMySqlPrepStmt;
201  protected
202  procedure ClearResultSets; override;
203  procedure BindInParameters; override;
204  function CreateResultSet(const SQL: string): IZResultSet;
205  procedure FetchOutParams(ResultSet: IZResultSet);
206  procedure RegisterParamTypeAndName(const ParameterIndex:integer;
207  const ParamTypeName, ParamName: String; Const ColumnSize, Precision: Integer);
208  public
209  constructor Create(PlainDriver: IZMySQLPlainDriver;
210  Connection: IZConnection; const SQL: string; Info: TStrings;
211  Handle: PZMySQLConnect);
212 
213  function Execute(const SQL: RawByteString): Boolean; override;
214  function ExecuteQuery(const SQL: RawByteString): IZResultSet; override;
215  function ExecuteUpdate(const SQL: RawByteString): Integer; override;
216 
217  function ExecuteQueryPrepared: IZResultSet; override;
218  function ExecuteUpdatePrepared: Integer; override;
219 
220  function IsUseResult: Boolean;
221  function IsPreparedStatement: Boolean;
222 
223  function HasMoreResultSets: Boolean; override;
224  function GetFirstResultSet: IZResultSet; override;
225  function GetPreviousResultSet: IZResultSet; override;
226  function GetNextResultSet: IZResultSet; override;
227  function GetLastResultSet: IZResultSet; override;
228  function BOR: Boolean; override;
229  function EOR: Boolean; override;
230  function GetResultSetByIndex(const Index: Integer): IZResultSet; override;
231  function GetResultSetCount: Integer; override;
232  end;
233 
234 implementation
235 
236 uses
237  Types, Math, DateUtils, ZDbcMySqlUtils, ZDbcMySqlResultSet, ZSysUtils,
238  ZDbcResultSetMetadata, ZMessages, ZDbcCachedResultSet, ZDbcUtils, ZEncoding,
239  ZDbcResultSet{$IFDEF WITH_UNITANSISTRINGS}, AnsiStrings{$ENDIF};
240 
241 { TZMySQLStatement }
242 
243 {**
244  Constructs this object and assignes the main properties.
245  @param PlainDriver a native MySQL plain driver.
246  @param Connection a database connection object.
247  @param Handle a connection handle pointer.
248  @param Info a statement parameters.
249 }
250 constructor TZMySQLStatement.Create(PlainDriver: IZMySQLPlainDriver;
251  Connection: IZConnection; Info: TStrings; Handle: PZMySQLConnect);
252 begin
253  inherited Create(Connection, Info);
254  FHandle := Handle;
255  FPlainDriver := PlainDriver;
256  ResultSetType := rtScrollInsensitive;
257 
258  FUseResult := StrToBoolEx(DefineStatementParameter(Self, 'useresult', 'false'));
259 end;
260 
261 {**
262  Checks is use result should be used in result sets.
263  @return <code>True</code> use result in result sets,
264  <code>False</code> store result in result sets.
265 }
266 function TZMySQLStatement.IsUseResult: Boolean;
267 begin
268  Result := FUseResult;
269 end;
270 
271 {**
272  Checks if this is a prepared mysql statement.
273  @return <code>False</code> This is not a prepared mysql statement.
274 }
275 function TZMySQLStatement.IsPreparedStatement: Boolean;
276 begin
277  Result := False;
278 end;
279 
280 function TZMySQLStatement.GetStmtHandle: PZMySqlPrepStmt;
281 begin
282  Result := nil;
283 end;
284 
285 {**
286  Creates a result set based on the current settings.
287  @return a created result set object.
288 }
289 function TZMySQLStatement.CreateResultSet(const SQL: string): IZResultSet;
290 var
291  CachedResolver: TZMySQLCachedResolver;
292  NativeResultSet: TZMySQLResultSet;
293  CachedResultSet: TZCachedResultSet;
294 begin
295  NativeResultSet := TZMySQLResultSet.Create(FPlainDriver, Self, SQL, FHandle,
296  FUseResult, nil);
297  NativeResultSet.SetConcurrency(rcReadOnly);
298  if (GetResultSetConcurrency <> rcReadOnly) or (FUseResult
299  and (GetResultSetType <> rtForwardOnly)) then
300  begin
301  CachedResolver := TZMySQLCachedResolver.Create(FPlainDriver, FHandle, Self,
302  NativeResultSet.GetMetaData);
303  CachedResultSet := TZCachedResultSet.Create(NativeResultSet, SQL,
304  CachedResolver, ConSettings);
305  CachedResultSet.SetConcurrency(GetResultSetConcurrency);
306  Result := CachedResultSet;
307  end
308  else
309  Result := NativeResultSet;
310 end;
311 
312 
313 {**
314  Executes an SQL statement that returns a single <code>ResultSet</code> object.
315  @param sql typically this is a static SQL <code>SELECT</code> statement
316  @return a <code>ResultSet</code> object that contains the data produced by the
317  given query; never <code>null</code>
318 }
319 function TZMySQLStatement.ExecuteQuery(const SQL: RawByteString): IZResultSet;
320 begin
321  ASQL := SQL;
322  Result := nil;
323  if FPlainDriver.ExecQuery(FHandle, PAnsiChar(ASQL)) = 0 then
324  begin
325  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, LogSQL);
326  if not FPlainDriver.ResultSetExists(FHandle) then
327  raise EZSQLException.Create(SCanNotOpenResultSet);
328  Result := CreateResultSet(LogSQL);
329  end
330  else
331  CheckMySQLError(FPlainDriver, FHandle, lcExecute, LogSQL);
332 end;
333 
334 {**
335  Executes an SQL <code>INSERT</code>, <code>UPDATE</code> or
336  <code>DELETE</code> statement. In addition,
337  SQL statements that return nothing, such as SQL DDL statements,
338  can be executed.
339 
340  @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
341  <code>DELETE</code> statement or an SQL statement that returns nothing
342  @return either the row count for <code>INSERT</code>, <code>UPDATE</code>
343  or <code>DELETE</code> statements, or 0 for SQL statements that return nothing
344 }
345 function TZMySQLStatement.ExecuteUpdate(const SQL: RawByteString): Integer;
346 var
347  QueryHandle: PZMySQLResult;
348  HasResultset : Boolean;
349 begin
350  ASQL := SQL;
351  Result := -1;
352  if FPlainDriver.ExecQuery(FHandle, PAnsichar(ASQL)) = 0 then
353  begin
354  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, LogSQL);
355  HasResultSet := FPlainDriver.ResultSetExists(FHandle);
356  { Process queries with result sets }
357  if HasResultSet then
358  begin
359  QueryHandle := FPlainDriver.StoreResult(FHandle);
360  if QueryHandle <> nil then
361  begin
362  Result := FPlainDriver.GetRowCount(QueryHandle);
363  FPlainDriver.FreeResult(QueryHandle);
364  end
365  else
366  Result := FPlainDriver.GetAffectedRows(FHandle);
367  while(FPlainDriver.RetrieveNextRowset(FHandle) = 0) do
368  begin
369  QueryHandle := FPlainDriver.StoreResult(FHandle);
370  if QueryHandle <> nil then
371  begin
372  FPlainDriver.FreeResult(QueryHandle);
373  end;
374  end;
375  end
376  { Process regular query }
377  else
378  Result := FPlainDriver.GetAffectedRows(FHandle);
379  end
380  else
381  CheckMySQLError(FPlainDriver, FHandle, lcExecute, LogSQL);
382  LastUpdateCount := Result;
383 end;
384 
385 {**
386  Executes an SQL statement that may return multiple results.
387  Under some (uncommon) situations a single SQL statement may return
388  multiple result sets and/or update counts. Normally you can ignore
389  this unless you are (1) executing a stored procedure that you know may
390  return multiple results or (2) you are dynamically executing an
391  unknown SQL string. The methods <code>execute</code>,
392  <code>getMoreResults</code>, <code>getResultSet</code>,
393  and <code>getUpdateCount</code> let you navigate through multiple results.
394 
395  The <code>execute</code> method executes an SQL statement and indicates the
396  form of the first result. You can then use the methods
397  <code>getResultSet</code> or <code>getUpdateCount</code>
398  to retrieve the result, and <code>getMoreResults</code> to
399  move to any subsequent result(s).
400 
401  @param sql any SQL statement
402  @return <code>true</code> if the next result is a <code>ResultSet</code> object;
403  <code>false</code> if it is an update count or there are no more results
404 }
405 function TZMySQLStatement.Execute(const SQL: RawByteString): Boolean;
406 var
407  HasResultset : Boolean;
408 begin
409  ASQL := SQL;
410  Result := False;
411  if FPlainDriver.ExecQuery(FHandle, PAnsiChar(ASQL)) = 0 then
412  begin
413  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, LogSQL);
414  HasResultSet := FPlainDriver.ResultSetExists(FHandle);
415  { Process queries with result sets }
416  if HasResultSet then
417  begin
418  Result := True;
419  LastResultSet := CreateResultSet(LogSQL);
420  end
421  { Processes regular query. }
422  else
423  begin
424  Result := False;
425  LastUpdateCount := FPlainDriver.GetAffectedRows(FHandle);
426  end;
427  end
428  else
429  CheckMySQLError(FPlainDriver, FHandle, lcExecute, LogSQL);
430 end;
431 
432 {**
433  Moves to a <code>Statement</code> object's next result. It returns
434  <code>true</code> if this result is a <code>ResultSet</code> object.
435  This method also implicitly closes any current <code>ResultSet</code>
436  object obtained with the method <code>getResultSet</code>.
437 
438  <P>There are no more results when the following is true:
439  <PRE>
440  <code>(!getMoreResults() && (getUpdateCount() == -1)</code>
441  </PRE>
442 
443  @return <code>true</code> if the next result is a <code>ResultSet</code> object;
444  <code>false</code> if it is an update count or there are no more results
445  @see #execute
446 }
447 function TZMySQLStatement.GetMoreResults: Boolean;
448 var
449  AStatus: integer;
450 begin
451  Result := inherited GetMoreResults;
452  if FPlainDriver.GetClientVersion >= 40100 then
453  begin
454  AStatus := FPlainDriver.RetrieveNextRowset(FHandle);
455  if AStatus > 0 then
456  CheckMySQLError(FPlainDriver, FHandle, lcExecute, SSQL)
457  else
458  Result := (AStatus = 0);
459 
460  if LastResultSet <> nil then
461  LastResultSet.Close;
462  LastResultSet := nil;
463  LastUpdateCount := -1;
464  if FPlainDriver.ResultSetExists(FHandle) then
465  LastResultSet := CreateResultSet(SSQL)
466  else
467  LastUpdateCount := FPlainDriver.GetAffectedRows(FHandle);
468  end;
469 end;
470 
471 { TZMySQLEmulatedPreparedStatement }
472 
473 {**
474  Constructs this object and assignes the main properties.
475  @param PlainDriver a native MySQL Plain driver.
476  @param Connection a database connection object.
477  @param Info a statement parameters.
478  @param Handle a connection handle pointer.
479 }
480 constructor TZMySQLEmulatedPreparedStatement.Create(PlainDriver: IZMySQLPlainDriver;
481  Connection: IZConnection; const SQL: string; Info: TStrings; Handle: PZMySQLConnect);
482 begin
483  inherited Create(Connection, SQL, Info);
484  FHandle := Handle;
485  FPlainDriver := PlainDriver;
486  ResultSetType := rtScrollInsensitive;
487  FUseDefaults := StrToBoolEx(DefineStatementParameter(Self, 'defaults', 'true'));
488 end;
489 
490 {**
491  Creates a temporary statement which executes queries.
492  @param Info a statement parameters.
493  @return a created statement object.
494 }
495 function TZMySQLEmulatedPreparedStatement.CreateExecStatement: IZStatement;
496 begin
497  Result := TZMySQLStatement.Create(FPlainDriver, Connection, Info,FHandle);
498 end;
499 
500 {**
501  Prepares an SQL parameter for the query.
502  @param ParameterIndex the first parameter is 1, the second is 2, ...
503  @return a string representation of the parameter.
504 }
505 function TZMySQLEmulatedPreparedStatement.PrepareAnsiSQLParam(ParamIndex: Integer): RawByteString;
506 var
507  Value: TZVariant;
508  TempBytes: TByteDynArray;
509  TempBlob: IZBlob;
510  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond: Word;
511 begin
512  TempBytes := nil;
513  if InParamCount <= ParamIndex then
514  raise EZSQLException.Create(SInvalidInputParameterCount);
515 
516  Value := InParamValues[ParamIndex];
517  if DefVarManager.IsNull(Value) then
518  if FUseDefaults and (InParamDefaultValues[ParamIndex] <> '') then
519  Result := ConSettings^.ConvFuncs.ZStringToRaw(InParamDefaultValues[ParamIndex],
520  ConSettings^.CTRL_CP, ConSettings^.ClientCodePage^.CP)
521  else
522  Result := 'NULL'
523  else
524  begin
525  case InParamTypes[ParamIndex] of
526  stBoolean:
527  if SoftVarManager.GetAsBoolean(Value) then
528  Result := '''Y'''
529  else
530  Result := '''N''';
531  stByte, stShort, stInteger, stLong, stBigDecimal, stFloat, stDouble:
532  Result := RawByteString(SoftVarManager.GetAsString(Value));
533  stBytes:
534  begin
535  TempBytes := SoftVarManager.GetAsBytes(Value);
536  Result := GetSQLHexAnsiString(PAnsiChar(TempBytes), Length(TempBytes));
537  end;
538  stString:
539  Result := FPlainDriver.EscapeString(FHandle, ZPlainString(SoftVarManager.GetAsString(Value)), ConSettings, True);
540  stUnicodeString:
541  Result := FPlainDriver.EscapeString(FHandle, ZPlainString(SoftVarManager.GetAsUnicodeString(Value)), ConSettings, True);
542  stDate:
543  begin
544  DecodeDateTime(SoftVarManager.GetAsDateTime(Value),
545  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
546  Result := RawByteString('''' + Format('%0.4d-%0.2d-%0.2d',
547  [AYear, AMonth, ADay]) + '''');
548  end;
549  stTime:
550  begin
551  DecodeDateTime(SoftVarManager.GetAsDateTime(Value),
552  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
553  Result := RawByteString('''' + Format('%0.2d:%0.2d:%0.2d',
554  [AHour, AMinute, ASecond]) + '''');
555  end;
556  stTimestamp:
557  begin
558  DecodeDateTime(SoftVarManager.GetAsDateTime(Value),
559  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
560  Result := RawByteString('''' + Format('%0.4d-%0.2d-%0.2d %0.2d:%0.2d:%0.2d',
561  [AYear, AMonth, ADay, AHour, AMinute, ASecond]) + '''');
562  end;
563  stAsciiStream, stUnicodeStream, stBinaryStream:
564  begin
565  TempBlob := DefVarManager.GetAsInterface(Value) as IZBlob;
566  if not TempBlob.IsEmpty then
567  begin
568  case InParamTypes[ParamIndex] of
569  stBinaryStream:
570  Result := GetSQLHexAnsiString(PAnsichar(TempBlob.GetBuffer), TempBlob.Length);
571  else
572  Result := FPlainDriver.EscapeString(FHandle,
573  GetValidatedAnsiStringFromBuffer(TempBlob.GetBuffer,
574  TempBlob.Length, TempBlob.WasDecoded, ConSettings),
575  ConSettings, True);
576  end;
577  end
578  else
579  Result := 'NULL';
580  end;
581  end;
582  end;
583 end;
584 
585 { TZMySQLPreparedStatement }
586 
587 {**
588  Constructs this object and assignes the main properties.
589  @param PlainDriver a Oracle plain driver.
590  @param Connection a database connection object.
591  @param Info a statement parameters.
592  @param Handle a connection handle pointer.
593 }
594 constructor TZMySQLPreparedStatement.Create(
595  PlainDriver: IZMySQLPlainDriver; Connection: IZConnection;
596  const SQL: string; Info: TStrings);
597 begin
598  inherited Create(Connection, SQL, Info);
599  FMySQLConnection := Connection as IZMySQLConnection;
600  FHandle := FMysqlConnection.GetConnectionHandle;
601  FPlainDriver := PlainDriver;
602  ResultSetType := rtScrollInsensitive;
603 
604  FUseResult := StrToBoolEx(DefineStatementParameter(Self, 'useresult', 'false'));
605  FUseDefaults := StrToBoolEx(DefineStatementParameter(Self, 'defaults', 'true'));
606 end;
607 
608 {**
609  Destroys this object and cleanups the memory.
610 }
611 destructor TZMySQLPreparedStatement.Destroy;
612 begin
613  FStmtHandle := FPlainDriver.ClosePrepStmt(FStmtHandle);
614  inherited Destroy;
615 end;
616 
617 procedure TZMySQLPreparedStatement.Prepare;
618 begin
619  if not Prepared then
620  begin
621  FStmtHandle := FPlainDriver.InitializePrepStmt(FHandle);
622  if (FStmtHandle = nil) then
623  begin
624  CheckMySQLPrepStmtError(FPlainDriver, FStmtHandle, lcPrepStmt, SFailedtoInitPrepStmt);
625  exit;
626  end;
627  if (FPlainDriver.PrepareStmt(FStmtHandle, PAnsiChar(ASQL), length(ASQL)) <> 0) then
628  begin
629  CheckMySQLPrepStmtError(FPlainDriver, FStmtHandle, lcPrepStmt, SFailedtoPrepareStmt);
630  exit;
631  end;
632  LogPrepStmtMessage(lcPrepStmt, SQL);
633  inherited Prepare;
634  end;
635 end;
636 
637 {**
638  Checks is use result should be used in result sets.
639  @return <code>True</code> use result in result sets,
640  <code>False</code> store result in result sets.
641 }
642 function TZMySQLPreparedStatement.IsUseResult: Boolean;
643 begin
644  Result := FUseResult;
645 end;
646 
647 {**
648  Checks if this is a prepared mysql statement.
649  @return <code>True</code> This is a prepared mysql statement.
650 }
651 function TZMySQLPreparedStatement.IsPreparedStatement: Boolean;
652 begin
653  Result := True;
654 end;
655 
656 {**
657  Creates a result set based on the current settings.
658  @return a created result set object.
659 }
660 function TZMySQLPreparedStatement.CreateResultSet(const SQL: string): IZResultSet;
661 var
662  CachedResolver: TZMySQLCachedResolver;
663  NativeResultSet: TZMySQLPreparedResultSet;
664  CachedResultSet: TZCachedResultSet;
665 begin
666  NativeResultSet := TZMySQLPreparedResultSet.Create(FPlainDriver, Self, SQL, FHandle,
667  FUseResult);
668  NativeResultSet.SetConcurrency(rcReadOnly);
669  if (GetResultSetConcurrency <> rcReadOnly) or (FUseResult
670  and (GetResultSetType <> rtForwardOnly)) then
671  begin
672  CachedResolver := TZMySQLCachedResolver.Create(FPlainDriver, FHandle, (Self as IZMysqlStatement),
673  NativeResultSet.GetMetaData);
674  CachedResultSet := TZCachedResultSet.Create(NativeResultSet, SQL,
675  CachedResolver, ConSettings);
676  CachedResultSet.SetConcurrency(GetResultSetConcurrency);
677  Result := CachedResultSet;
678  end
679  else
680  Result := NativeResultSet;
681 end;
682 
683 procedure TZMysqlPreparedStatement.BindInParameters;
684 var
685  PBuffer: Pointer;
686  year, month, day, hour, minute, second, millisecond: word;
687  MyType: TMysqlFieldTypes;
688  I, OffSet, PieceSize: integer;
689  TempBlob: IZBlob;
690  TempAnsi: RawByteString;
691 begin
692  //http://dev.mysql.com/doc/refman/5.0/en/storage-requirements.html
693  if InParamCount = 0 then
694  exit;
695  { Initialize Bind Array and Column Array }
696  FParamBindBuffer := TZMySqlParamBindBuffer.Create(FPlainDriver,InParamCount,FColumnArray);
697 
698  For I := 0 to InParamCount - 1 do
699  begin
700  if (InParamValues[I].VType = vtNull) and (InParamDefaultValues[I] <> '') and
701  StrToBoolEx(DefineStatementParameter(Self, 'defaults', 'true')) then
702  SoftVarManager.SetAsString(InParamValues[I], Copy(InParamDefaultValues[I], 2, Length(InParamDefaultValues[I])-2));
703  MyType := GetFieldType(InParamValues[I]);
704  case MyType of
705  FIELD_TYPE_VARCHAR:
706  begin
707  TempAnsi := ZPlainString(InParamValues[I].VUnicodeString);
708  FParamBindBuffer.AddColumn(FIELD_TYPE_STRING, Length(TempAnsi),false);
709  end;
710  FIELD_TYPE_STRING:
711  begin
712  TempAnsi := ZPlainString(InParamValues[I].VString);
713  FParamBindBuffer.AddColumn(FIELD_TYPE_STRING, Length(TempAnsi),false);
714  end;
715  FIELD_TYPE_BLOB:
716  begin
717  TempBlob := (InParamValues[I].VInterface as IZBlob);
718  if TempBlob.IsEmpty then
719  DefVarManager.SetNull(InParamValues[I])
720  else
721  if InParamTypes[I] = stBinaryStream then
722  FParamBindBuffer.AddColumn(FIELD_TYPE_BLOB, TempBlob.Length, TempBlob.Length > ChunkSize)
723  else
724  begin
725  TempAnsi := GetValidatedAnsiStringFromBuffer(TempBlob.GetBuffer,
726  TempBlob.Length, TempBlob.WasDecoded, ConSettings);
727  TempBlob := TZAbstractBlob.CreateWithData(PAnsiChar(TempAnsi), Length(TempAnsi));
728  TempBlob.SetString(TempAnsi);
729  InParamValues[I].VInterface := TempBlob;
730  FParamBindBuffer.AddColumn(FIELD_TYPE_STRING, TempBlob.Length, TempBlob.Length > ChunkSize);
731  end;
732  end;
733  FIELD_TYPE_TINY:
734  FParamBindBuffer.AddColumn(FIELD_TYPE_STRING,1,false);
735  FIELD_TYPE_TINY_BLOB:
736  FParamBindBuffer.AddColumn(MyType,Length(InParamValues[i].VBytes),false);
737  else
738  FParamBindBuffer.AddColumn(MyType,getMySQLFieldSize(MyType, 0),false);
739  end;
740  PBuffer := @FColumnArray[I].buffer[0];
741 
742  if InParamValues[I].VType=vtNull then
743  FColumnArray[I].is_null := 1
744  else
745  FColumnArray[I].is_null := 0;
746  case FParamBindBuffer.GetBufferType(I+1) of
747 
748  FIELD_TYPE_FLOAT: Single(PBuffer^) := InParamValues[I].VFloat;
749  FIELD_TYPE_DOUBLE: Double(PBuffer^) := InParamValues[I].VFloat;
750  FIELD_TYPE_STRING:
751  case MyType of
752  FIELD_TYPE_TINY:
753  if InParamValues[I].VBoolean then
754  PAnsiChar(PBuffer)^ := 'Y'
755  else
756  PAnsiChar(PBuffer)^ := 'N';
757  FIELD_TYPE_VARCHAR:
758  System.Move(PAnsiChar(TempAnsi)^, PBuffer^, Length(TempAnsi));
759  FIELD_TYPE_BLOB:
760  if not (Length(TempAnsi) > ChunkSize ) then
761  System.Move(PAnsiChar(TempAnsi)^, PBuffer^, Length(TempAnsi));
762  else
763  System.Move(PAnsiChar(TempAnsi)^, PBuffer^, Length(TempAnsi));
764  end;
765  FIELD_TYPE_LONGLONG: Int64(PBuffer^) := InParamValues[I].VInteger;
766  FIELD_TYPE_DATETIME:
767  begin
768  DecodeDateTime(InParamValues[I].VDateTime, Year, Month, Day, hour, minute, second, millisecond);
769  PMYSQL_TIME(PBuffer)^.year := year;
770  PMYSQL_TIME(PBuffer)^.month := month;
771  PMYSQL_TIME(PBuffer)^.day := day;
772  PMYSQL_TIME(PBuffer)^.hour := hour;
773  PMYSQL_TIME(PBuffer)^.minute := minute;
774  PMYSQL_TIME(PBuffer)^.second := second;
775  PMYSQL_TIME(PBuffer)^.second_part := millisecond;
776  end;
777  FIELD_TYPE_TINY_BLOB:
778  System.Move(PAnsiChar(InParamValues[i].VBytes)^, PBuffer^, Length(InParamValues[i].VBytes));
779  FIELD_TYPE_MEDIUM_BLOB, FIELD_TYPE_LONG_BLOB,
780  FIELD_TYPE_BLOB:
781  begin
782  if TempBlob.Length<=ChunkSize then
783  System.Move(TempBlob.GetBuffer^, PBuffer^, TempBlob.Length);
784  TempBlob := nil;
785  end;
786  FIELD_TYPE_NULL:;
787  end;
788  end;
789 
790  if (FPlainDriver.BindParameters(FStmtHandle, FParamBindBuffer.GetBufferAddress) <> 0) then
791  begin
792  checkMySQLPrepStmtError (FPlainDriver, FStmtHandle, lcPrepStmt, SBindingFailure);
793  exit;
794  end;
795  inherited BindInParameters;
796 
797  // Send large blobs in chuncks
798  For I := 0 to InParamCount - 1 do
799  begin
800  if FParamBindBuffer.GetBufferType(I+1) in [FIELD_TYPE_STRING,FIELD_TYPE_BLOB] then
801  begin
802  MyType := GetFieldType(InParamValues[I]);
803  if MyType = FIELD_TYPE_BLOB then
804  begin
805  TempBlob := (InParamValues[I].VInterface as IZBlob);
806  if TempBlob.Length>ChunkSize then
807  begin
808  OffSet := 0;
809  PieceSize := ChunkSize;
810  while OffSet < TempBlob.Length do
811  begin
812  if OffSet+PieceSize > TempBlob.Length then
813  PieceSize := TempBlob.Length - OffSet;
814  if (FPlainDriver.SendPreparedLongData(FStmtHandle, I, PAnsiChar(TempBlob.GetBuffer)+OffSet, PieceSize) <> 0) then
815  begin
816  checkMySQLPrepStmtError (FPlainDriver, FStmtHandle, lcPrepStmt, SBindingFailure);
817  exit;
818  end;
819  Inc(OffSet, PieceSize);
820  end;
821  end;
822  TempBlob:=nil;
823  end;
824  end;
825  end;
826 end;
827 
828 procedure TZMySQLPreparedStatement.UnPrepareInParameters;
829 begin
830  // Empty : Mysql can't prepare datastructures before the actual parameters are known, because the
831  // number/datatype of parameters isn't returned by the server.
832  inherited UnPrepareInParameters;
833 end;
834 
835 function TZMysqlPreparedStatement.getFieldType (testVariant: TZVariant): TMysqlFieldTypes;
836 begin
837  case testVariant.vType of
838  vtNull: Result := FIELD_TYPE_TINY;
839  vtBoolean: Result := FIELD_TYPE_TINY;
840  vtBytes: Result := FIELD_TYPE_TINY_BLOB;
841  vtInteger: Result := FIELD_TYPE_LONGLONG;
842  vtFloat: Result := FIELD_TYPE_DOUBLE;
843  vtString: Result := FIELD_TYPE_STRING;
844  vtDateTime: Result := FIELD_TYPE_DATETIME;
845  vtUnicodeString: Result := FIELD_TYPE_VARCHAR;
846  vtInterface: Result := FIELD_TYPE_BLOB;
847  else
848  raise EZSQLException.Create(SUnsupportedDataType);
849  end;
850 end;
851 
852 {**
853  Executes the SQL query in this <code>PreparedStatement</code> object
854  and returns the result set generated by the query.
855 
856  @return a <code>ResultSet</code> object that contains the data produced by the
857  query; never <code>null</code>
858 }
859 function TZMySQLPreparedStatement.ExecuteQueryPrepared: IZResultSet;
860 begin
861  Result := nil;
862  Prepare;
863  BindInParameters;
864  if (self.FPlainDriver.ExecuteStmt(FStmtHandle) <> 0) then
865  try
866  checkMySQLPrepStmtError(FPlainDriver,FStmtHandle, lcExecPrepStmt, SPreparedStmtExecFailure);
867  except
868  if Assigned(FParamBindBuffer) then
869  FreeAndNil(FParamBindBuffer);
870  raise;
871  end;
872 
873  if Assigned(FParamBindBuffer) then
874  FreeAndNil(FParamBindBuffer);
875 
876  if FPlainDriver.GetPreparedFieldCount(FStmtHandle) = 0 then
877  raise EZSQLException.Create(SCanNotOpenResultSet);
878  Result := CreateResultSet(SQL);
879  inherited ExecuteQueryPrepared;
880 end;
881 
882 {**
883  Executes the SQL INSERT, UPDATE or DELETE statement
884  in this <code>PreparedStatement</code> object.
885  In addition,
886  SQL statements that return nothing, such as SQL DDL statements,
887  can be executed.
888 
889  @return either the row count for INSERT, UPDATE or DELETE statements;
890  or 0 for SQL statements that return nothing
891 }
892 function TZMySQLPreparedStatement.ExecuteUpdatePrepared: Integer;
893 begin
894  Prepare;
895  BindInParameters;
896  if (self.FPlainDriver.ExecuteStmt(FStmtHandle) <> 0) then
897  try
898  checkMySQLPrepStmtError(FPlainDriver,FStmtHandle, lcExecPrepStmt, SPreparedStmtExecFailure);
899  except
900  if Assigned(FParamBindBuffer) then
901  FreeAndNil(FParamBindBuffer); //MemLeak closed
902  raise;
903  end;
904 
905  if Assigned(FParamBindBuffer) then
906  FreeAndNil(FParamBindBuffer); //MemLeak closed
907 
908  { Process queries with result sets }
909  if FPlainDriver.GetPreparedFieldCount(FStmtHandle) > 0 then
910  begin
911  FPlainDriver.StorePreparedResult(FStmtHandle);
912  Result := FPlainDriver.GetPreparedAffectedRows(FStmtHandle);
913  if Assigned(FStmtHandle) then
914  begin
915  FPlainDriver.FreePreparedResult(FStmtHandle);
916  while(FPlainDriver.GetPreparedNextResult(FStmtHandle) = 0) do
917  FPlainDriver.FreePreparedResult(FStmtHandle);
918  end;
919 
920  end
921  { Process regular query }
922  else
923  Result := FPlainDriver.GetPreparedAffectedRows(FStmtHandle);
924  LastUpdateCount := Result;
925  Inherited ExecuteUpdatePrepared;
926 end;
927 
928 {**
929  Executes any kind of SQL statement.
930  Some prepared statements return multiple results; the <code>execute</code>
931  method handles these complex statements as well as the simpler
932  form of statements handled by the methods <code>executeQuery</code>
933  and <code>executeUpdate</code>.
934  @see Statement#execute
935 }
936 function TZMySQLPreparedStatement.ExecutePrepared: Boolean;
937 begin
938  Prepare;
939  BindInParameters;
940  if (FPlainDriver.ExecuteStmt(FStmtHandle) <> 0) then
941  try
942  checkMySQLPrepStmtError(FPlainDriver,FStmtHandle, lcExecPrepStmt, SPreparedStmtExecFailure);
943  except
944  if Assigned(FParamBindBuffer) then
945  FreeAndNil(FParamBindBuffer); //MemLeak closed
946  raise;
947  end;
948 
949  if Assigned(FParamBindBuffer) then
950  FreeAndNil(FParamBindBuffer); //MemLeak closed
951 
952  if FPlainDriver.GetPreparedFieldCount(FStmtHandle) > 0 then
953  begin
954  Result := True;
955  LastResultSet := CreateResultSet(SQL);
956  end
957  { Processes regular query. }
958  else
959  begin
960  Result := False;
961  LastUpdateCount := FPlainDriver.GetPreparedAffectedRows(FStmtHandle);
962  end;
963 
964  inherited ExecutePrepared;
965 end;
966 
967 function TZMySQLPreparedStatement.GetStmtHandle: PZMySqlPrepStmt;
968 begin
969  Result := FStmtHandle;
970 end;
971 
972 { TZMySQLCallableStatement }
973 
974 {**
975  Create sql string for calling stored procedure.
976  @return a Stored Procedure SQL string
977 }
978 function TZMySQLCallableStatement.GetCallSQL: RawByteString;
979  function GenerateParamsStr(Count: integer): RawByteString;
980  var
981  I: integer;
982  begin
983  Result := '';
984  for I := 0 to Count-1 do
985  begin
986  if I > 0 then
987  Result := Result + ', ';
988  if FDBParamTypes[i] in [1, 2, 3, 4] then
989  Result := Result + '@'+ZPlainString(FParamNames[I])
990  end;
991  end;
992 
993 var
994  InParams: RawByteString;
995 begin
996  if HasOutParameter then
997  InParams := GenerateParamsStr(OutParamCount)
998  else
999  InParams := GenerateParamsStr(InParamCount);
1000  Result := 'CALL '+ZPlainString(SQL)+'('+InParams+')';
1001 end;
1002 
1003 function TZMySQLCallableStatement.GetOutParamSQL: String;
1004  function GenerateParamsStr(Count: integer): string;
1005  var
1006  I: integer;
1007  begin
1008  Result := '';
1009  I := 0;
1010  while True do
1011  if (FDBParamTypes[i] = 0) or ( I = Length(FDBParamTypes)) then
1012  break
1013  else
1014  begin
1015  if FDBParamTypes[i] in [2, 3, 4] then
1016  begin
1017  if Result <> '' then
1018  Result := Result + ',';
1019  if FParamTypeNames[i] = '' then
1020  Result := Result + ' @'+FParamNames[I]+' AS '+FParamNames[I]
1021  else
1022  Result := Result + ' CAST(@'+FParamNames[I]+ ' AS '+FParamTypeNames[i]+') AS '+FParamNames[I];
1023  end;
1024  Inc(i);
1025  end;
1026  end;
1027 
1028 var
1029  OutParams: String;
1030 begin
1031  OutParams := GenerateParamsStr(Self.OutParamCount-Length(InParamValues));
1032  Result := 'SELECT '+ OutParams;
1033 end;
1034 
1035 function TZMySQLCallableStatement.GetSelectFunctionSQL: RawByteString;
1036  function GenerateInParamsStr: RawByteString;
1037  var
1038  I: Integer;
1039  begin
1040  Result := '';
1041  for i := 0 to Length(InParamValues) -1 do
1042  if Result = '' then
1043  Result := PrepareAnsiSQLParam(I)
1044  else
1045  Result := Result+', '+ PrepareAnsiSQLParam(I);
1046  end;
1047 var
1048  InParams: RawByteString;
1049 begin
1050  InParams := GenerateInParamsStr;
1051  Result := 'SELECT '+ZPlainString(SQL)+'('+InParams+')';
1052  Result := Result + ' AS ReturnValue';
1053 end;
1054 
1055 {**
1056  Executes an SQL <code>INSERT</code>, <code>UPDATE</code> or
1057  <code>DELETE</code> statement. In addition,
1058  SQL statements that return nothing, such as SQL DDL statements,
1059  can be executed.
1060 
1061  @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
1062  <code>DELETE</code> statement or an SQL statement that returns nothing
1063  @return either the row count for <code>INSERT</code>, <code>UPDATE</code>
1064  or <code>DELETE</code> statements, or 0 for SQL statements that return nothing
1065 }
1066 function TZMySQLCallableStatement.PrepareAnsiSQLParam(ParamIndex: Integer): RawByteString;
1067 var
1068  Value: TZVariant;
1069  TempBytes: TByteDynArray;
1070  TempBlob: IZBlob;
1071  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond: Word;
1072 begin
1073  TempBytes := nil;
1074  if InParamCount <= ParamIndex then
1075  raise EZSQLException.Create(SInvalidInputParameterCount);
1076 
1077  Value := InParamValues[ParamIndex];
1078  if DefVarManager.IsNull(Value) then
1079  if FUseDefaults and (InParamDefaultValues[ParamIndex] <> '') then
1080  Result := ConSettings^.ConvFuncs.ZStringToRaw(InParamDefaultValues[ParamIndex],
1081  ConSettings^.CTRL_CP, ConSettings^.ClientCodePage^.CP)
1082  else
1083  Result := 'NULL'
1084  else
1085  begin
1086  case InParamTypes[ParamIndex] of
1087  stBoolean:
1088  if SoftVarManager.GetAsBoolean(Value) then
1089  Result := '''Y'''
1090  else
1091  Result := '''N''';
1092  stByte, stShort, stInteger, stLong, stBigDecimal, stFloat, stDouble:
1093  Result := RawByteString(SoftVarManager.GetAsString(Value));
1094  stBytes:
1095  begin
1096  TempBytes := SoftVarManager.GetAsBytes(Value);
1097  Result := GetSQLHexAnsiString(PAnsiChar(TempBytes), Length(TempBytes));
1098  end;
1099  stString:
1100  Result := FPlainDriver.EscapeString(FHandle, ZPlainString(SoftVarManager.GetAsString(Value), ConSettings), ConSettings, True);
1101  stUnicodeString:
1102  Result := FPlainDriver.EscapeString(FHandle, ZPlainString(SoftVarManager.GetAsUnicodeString(Value)), ConSettings, True);
1103  stDate:
1104  begin
1105  DecodeDateTime(SoftVarManager.GetAsDateTime(Value),
1106  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
1107  Result := '''' + RawByteString(Format('%0.4d-%0.2d-%0.2d',
1108  [AYear, AMonth, ADay])) + '''';
1109  end;
1110  stTime:
1111  begin
1112  DecodeDateTime(SoftVarManager.GetAsDateTime(Value),
1113  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
1114  Result := '''' + RawByteString(Format('%0.2d:%0.2d:%0.2d',
1115  [AHour, AMinute, ASecond])) + '''';
1116  end;
1117  stTimestamp:
1118  begin
1119  DecodeDateTime(SoftVarManager.GetAsDateTime(Value),
1120  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
1121  Result := '''' + RawByteString(Format('%0.4d-%0.2d-%0.2d %0.2d:%0.2d:%0.2d',
1122  [AYear, AMonth, ADay, AHour, AMinute, ASecond])) + '''';
1123  end;
1124  stAsciiStream, stUnicodeStream, stBinaryStream:
1125  begin
1126  TempBlob := DefVarManager.GetAsInterface(Value) as IZBlob;
1127  if not TempBlob.IsEmpty then
1128  case InParamTypes[ParamIndex] of
1129  stBinaryStream:
1130  Result := GetSQLHexAnsiString(PAnsichar(TempBlob.GetBuffer), TempBlob.Length);
1131  else
1132  Result := FPlainDriver.EscapeString(FHandle,
1133  GetValidatedAnsiStringFromBuffer(TempBlob.GetBuffer,
1134  TempBlob.Length, TempBlob.WasDecoded, ConSettings),
1135  ConSettings, True);
1136  end
1137  else
1138  Result := 'NULL';
1139  end;
1140  end;
1141  end;
1142 end;
1143 
1144 function TZMySQLCallableStatement.GetStmtHandle: PZMySqlPrepStmt;
1145 begin
1146  Result := nil;
1147 end;
1148 
1149 procedure TZMySQLCallableStatement.ClearResultSets;
1150 begin
1151  inherited;
1152  FPlainDriver.FreeResult(FQueryHandle);
1153  FQueryHandle := nil;
1154 end;
1155 
1156 procedure TZMySQLCallableStatement.BindInParameters;
1157 var
1158  I: integer;
1159  ExecQuery: RawByteString;
1160 begin
1161  I := 0;
1162  ExecQuery := '';
1163  while True do
1164  if (i = Length(FDBParamTypes)) then
1165  break
1166  else
1167  begin
1168  if FDBParamTypes[i] in [1, 3] then //ptInputOutput
1169  if ExecQuery = '' then
1170  ExecQuery := 'SET @'+ZPlainString(FParamNames[i])+' = '+PrepareAnsiSQLParam(I)
1171  else
1172  ExecQuery := ExecQuery + ', @'+ZPlainString(FParamNames[i])+' = '+PrepareAnsiSQLParam(I);
1173  Inc(i);
1174  end;
1175  if not (ExecQuery = '') then
1176  if FPlainDriver.ExecQuery(Self.FHandle, PAnsiChar(ExecQuery)) = 0 then
1177  DriverManager.LogMessage(lcBindPrepStmt, FPlainDriver.GetProtocol, String(ExecQuery))
1178  else
1179  CheckMySQLError(FPlainDriver, FHandle, lcExecute, String(ExecQuery));
1180 end;
1181 
1182 {**
1183  Creates a result set based on the current settings.
1184  @return a created result set object.
1185 }
1186 function TZMySQLCallableStatement.CreateResultSet(const SQL: string): IZResultSet;
1187 var
1188  CachedResolver: TZMySQLCachedResolver;
1189  NativeResultSet: TZMySQLResultSet;
1190  CachedResultSet: TZCachedResultSet;
1191 begin
1192  NativeResultSet := TZMySQLResultSet.Create(FPlainDriver, Self, SQL, FHandle,
1193  FUseResult, @LastUpdateCount, not IsFunction);
1194  NativeResultSet.SetConcurrency(rcReadOnly);
1195  if (GetResultSetConcurrency <> rcReadOnly) or (FUseResult
1196  and (GetResultSetType <> rtForwardOnly)) or (not IsFunction) then
1197  begin
1198  CachedResolver := TZMySQLCachedResolver.Create(FPlainDriver, FHandle, Self,
1199  NativeResultSet.GetMetaData);
1200  CachedResultSet := TZCachedResultSet.Create(NativeResultSet, SQL,
1201  CachedResolver, ConSettings);
1202  CachedResultSet.SetConcurrency(rcReadOnly);
1203  {Need to fetch all data. The handles must be released for mutiple
1204  Resultsets}
1205  CachedResultSet.AfterLast;//Fetch all
1206  CachedResultSet.BeforeFirst;//Move to first pos
1207  NativeResultSet.ReleaseHandle; //Release the handles
1208  Result := CachedResultSet;
1209  end
1210  else
1211  Result := NativeResultSet;
1212 end;
1213 
1214 {**
1215  Sets output parameters from a ResultSet
1216  @param Value a IZResultSet object.
1217 }
1218 procedure TZMySQLCallableStatement.FetchOutParams(ResultSet: IZResultSet);
1219 var
1220  ParamIndex, I: Integer;
1221  Temp: TZVariant;
1222  HasRows: Boolean;
1223 begin
1224  ResultSet.BeforeFirst;
1225  HasRows := ResultSet.Next;
1226 
1227  I := 1;
1228  for ParamIndex := 0 to OutParamCount - 1 do
1229  begin
1230  if not (FDBParamTypes[ParamIndex] in [2, 3, 4]) then // ptOutput, ptInputOutput, ptResult
1231  Continue;
1232  if I > ResultSet.GetMetadata.GetColumnCount then
1233  Break;
1234 
1235  if (not HasRows) or (ResultSet.IsNull(I)) then
1236  DefVarManager.SetNull(Temp)
1237  else
1238  case ResultSet.GetMetadata.GetColumnType(I) of
1239  stBoolean:
1240  DefVarManager.SetAsBoolean(Temp, ResultSet.GetBoolean(I));
1241  stByte:
1242  DefVarManager.SetAsInteger(Temp, ResultSet.GetByte(I));
1243  stBytes:
1244  DefVarManager.SetAsBytes(Temp, ResultSet.GetBytes(I));
1245  stShort:
1246  DefVarManager.SetAsInteger(Temp, ResultSet.GetShort(I));
1247  stInteger:
1248  DefVarManager.SetAsInteger(Temp, ResultSet.GetInt(I));
1249  stLong:
1250  DefVarManager.SetAsInteger(Temp, ResultSet.GetLong(I));
1251  stFloat:
1252  DefVarManager.SetAsFloat(Temp, ResultSet.GetFloat(I));
1253  stDouble:
1254  DefVarManager.SetAsFloat(Temp, ResultSet.GetDouble(I));
1255  stBigDecimal:
1256  DefVarManager.SetAsFloat(Temp, ResultSet.GetBigDecimal(I));
1257  stString, stAsciiStream:
1258  DefVarManager.SetAsString(Temp, ResultSet.GetString(I));
1259  stUnicodeString, stUnicodeStream:
1260  DefVarManager.SetAsUnicodeString(Temp, ResultSet.GetUnicodeString(I));
1261  stDate:
1262  DefVarManager.SetAsDateTime(Temp, ResultSet.GetDate(I));
1263  stTime:
1264  DefVarManager.SetAsDateTime(Temp, ResultSet.GetTime(I));
1265  stTimestamp:
1266  DefVarManager.SetAsDateTime(Temp, ResultSet.GetTimestamp(I));
1267  stBinaryStream:
1268  DefVarManager.SetAsInterface(Temp, ResultSet.GetBlob(I));
1269  else
1270  DefVarManager.SetAsString(Temp, ResultSet.GetString(I));
1271  end;
1272  OutParamValues[ParamIndex] := Temp;
1273  Inc(I);
1274  end;
1275  ResultSet.BeforeFirst;
1276 end;
1277 
1278 procedure TZMySQLCallableStatement.RegisterParamTypeAndName(const ParameterIndex:integer;
1279  const ParamTypeName, ParamName: String; Const ColumnSize, Precision: Integer);
1280 begin
1281  FParamNames[ParameterIndex] := ParamName;
1282  if ( Pos('char', LowerCase(ParamTypeName)) > 0 ) or
1283  ( Pos('set', LowerCase(ParamTypeName)) > 0 ) then
1284  FParamTypeNames[ParameterIndex] := 'CHAR('+IntToStr(ColumnSize)+')'
1285  else
1286  if ( Pos('set', LowerCase(ParamTypeName)) > 0 ) then
1287  FParamTypeNames[ParameterIndex] := 'CHAR('+IntToStr(ColumnSize)+')'
1288  else
1289  if ( Pos('datetime', LowerCase(ParamTypeName)) > 0 ) or
1290  ( Pos('timestamp', LowerCase(ParamTypeName)) > 0 ) then
1291  FParamTypeNames[ParameterIndex] := 'DATETIME'
1292  else
1293  if ( Pos('date', LowerCase(ParamTypeName)) > 0 ) then
1294  FParamTypeNames[ParameterIndex] := 'DATE'
1295  else
1296  if ( Pos('time', LowerCase(ParamTypeName)) > 0 ) then
1297  FParamTypeNames[ParameterIndex] := 'TIME'
1298  else
1299  if ( Pos('int', LowerCase(ParamTypeName)) > 0 ) or
1300  ( Pos('year', LowerCase(ParamTypeName)) > 0 ) then
1301  FParamTypeNames[ParameterIndex] := 'SIGNED'
1302  else
1303  if ( Pos('binary', LowerCase(ParamTypeName)) > 0 ) then
1304  FParamTypeNames[ParameterIndex] := 'BINARY('+IntToStr(ColumnSize)+')'
1305  else
1306  FParamTypeNames[ParameterIndex] := '';
1307 end;
1308 
1309 constructor TZMySQLCallableStatement.Create(PlainDriver: IZMySQLPlainDriver;
1310  Connection: IZConnection; const SQL: string; Info: TStrings;
1311  Handle: PZMySQLConnect);
1312 begin
1313  inherited Create(Connection, SQL, Info);
1314  FHandle := Handle;
1315  FPlainDriver := PlainDriver;
1316  ResultSetType := rtScrollInsensitive;
1317  FUseResult := StrToBoolEx(DefineStatementParameter(Self, 'useresult', 'false'));
1318  FUseDefaults := StrToBoolEx(DefineStatementParameter(Self, 'defaults', 'true'))
1319 end;
1320 
1321 {**
1322  Executes an SQL statement that returns a single <code>ResultSet</code> object.
1323  @param sql typically this is a static SQL <code>SELECT</code> statement
1324  @return a <code>ResultSet</code> object that contains the data produced by the
1325  given query; never <code>null</code>
1326 }
1327 function TZMySQLCallableStatement.ExecuteQuery(const SQL: RawByteString): IZResultSet;
1328 begin
1329  Result := nil;
1330  ASQL := SQL;
1331  if FPlainDriver.ExecQuery(FHandle, PAnsiChar(SQL)) = 0 then
1332  begin
1333  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, SSQL);
1334  if not FPlainDriver.ResultSetExists(FHandle) then
1335  raise EZSQLException.Create(SCanNotOpenResultSet);
1336  if IsFunction then
1337  ClearResultSets;
1338  FResultSets.Add(CreateResultSet(SSQL));
1339  if FPlainDriver.CheckAnotherRowset(FHandle) then
1340  begin
1341  while FPlainDriver.RetrieveNextRowset(FHandle) = 0 do
1342  if FPlainDriver.CheckAnotherRowset(FHandle) then
1343  FResultSets.Add(CreateResultSet(SSQL))
1344  else break;
1345  CheckMySQLError(FPlainDriver, FHandle, lcExecute, SSQL);
1346  end;
1347  FActiveResultset := FResultSets.Count-1;
1348  Result := IZResultSet(FResultSets[FActiveResultset]);
1349  end
1350  else
1351  CheckMySQLError(FPlainDriver, FHandle, lcExecute, SSQL);
1352 end;
1353 
1354 {**
1355  Executes an SQL <code>INSERT</code>, <code>UPDATE</code> or
1356  <code>DELETE</code> statement. In addition,
1357  SQL statements that return nothing, such as SQL DDL statements,
1358  can be executed.
1359 
1360  @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
1361  <code>DELETE</code> statement or an SQL statement that returns nothing
1362  @return either the row count for <code>INSERT</code>, <code>UPDATE</code>
1363  or <code>DELETE</code> statements, or 0 for SQL statements that return nothing
1364 }
1365 function TZMySQLCallableStatement.ExecuteUpdate(const SQL: RawByteString): Integer;
1366 begin
1367  Result := -1;
1368  ASQL := SQL;
1369  if FPlainDriver.ExecQuery(FHandle, PAnsiChar(ASQL)) = 0 then
1370  begin
1371  { Process queries with result sets }
1372  if FPlainDriver.ResultSetExists(FHandle) then
1373  begin
1374  ClearResultSets;
1375  FActiveResultset := 0;
1376  FResultSets.Add(CreateResultSet(LogSQL));
1377  if FPlainDriver.CheckAnotherRowset(FHandle) then
1378  begin
1379  Result := LastUpdateCount;
1380  while FPlainDriver.RetrieveNextRowset(FHandle) = 0 do
1381  if FPlainDriver.CheckAnotherRowset(FHandle) then
1382  begin
1383  FResultSets.Add(CreateResultSet(SSQL));
1384  inc(Result, LastUpdateCount); //LastUpdateCount will be returned from ResultSet.Open
1385  end
1386  else break;
1387  CheckMySQLError(FPlainDriver, FHandle, lcExecute, SSQL);
1388  end
1389  else
1390  Result := LastUpdateCount;
1391  FActiveResultset := FResultSets.Count-1;
1392  LastResultSet := IZResultSet(FResultSets[FActiveResultset]);
1393  end
1394  else { Process regular query }
1395  Result := FPlainDriver.GetAffectedRows(FHandle);
1396  end
1397  else
1398  CheckMySQLError(FPlainDriver, FHandle, lcExecute, SSQL);
1399  LastUpdateCount := Result;
1400 end;
1401 
1402 {**
1403  Executes an SQL statement that may return multiple results.
1404  Under some (uncommon) situations a single SQL statement may return
1405  multiple result sets and/or update counts. Normally you can ignore
1406  this unless you are (1) executing a stored procedure that you know may
1407  return multiple results or (2) you are dynamically executing an
1408  unknown SQL string. The methods <code>execute</code>,
1409  <code>getMoreResults</code>, <code>getResultSet</code>,
1410  and <code>getUpdateCount</code> let you navigate through multiple results.
1411 
1412  The <code>execute</code> method executes an SQL statement and indicates the
1413  form of the first result. You can then use the methods
1414  <code>getResultSet</code> or <code>getUpdateCount</code>
1415  to retrieve the result, and <code>getMoreResults</code> to
1416  move to any subsequent result(s).
1417 
1418  @param sql any SQL statement
1419  @return <code>true</code> if the next result is a <code>ResultSet</code> object;
1420  <code>false</code> if it is an update count or there are no more results
1421 }
1422 function TZMySQLCallableStatement.Execute(const SQL: RawByteString): Boolean;
1423 var
1424  HasResultset : Boolean;
1425 begin
1426  Result := False;
1427  {$IFNDEF UNICODE}ASQL := SQL;{$ENDIF}
1428  if FPlainDriver.ExecQuery(FHandle, PAnsiChar(ASQL)) = 0 then
1429  begin
1430  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, LogSQL);
1431  HasResultSet := FPlainDriver.ResultSetExists(FHandle);
1432  { Process queries with result sets }
1433  if HasResultSet then
1434  begin
1435  Result := True;
1436  LastResultSet := CreateResultSet(LogSQL);
1437  end
1438  { Processes regular query. }
1439  else
1440  begin
1441  Result := False;
1442  LastUpdateCount := FPlainDriver.GetAffectedRows(FHandle);
1443  end;
1444  end
1445  else
1446  CheckMySQLError(FPlainDriver, FHandle, lcExecute, LogSQL);
1447 end;
1448 
1449 {**
1450  Executes the SQL query in this <code>PreparedStatement</code> object
1451  and returns the result set generated by the query.
1452 
1453  @return a <code>ResultSet</code> object that contains the data produced by the
1454  query; never <code>null</code>
1455 }
1456 function TZMySQLCallableStatement.ExecuteQueryPrepared: IZResultSet;
1457 begin
1458  if IsFunction then
1459  begin
1460  TrimInParameters;
1461  Result := ExecuteQuery(GetSelectFunctionSQL);
1462  end
1463  else
1464  begin
1465  BindInParameters;
1466  ExecuteUpdate(GetCallSQL);
1467  if OutParamCount > 0 then
1468  Result := ExecuteQuery(ZPlainString(GetOutParamSQL)) //Get the Last Resultset
1469  else
1470  Result := GetLastResultSet;
1471  end;
1472  if Assigned(Result) then
1473  FetchOutParams(Result);
1474 end;
1475 
1476 {**
1477  Executes the SQL INSERT, UPDATE or DELETE statement
1478  in this <code>PreparedStatement</code> object.
1479  In addition,
1480  SQL statements that return nothing, such as SQL DDL statements,
1481  can be executed.
1482 
1483  @return either the row count for INSERT, UPDATE or DELETE statements;
1484  or 0 for SQL statements that return nothing
1485 }
1486 function TZMySQLCallableStatement.ExecuteUpdatePrepared: Integer;
1487 begin
1488  if IsFunction then
1489  begin
1490  TrimInParameters;
1491  Result := ExecuteUpdate(GetSelectFunctionSQL);
1492  FetchOutParams(LastResultSet);
1493  end
1494  else
1495  begin
1496  BindInParameters;
1497  Result := ExecuteUpdate(GetCallSQL);
1498  if OutParamCount > 0 then
1499  FetchOutParams(ExecuteQuery(ZPlainString(GetOutParamSQL))); //Get the Last Resultset
1500  Inc(Result, LastUpdateCount);
1501  end;
1502 end;
1503 
1504 {**
1505  Checks is use result should be used in result sets.
1506  @return <code>True</code> use result in result sets,
1507  <code>False</code> store result in result sets.
1508 }
1509 function TZMySQLCallableStatement.IsUseResult: Boolean;
1510 begin
1511  Result := FUseResult;
1512 end;
1513 
1514 {**
1515  Checks if this is a prepared mysql statement.
1516  @return <code>False</code> This is not a prepared mysql statement.
1517 }
1518 function TZMySQLCallableStatement.IsPreparedStatement: Boolean;
1519 begin
1520  Result := False;
1521 end;
1522 
1523 {**
1524  Are more resultsets retrieved?
1525  @result Returns <code>True</code> if more resultsets are retrieved
1526 }
1527 function TZMySQLCallableStatement.HasMoreResultSets: Boolean;
1528 begin
1529  Result := FResultSets.Count > 1;
1530 end;
1531 
1532 {**
1533  Get the first resultset..
1534  @result <code>IZResultSet</code> if supported
1535 }
1536 function TZMySQLCallableStatement.GetNextResultSet: IZResultSet;
1537 begin
1538  if ( FActiveResultset < FResultSets.Count-1) and ( FResultSets.Count > 1) then
1539  begin
1540  Inc(FActiveResultset);
1541  Result := IZResultSet(FResultSets[FActiveResultset]);
1542  end
1543  else
1544  if FResultSets.Count = 0 then
1545  Result := nil
1546  else
1547  Result := IZResultSet(FResultSets[FActiveResultset]);
1548 end;
1549 
1550 {**
1551  Get the previous resultset..
1552  @result <code>IZResultSet</code> if supported
1553 }
1554 function TZMySQLCallableStatement.GetPreviousResultSet: IZResultSet;
1555 begin
1556  if ( FActiveResultset > 0) and ( FResultSets.Count > 0) then
1557  begin
1558  Dec(FActiveResultset);
1559  Result := IZResultSet(FResultSets[FActiveResultset]);
1560  end
1561  else
1562  if FResultSets.Count = 0 then
1563  Result := nil
1564  else
1565  Result := IZResultSet(FResultSets[FActiveResultset]);
1566 end;
1567 
1568 {**
1569  Get the next resultset..
1570  @result <code>IZResultSet</code> if supported
1571 }
1572 function TZMySQLCallableStatement.GetFirstResultSet: IZResultSet;
1573 begin
1574  if FResultSets.Count = 0 then
1575  Result := nil
1576  else
1577  begin
1578  FActiveResultset := 0;
1579  Result := IZResultSet(FResultSets[0]);
1580  end;
1581 end;
1582 
1583 {**
1584  Get the last resultset..
1585  @result <code>IZResultSet</code> if supported
1586 }
1587 function TZMySQLCallableStatement.GetLastResultSet: IZResultSet;
1588 begin
1589  if FResultSets.Count = 0 then
1590  Result := nil
1591  else
1592  begin
1593  FActiveResultset := FResultSets.Count -1;
1594  Result := IZResultSet(FResultSets[FResultSets.Count -1]);
1595  end;
1596 end;
1597 
1598 {**
1599  First ResultSet?
1600  @result <code>True</code> if first ResultSet
1601 }
1602 function TZMySQLCallableStatement.BOR: Boolean;
1603 begin
1604  Result := FActiveResultset = 0;
1605 end;
1606 
1607 {**
1608  Last ResultSet?
1609  @result <code>True</code> if Last ResultSet
1610 }
1611 function TZMySQLCallableStatement.EOR: Boolean;
1612 begin
1613  Result := FActiveResultset = FResultSets.Count -1;
1614 end;
1615 
1616 {**
1617  Retrieves a ResultSet by his index.
1618  @param Integer the index of the Resultset
1619  @result <code>IZResultSet</code> of the Index or nil.
1620 }
1621 function TZMySQLCallableStatement.GetResultSetByIndex(const Index: Integer): IZResultSet;
1622 begin
1623  Result := nil;
1624  if ( Index < 0 ) or ( Index > FResultSets.Count -1 ) then
1625  raise Exception.Create(Format(SListIndexError, [Index]))
1626  else
1627  Result := IZResultSet(FResultSets[Index]);
1628 end;
1629 
1630 {**
1631  Returns the Count of retrived ResultSets.
1632  @result <code>Integer</code> Count
1633 }
1634 function TZMySQLCallableStatement.GetResultSetCount: Integer;
1635 begin
1636  Result := FResultSets.Count;
1637 end;
1638 
1639 { TZMySQLAbstractBindBuffer }
1640 
1641 constructor TZMySQLAbstractBindBuffer.Create(PlainDriver: IZMysqlPlainDriver;
1642  const BindCount: Integer; var ColumnArray: TZMysqlColumnBuffer);
1643 begin
1644  inherited Create;
1645  FBindOffsets := PlainDriver.GetBindOffsets;
1646  if FBindOffsets.buffer_type=0 then
1647  raise EZSQLException.Create('Unknown dll version : '+IntToStr(PlainDriver.GetClientVersion));
1648  FPColumnArray := @ColumnArray;
1649  setlength(FBindArray,0);
1650  setlength(ColumnArray,BindCount);
1651  setlength(FBindArray,BindCount*FBindOffsets.size);
1652 end;
1653 
1654 function TZMySQLAbstractBindBuffer.GetColumnArray: TZMysqlColumnBuffer;
1655 begin
1656  result := FPColumnArray^;
1657 end;
1658 
1659 function TZMySQLAbstractBindBuffer.GetBufferAddress: Pointer;
1660 begin
1661  result := @FBindArray[0];
1662 end;
1663 
1664 function TZMySQLAbstractBindBuffer.GetBufferType(ColumnIndex: Integer): TMysqlFieldTypes;
1665 begin
1666  result := PTMysqlFieldTypes(@FbindArray[NativeUInt((ColumnIndex-1)*FBindOffsets.size)+FBindOffsets.buffer_type])^;
1667 end;
1668 
1669 function TZMySQLAbstractBindBuffer.GetBufferIsSigned(ColumnIndex: Integer): Boolean;
1670 begin
1671  result := PByte(@FbindArray[NativeUInt((ColumnIndex-1)*FBindOffsets.size)+FBindOffsets.is_unsigned])^ <> 0;
1672 end;
1673 
1674 { TZMySQLResultSetBindBuffer }
1675 
1676 procedure TZMySQLResultSetBindBuffer.AddColumn(PlainDriver: IZMysqlPlainDriver;
1677  const FieldHandle: PZMySQLField);
1678 var
1679  buffertype: TMysqlFieldTypes;
1680  ColOffset: NativeUInt;
1681 begin
1682  buffertype := PlainDriver.GetFieldType(FieldHandle);
1683  Inc(FAddedColumnCount);
1684  With FPColumnArray^[FAddedColumnCount-1] do
1685  begin
1686  case buffertype of
1687  FIELD_TYPE_DATE: Length := sizeOf(MYSQL_TIME);
1688  FIELD_TYPE_TIME: Length := sizeOf(MYSQL_TIME);
1689  FIELD_TYPE_DATETIME: Length := sizeOf(MYSQL_TIME);
1690  FIELD_TYPE_TIMESTAMP: Length := sizeOf(MYSQL_TIME);
1691  FIELD_TYPE_TINY: Length := 1;
1692  FIELD_TYPE_SHORT: Length := 2;
1693  FIELD_TYPE_LONG: Length := 4;
1694  FIELD_TYPE_LONGLONG: Length := 8;
1695  FIELD_TYPE_FLOAT: Length := 4;
1696  FIELD_TYPE_DOUBLE: Length := 8;
1697  FIELD_TYPE_NEWDECIMAL: Length := 11;
1698  FIELD_TYPE_BLOB,
1699  FIELD_TYPE_MEDIUM_BLOB,
1700  FIELD_TYPE_LONG_BLOB,
1701  FIELD_TYPE_GEOMETRY:
1702  Length := PlainDriver.GetFieldMaxLength(FieldHandle)+1;
1703  FIELD_TYPE_VARCHAR,
1704  FIELD_TYPE_VAR_STRING,
1705  FIELD_TYPE_STRING:
1706  Length := Min(MaxBlobSize, Max(PlainDriver.GetFieldLength(FieldHandle), PlainDriver.GetFieldMaxLength(FieldHandle)))+1;
1707  else
1708  Length := PlainDriver.GetFieldLength(FieldHandle);
1709  end;
1710  SetLength(Buffer, Length);
1711  end;
1712  ColOffset := NativeUInt((FAddedColumnCount-1)*FBindOffsets.size);
1713  PTMysqlFieldTypes(@FbindArray[ColOffset+FBindOffsets.buffer_type])^ := buffertype;
1714  PULong(@FbindArray[ColOffset+FBindOffsets.buffer_length])^ := FPColumnArray^[FAddedColumnCount-1].length;
1715  PByte(@FbindArray[ColOffset+FBindOffsets.is_unsigned])^:= PlainDriver.GetFieldFlags(FieldHandle) and UNSIGNED_FLAG;
1716  PPointer(@FbindArray[ColOffset+FBindOffsets.buffer])^:= @FPColumnArray^[FAddedColumnCount-1].buffer[0];
1717  PPointer(@FbindArray[ColOffset+FBindOffsets.length])^:= @FPColumnArray^[FAddedColumnCount-1].length;
1718  PPointer(@FbindArray[ColOffset+FBindOffsets.is_null])^:= @FPColumnArray^[FAddedColumnCount-1].is_null;
1719 end;
1720 
1721 { TZMySQLParamBindBuffer }
1722 
1723 // largeblobparameter: true to indicate that parameter is a blob that will be
1724 // sent chunked. Set to false for result set columns.
1725 
1726 procedure TZMySQLParamBindBuffer.AddColumn(buffertype: TMysqlFieldTypes;
1727  const field_length: integer; const largeblobparameter: boolean);
1728 var
1729  tempbuffertype: TMysqlFieldTypes;
1730  ColOffset:NativeUInt;
1731 begin
1732  Case buffertype of
1733  FIELD_TYPE_DECIMAL,
1734  FIELD_TYPE_NEWDECIMAL: tempbuffertype := FIELD_TYPE_DOUBLE;
1735  Else
1736  tempbuffertype := buffertype;
1737  End;
1738  Inc(FAddedColumnCount);
1739  With FPColumnArray^[FAddedColumnCount-1] do
1740  begin
1741  length := getMySQLFieldSize(tempbuffertype,field_length);
1742  if largeblobparameter then
1743  begin
1744  is_Null := 0;
1745  buffer := nil;
1746  end
1747  else if field_length = 0 then
1748  begin
1749  is_Null := 1;
1750  buffer := nil;
1751  end
1752  else
1753  begin
1754  if tempbuffertype in [FIELD_TYPE_TINY_BLOB, FIELD_TYPE_MEDIUM_BLOB,
1755  FIELD_TYPE_LONG_BLOB, FIELD_TYPE_BLOB, FIELD_TYPE_VAR_STRING, FIELD_TYPE_STRING] then
1756  //ludob: mysql adds terminating #0 on top of data. Avoid buffer overrun.
1757  SetLength(buffer,length+1)
1758  else
1759  SetLength(buffer,length);
1760  is_null := 0;
1761  end;
1762  end;
1763  ColOffset:=NativeUInt((FAddedColumnCount-1)*FBindOffsets.size);
1764  PTMysqlFieldTypes(@FbindArray[ColOffset+FBindOffsets.buffer_type])^:=tempbuffertype;
1765  PULong(@FbindArray[ColOffset+FBindOffsets.buffer_length])^ := FPColumnArray^[FAddedColumnCount-1].length;
1766  PByte(@FbindArray[ColOffset+FBindOffsets.is_unsigned])^:= 0;
1767  PPointer(@FbindArray[ColOffset+FBindOffsets.buffer])^:= @FPColumnArray^[FAddedColumnCount-1].buffer[0];
1768  PPointer(@FbindArray[ColOffset+FBindOffsets.length])^:= @FPColumnArray^[FAddedColumnCount-1].length;
1769  PPointer(@FbindArray[ColOffset+FBindOffsets.is_null])^:= @FPColumnArray^[FAddedColumnCount-1].is_null;
1770 end;
1771 
1772 end.