zeoslib  UNKNOWN
 All Files
ZDbcPostgreSqlStatement.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { PostgreSQL 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 ZDbcPostgreSqlStatement;
53 
54 interface
55 
56 {$I ZDbc.inc}
57 
58 uses
59  Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} SysUtils, Types,
60  ZSysUtils, ZDbcIntfs, ZDbcStatement, ZDbcLogging, ZPlainPostgreSqlDriver,
61  ZCompatibility, ZVariant, ZDbcGenericResolver, ZDbcCachedResultSet,
62  ZDbcPostgreSql;
63 
64 type
65 
66  {** Defines a PostgreSQL specific statement. }
67  IZPostgreSQLStatement = interface(IZStatement)
68  ['{E4FAFD96-97CC-4247-8ECC-6E0A168FAFE6}']
69 
70  function IsOidAsBlob: Boolean;
71  end;
72 
73  {** Implements Generic PostgreSQL Statement. }
74  TZPostgreSQLStatement = class(TZAbstractStatement, IZPostgreSQLStatement)
75  private
76  FPlainDriver: IZPostgreSQLPlainDriver;
77  FOidAsBlob: Boolean;
78  protected
79  function CreateResultSet(const SQL: string;
80  QueryHandle: PZPostgreSQLResult): IZResultSet;
81  function GetConnectionHandle():PZPostgreSQLConnect;
82  public
83  constructor Create(PlainDriver: IZPostgreSQLPlainDriver;
84  Connection: IZConnection; Info: TStrings);
85  destructor Destroy; override;
86 
87  function ExecuteQuery(const SQL: RawByteString): IZResultSet; override;
88  function ExecuteUpdate(const SQL: RawByteString): Integer; override;
89  function Execute(const SQL: RawByteString): Boolean; override;
90 
91  function IsOidAsBlob: Boolean;
92  end;
93 
94  {$IFDEF ZEOS_TEST_ONLY}
95  {** Implements Emulated Prepared SQL Statement. }
96  TZPostgreSQLEmulatedPreparedStatement = class(TZEmulatedPreparedStatement)
97  private
98  FPlainDriver: IZPostgreSQLPlainDriver;
99  Foidasblob: Boolean;
100  protected
101  function CreateExecStatement: IZStatement; override;
102  function PrepareAnsiSQLParam(ParamIndex: Integer): RawByteString; override;
103  function GetConnectionHandle: PZPostgreSQLConnect;
104  public
105  constructor Create(PlainDriver: IZPostgreSQLPlainDriver;
106  Connection: IZConnection; const SQL: string; Info: TStrings);
107  end;
108  {$ENDIF}
109 
110  TZPostgreSQLPreparedStatement = class(TZAbstractPreparedStatement)
111  private
112  FPlanName: String;
113  FRawPlanName: RawByteString;
114  FPostgreSQLConnection: IZPostgreSQLConnection;
115  FPlainDriver: IZPostgreSQLPlainDriver;
116  QueryHandle: PZPostgreSQLResult;
117  Foidasblob: Boolean;
118  FConnectionHandle: PZPostgreSQLConnect;
119  Findeterminate_datatype: Boolean;
120  FCachedQuery: TStrings;
121  function CreateResultSet(QueryHandle: PZPostgreSQLResult): IZResultSet;
122  protected
123  procedure SetPlanNames; virtual; abstract;
124  public
125  constructor Create(PlainDriver: IZPostgreSQLPlainDriver;
126  Connection: IZPostgreSQLConnection; const SQL: string; Info: TStrings);
127  destructor Destroy; override;
128  end;
129 
130  {** EgonHugeist: Implements Prepared SQL Statement with AnsiString usage }
131  TZPostgreSQLClassicPreparedStatement = class(TZPostgreSQLPreparedStatement)
132  private
133  FExecSQL: RawByteString;
134  function GetAnsiSQLQuery: RawByteString;
135  protected
136  procedure SetPlanNames; override;
137  function PrepareAnsiSQLParam(ParamIndex: Integer; Escaped: Boolean): RawByteString;
138  procedure PrepareInParameters; override;
139  procedure BindInParameters; override;
140  procedure UnPrepareInParameters; override;
141  public
142  procedure Prepare; override;
143 
144  function ExecuteQuery(const SQL: RawByteString): IZResultSet; override;
145  function ExecuteUpdate(const SQL: RawByteString): Integer; override;
146  function Execute(const SQL: RawByteString): Boolean; override;
147 
148  function ExecuteQueryPrepared: IZResultSet; override;
149  function ExecuteUpdatePrepared: Integer; override;
150  function ExecutePrepared: Boolean; override;
151  end;
152 
153  {** EgonHugeist: Implements Prepared SQL Statement based on Protocol3
154  ServerVersion 7.4Up and ClientVersion 8.0Up. with C++API usage}
155  TZPostgreSQLCAPIPreparedStatement = class(TZPostgreSQLPreparedStatement)
156  private
157  FPQparamValues: TPQparamValues;
158  FPQparamLengths: TPQparamLengths;
159  FPQparamFormats: TPQparamFormats;
160  function ExectuteInternal(const SQL: RawByteString; const LogSQL: String;
161  const LoggingCategory: TZLoggingCategory): PZPostgreSQLResult;
162  protected
163  procedure SetPlanNames; override;
164  procedure SetASQL(const Value: RawByteString); override;
165  procedure SetWSQL(const Value: ZWideString); override;
166  procedure PrepareInParameters; override;
167  procedure BindInParameters; override;
168  procedure UnPrepareInParameters; override;
169  function PrepareAnsiSQLQuery: RawByteString;
170 
171  public
172  procedure Prepare; override;
173  procedure Unprepare; override;
174 
175  function ExecuteQueryPrepared: IZResultSet; override;
176  function ExecuteUpdatePrepared: Integer; override;
177  function ExecutePrepared: Boolean; override;
178  end;
179 
180  {** Implements callable Postgresql Statement. }
181  TZPostgreSQLCallableStatement = class(TZAbstractCallableStatement)
182  private
183  Foidasblob: Boolean;
184  FPlainDriver: IZPostgreSQLPlainDriver;
185  function GetProcedureSql: string;
186  function FillParams(const ASql: String): RawByteString;
187  function PrepareAnsiSQLParam(ParamIndex: Integer): RawByteString;
188  protected
189  function GetConnectionHandle:PZPostgreSQLConnect;
190  function GetPlainDriver:IZPostgreSQLPlainDriver;
191  function CreateResultSet(const SQL: string;
192  QueryHandle: PZPostgreSQLResult): IZResultSet;
193  procedure FetchOutParams(ResultSet: IZResultSet);
194  procedure TrimInParameters; override;
195  public
196  constructor Create(Connection: IZConnection; const SQL: string; Info: TStrings);
197 
198  function ExecuteQuery(const SQL: RawByteString): IZResultSet; override;
199  function ExecuteUpdate(const SQL: RawByteString): Integer; override;
200 
201  function ExecuteQueryPrepared: IZResultSet; override;
202  function ExecuteUpdatePrepared: Integer; override;
203  end;
204 
205  {** Implements a specialized cached resolver for PostgreSQL. }
206  TZPostgreSQLCachedResolver = class(TZGenericCachedResolver, IZCachedResolver)
207  protected
208  function CheckKeyColumn(ColumnIndex: Integer): Boolean; override;
209  end;
210 
211 implementation
212 
213 uses
214  ZMessages, ZDbcPostgreSqlResultSet, ZDbcPostgreSqlUtils, ZTokenizer,
215  ZEncoding{$IFDEF WITH_UNITANSISTRINGS}, AnsiStrings{$ENDIF};
216 
217 { TZPostgreSQLStatement }
218 
219 {**
220  Constructs this object and assignes the main properties.
221  @param PlainDriver a PostgreSQL plain driver.
222  @param Connection a database connection object.
223  @param Info a statement parameters.
224  @param Handle a connection handle pointer.
225 }
226 constructor TZPostgreSQLStatement.Create(PlainDriver: IZPostgreSQLPlainDriver;
227  Connection: IZConnection; Info: TStrings);
228 begin
229  inherited Create(Connection, Info);
230  FPlainDriver := PlainDriver;
231  ResultSetType := rtScrollInsensitive;
232  { Processes connection properties. }
233  FOidAsBlob := StrToBoolEx(Self.Info.Values['oidasblob'])
234  or (Connection as IZPostgreSQLConnection).IsOidAsBlob;
235 end;
236 {**
237  Destroys this object and cleanups the memory.
238 }
239 destructor TZPostgreSQLStatement.Destroy;
240 begin
241  inherited Destroy;
242 end;
243 
244 {**
245  Checks is oid should be treated as Large Object.
246  @return <code>True</code> if oid should represent a Large Object.
247 }
248 function TZPostgreSQLStatement.IsOidAsBlob: Boolean;
249 begin
250  Result := FOidAsBlob;
251 end;
252 
253 {**
254  Creates a result set based on the current settings.
255  @return a created result set object.
256 }
257 function TZPostgreSQLStatement.CreateResultSet(const SQL: string;
258  QueryHandle: PZPostgreSQLResult): IZResultSet;
259 var
260  NativeResultSet: TZPostgreSQLResultSet;
261  CachedResultSet: TZCachedResultSet;
262  ConnectionHandle: PZPostgreSQLConnect;
263 begin
264  ConnectionHandle := GetConnectionHandle();
265  NativeResultSet := TZPostgreSQLResultSet.Create(FPlainDriver, Self, SQL,
266  ConnectionHandle, QueryHandle, ChunkSize);
267 
268  NativeResultSet.SetConcurrency(rcReadOnly);
269  if GetResultSetConcurrency = rcUpdatable then
270  begin
271  CachedResultSet := TZCachedResultSet.Create(NativeResultSet, SQL, nil, ConSettings);
272  CachedResultSet.SetConcurrency(rcUpdatable);
273  CachedResultSet.SetResolver(TZPostgreSQLCachedResolver.Create(
274  Self, NativeResultSet.GetMetadata));
275  Result := CachedResultSet;
276  end
277  else
278  Result := NativeResultSet;
279 end;
280 
281 {**
282  Executes an SQL statement that returns a single <code>ResultSet</code> object.
283  @param sql typically this is a static SQL <code>SELECT</code> statement
284  @return a <code>ResultSet</code> object that contains the data produced by the
285  given query; never <code>null</code>
286 }
287 function TZPostgreSQLStatement.ExecuteQuery(const SQL: RawByteString): IZResultSet;
288 var
289  QueryHandle: PZPostgreSQLResult;
290  ConnectionHandle: PZPostgreSQLConnect;
291 begin
292  Result := nil;
293  ConnectionHandle := GetConnectionHandle();
294  ASQL := SQL; //Preprepares the SQL and Sets the AnsiSQL
295  QueryHandle := FPlainDriver.ExecuteQuery(ConnectionHandle, PAnsiChar(ASQL));
296  CheckPostgreSQLError(Connection, FPlainDriver, ConnectionHandle, lcExecute,
297  SSQL, QueryHandle);
298  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, LogSQL);
299  if QueryHandle <> nil then
300  Result := CreateResultSet(LogSQL, QueryHandle)
301  else
302  Result := nil;
303 end;
304 
305 {**
306  Executes an SQL <code>INSERT</code>, <code>UPDATE</code> or
307  <code>DELETE</code> statement. In addition,
308  SQL statements that return nothing, such as SQL DDL statements,
309  can be executed.
310 
311  @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
312  <code>DELETE</code> statement or an SQL statement that returns nothing
313  @return either the row count for <code>INSERT</code>, <code>UPDATE</code>
314  or <code>DELETE</code> statements, or 0 for SQL statements that return nothing
315 }
316 function TZPostgreSQLStatement.ExecuteUpdate(const SQL: RawByteString): Integer;
317 var
318  QueryHandle: PZPostgreSQLResult;
319  ConnectionHandle: PZPostgreSQLConnect;
320 begin
321  Result := -1;
322  ConnectionHandle := GetConnectionHandle();
323 
324  ASQL := SQL; //Prepares SQL if needed
325  QueryHandle := FPlainDriver.ExecuteQuery(ConnectionHandle, PAnsiChar(ASQL));
326  CheckPostgreSQLError(Connection, FPlainDriver, ConnectionHandle, lcExecute,
327  LogSQL, QueryHandle);
328  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, LogSQL);
329 
330  if QueryHandle <> nil then
331  begin
332  Result := StrToIntDef(String(FPlainDriver.GetCommandTuples(QueryHandle)), 0);
333  FPlainDriver.Clear(QueryHandle);
334  end;
335 
336  { Autocommit statement. }
337  if Connection.GetAutoCommit then
338  Connection.Commit;
339 end;
340 
341 {**
342  Executes an SQL statement that may return multiple results.
343  Under some (uncommon) situations a single SQL statement may return
344  multiple result sets and/or update counts. Normally you can ignore
345  this unless you are (1) executing a stored procedure that you know may
346  return multiple results or (2) you are dynamically executing an
347  unknown SQL string. The methods <code>execute</code>,
348  <code>getMoreResults</code>, <code>getResultSet</code>,
349  and <code>getUpdateCount</code> let you navigate through multiple results.
350 
351  The <code>execute</code> method executes an SQL statement and indicates the
352  form of the first result. You can then use the methods
353  <code>getResultSet</code> or <code>getUpdateCount</code>
354  to retrieve the result, and <code>getMoreResults</code> to
355  move to any subsequent result(s).
356 
357  @param sql any SQL statement
358  @return <code>true</code> if the next result is a <code>ResultSet</code> object;
359  <code>false</code> if it is an update count or there are no more results
360 }
361 function TZPostgreSQLStatement.Execute(const SQL: RawByteString): Boolean;
362 var
363  QueryHandle: PZPostgreSQLResult;
364  ResultStatus: TZPostgreSQLExecStatusType;
365  ConnectionHandle: PZPostgreSQLConnect;
366 begin
367  ASQL := SQL;
368  ConnectionHandle := GetConnectionHandle();
369  QueryHandle := FPlainDriver.ExecuteQuery(ConnectionHandle, PAnsiChar(ASQL));
370  CheckPostgreSQLError(Connection, FPlainDriver, ConnectionHandle, lcExecute,
371  LogSQL, QueryHandle);
372  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, LogSQL);
373 
374  { Process queries with result sets }
375  ResultStatus := FPlainDriver.GetResultStatus(QueryHandle);
376  case ResultStatus of
377  PGRES_TUPLES_OK:
378  begin
379  Result := True;
380  LastResultSet := CreateResultSet(LogSQL, QueryHandle);
381  end;
382  PGRES_COMMAND_OK:
383  begin
384  Result := False;
385  LastUpdateCount := StrToIntDef(String(
386  FPlainDriver.GetCommandTuples(QueryHandle)), 0);
387  FPlainDriver.Clear(QueryHandle);
388  end;
389  else
390  begin
391  Result := False;
392  LastUpdateCount := StrToIntDef(String(
393  FPlainDriver.GetCommandTuples(QueryHandle)), 0);
394  FPlainDriver.Clear(QueryHandle);
395  end;
396  end;
397 
398  { Autocommit statement. }
399  if not Result and Connection.GetAutoCommit then
400  Connection.Commit;
401 end;
402 
403 {**
404  Provides connection handle from the associated IConnection
405 }
406 function TZPostgreSQLStatement.GetConnectionHandle():PZPostgreSQLConnect;
407 begin
408  if Self.Connection = nil then
409  Result := nil
410  else
411  Result := (Connection as IZPostgreSQLConnection).GetConnectionHandle;
412 end;
413 
414 {$IFDEF ZEOS_TEST_ONLY}
415 { TZPostgreSQLEmulatedPreparedStatement }
416 
417 {**
418  Constructs this object and assignes the main properties.
419  @param PlainDriver a PostgreSQL plain driver.
420  @param Connection a database connection object.
421  @param Info a statement parameters.
422  @param Handle a connection handle pointer.
423 }
424 constructor TZPostgreSQLEmulatedPreparedStatement.Create(
425  PlainDriver: IZPostgreSQLPlainDriver; Connection: IZConnection;
426  const SQL: string; Info: TStrings);
427 begin
428  inherited Create(Connection, SQL, Info);
429  FPlainDriver := PlainDriver;
430  ResultSetType := rtScrollInsensitive;
431  Foidasblob := StrToBoolDef(Self.Info.Values['oidasblob'], False) or
432  (Connection as IZPostgreSQLConnection).IsOidAsBlob;
433 end;
434 
435 {**
436  Creates a temporary statement which executes queries.
437  @return a created statement object.
438 }
439 function TZPostgreSQLEmulatedPreparedStatement.CreateExecStatement: IZStatement;
440 begin
441  Result := TZPostgreSQLStatement.Create(FPlainDriver, Connection, Info);
442 end;
443 
444 {**
445  Prepares an SQL parameter for the query.
446  @param ParameterIndex the first parameter is 1, the second is 2, ...
447  @return a string representation of the parameter.
448 }
449 function TZPostgreSQLEmulatedPreparedStatement.PrepareAnsiSQLParam(
450  ParamIndex: Integer): RawByteString;
451 begin
452  if InParamCount <= ParamIndex then
453  raise EZSQLException.Create(SInvalidInputParameterCount);
454 
455  Result := PGPrepareAnsiSQLParam(InParamValues[ParamIndex],
456  (Connection as IZPostgreSQLConnection), FPlainDriver, ChunkSize,
457  InParamTypes[ParamIndex], Foidasblob, True, False, ConSettings);
458 end;
459 
460 {**
461  Provides connection handle from the associated IConnection
462 }
463 function TZPostgreSQLEmulatedPreparedStatement.GetConnectionHandle:PZPostgreSQLConnect;
464 begin
465  if Self.Connection = nil then
466  Result := nil
467  else
468  Result := (self.Connection as IZPostgreSQLConnection).GetConnectionHandle;
469 end;
470 {$ENDIF}
471 
472 { TZPostgreSQLPreparedStatement }
473 
474 {**
475  Creates a result set based on the current settings.
476  @param QueryHandle the Postgres query handle
477  @return a created result set object.
478 }
479 constructor TZPostgreSQLPreparedStatement.Create(PlainDriver: IZPostgreSQLPlainDriver;
480  Connection: IZPostgreSQLConnection; const SQL: string; Info: TStrings);
481 begin
482  inherited Create(Connection, SQL, Info);
483  Foidasblob := StrToBoolDef(Self.Info.Values['oidasblob'], False) or
484  (Connection as IZPostgreSQLConnection).IsOidAsBlob;
485  FPostgreSQLConnection := Connection;
486  FPlainDriver := PlainDriver;
487  ResultSetType := rtScrollInsensitive;
488  FConnectionHandle := Connection.GetConnectionHandle;
489  Findeterminate_datatype := False;
490  SetPlanNames;
491 end;
492 
493 destructor TZPostgreSQLPreparedStatement.Destroy;
494 begin
495  if Assigned(FCachedQuery) then
496  FReeAndNil(FCachedQuery);
497  inherited Destroy;
498 end;
499 
500 function TZPostgreSQLPreparedStatement.CreateResultSet(QueryHandle: Pointer): IZResultSet;
501 var
502  NativeResultSet: TZPostgreSQLResultSet;
503  CachedResultSet: TZCachedResultSet;
504 begin
505  NativeResultSet := TZPostgreSQLResultSet.Create(FPlainDriver, Self, Self.SQL,
506  FConnectionHandle, QueryHandle, ChunkSize);
507 
508  NativeResultSet.SetConcurrency(rcReadOnly);
509  if GetResultSetConcurrency = rcUpdatable then
510  begin
511  CachedResultSet := TZCachedResultSet.Create(NativeResultSet, Self.SQL, nil,
512  ConSettings);
513  CachedResultSet.SetConcurrency(rcUpdatable);
514  CachedResultSet.SetResolver(TZPostgreSQLCachedResolver.Create(
515  Self, NativeResultSet.GetMetadata));
516  Result := CachedResultSet;
517  end
518  else
519  Result := NativeResultSet;
520 end;
521 
522 { TZPostgreSQLClassicPreparedStatement }
523 
524 function TZPostgreSQLClassicPreparedStatement.GetAnsiSQLQuery;
525 var
526  I: Integer;
527  ParamIndex: Integer;
528  Tokens: TStrings;
529 
530  function TokenizeSQLQuery: TStrings;
531  var
532  I: Integer;
533  Tokens: TStrings;
534  Temp: string;
535  begin
536  if FCachedQuery = nil then
537  begin
538  FCachedQuery := TStringList.Create;
539  if Pos('?', SQL) > 0 then
540  begin
541  Tokens := Connection.GetDriver.GetTokenizer.TokenizeBufferToList(SQL, [toUnifyWhitespaces]);
542  try
543  Temp := '';
544  for I := 0 to Tokens.Count - 1 do
545  begin
546  if Tokens[I] = '?' then
547  begin
548  FCachedQuery.Add(Temp);
549  FCachedQuery.AddObject('?', Self);
550  Temp := '';
551  end
552  else
553  Temp := Temp + Tokens[I];
554  end;
555  if Temp <> '' then
556  FCachedQuery.Add(Temp);
557  finally
558  Tokens.Free;
559  end;
560  end
561  else
562  FCachedQuery.Add(SQL);
563  end;
564  Result := FCachedQuery;
565  end;
566 begin
567  ParamIndex := 0;
568  Result := '';
569  Tokens := TokenizeSQLQuery;
570 
571  for I := 0 to Tokens.Count - 1 do
572  begin
573  if Tokens[I] = '?' then
574  begin
575  Result := Result + PrepareAnsiSQLParam(ParamIndex, True);
576  Inc(ParamIndex);
577  end
578  else
579  Result := Result + ZPlainString(Tokens[I]);
580  end;
581 end;
582 
583 procedure TZPostgreSQLClassicPreparedStatement.SetPlanNames;
584 begin
585  FPlanName := '"'+IntToStr(Hash(ASQL)+Cardinal(FStatementId)+NativeUInt(FConnectionHandle))+'"';
586  FRawPlanName := {$IFDEF UNICODE}RawByteString{$ENDIF}(FPlanName);
587 end;
588 
589 function TZPostgreSQLClassicPreparedStatement.PrepareAnsiSQLParam(ParamIndex: Integer;
590  Escaped: Boolean): RawByteString;
591 begin
592  if InParamCount <= ParamIndex then
593  raise EZSQLException.Create(SInvalidInputParameterCount);
594 
595  Result := PGPrepareAnsiSQLParam(InParamValues[ParamIndex],
596  (Connection as IZPostgreSQLConnection), FPlainDriver, ChunkSize,
597  InParamTypes[ParamIndex], Foidasblob, Escaped, True, ConSettings);
598 end;
599 
600 procedure TZPostgreSQLClassicPreparedStatement.PrepareInParameters;
601 var
602  I, N: Integer;
603  Tokens: TStrings;
604  TempSQL: String;
605  QueryHandle: PZPostgreSQLResult;
606 begin
607  if Pos('?', SQL) > 0 then
608  begin
609  Tokens := Connection.GetDriver.GetTokenizer.
610  TokenizeBufferToList(SQL, [toUnifyWhitespaces]);
611  try
612  TempSQL := 'PREPARE '+FPlanName+' AS ';
613  N := 0;
614  for I := 0 to Tokens.Count - 1 do
615  begin
616  if Tokens[I] = '?' then
617  begin
618  Inc(N);
619  TempSQL := TempSQL + '$' + IntToStr(N);
620  end else
621  TempSQL := TempSQL + Tokens[I];
622  end;
623  finally
624  Tokens.Free;
625  end;
626  end
627  else Exit;
628 
629  {$IFDEF UNICODE}WSQL{$ELSE}ASQL{$ENDIF} := TempSQL;
630  QueryHandle := FPlainDriver.ExecuteQuery(FConnectionHandle,
631  PAnsiChar(ASQL));
632  CheckPostgreSQLError(Connection, FPlainDriver, FConnectionHandle, lcPrepStmt,
633  SSQL, QueryHandle);
634  DriverManager.LogMessage(lcPrepStmt, FPlainDriver.GetProtocol, SSQL);
635  FPlainDriver.Clear(QueryHandle);
636 end;
637 
638 procedure TZPostgreSQLClassicPreparedStatement.BindInParameters;
639 var
640  I: Integer;
641 begin
642  if Self.InParamCount > 0 then
643  begin
644  if Prepared then
645  begin
646  FExecSQL := 'EXECUTE '+FRawPlanName+'(';
647  for i := 0 to InParamCount -1 do
648  if I = 0 then
649  FExecSQL := FExecSQL+PrepareAnsiSQLParam(i, False)
650  else
651  FExecSQL := FExecSQL+','+PrepareAnsiSQLParam(i, False);
652  FExecSQL := FExecSQL+');';
653  end
654  else
655  FExecSQL := GetAnsiSQLQuery;
656  end
657  else
658  FExecSQL := ASQL;
659 end;
660 
661 procedure TZPostgreSQLClassicPreparedStatement.UnPrepareInParameters;
662 begin
663  if Prepared and Assigned(FPostgreSQLConnection.GetConnectionHandle) then
664  begin
665  ASQL := 'DEALLOCATE '+FRawPlanName+';';
666  Execute(ASQL);
667  end;
668 end;
669 
670 procedure TZPostgreSQLClassicPreparedStatement.Prepare;
671 begin
672  { EgonHugeist: assume automated Prepare after third execution. That's the way
673  the JDBC Drivers go too... }
674  if (not Prepared ) and ( InParamCount > 0 ) and ( ExecCount > 2 ) then
675  inherited Prepare;
676  BindInParameters;
677 end;
678 
679 {**
680  Executes an SQL statement that returns a single <code>ResultSet</code> object.
681  @param sql typically this is a static SQL <code>SELECT</code> statement
682  @return a <code>ResultSet</code> object that contains the data produced by the
683  given query; never <code>null</code>
684 }
685 function TZPostgreSQLClassicPreparedStatement.ExecuteQuery(const SQL: RawByteString): IZResultSet;
686 begin
687  Result := nil;
688  ASQL := SQL; //Preprepares the SQL and Sets the AnsiSQL
689  QueryHandle := FPlainDriver.ExecuteQuery(FConnectionHandle, PAnsiChar(ASQL));
690  CheckPostgreSQLError(Connection, FPlainDriver,
691  FConnectionHandle, lcExecute, SSQL, QueryHandle);
692  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, Self.SSQL);
693  if QueryHandle <> nil then
694  Result := CreateResultSet(QueryHandle)
695  else
696  Result := nil;
697 end;
698 
699 {**
700  Executes an SQL <code>INSERT</code>, <code>UPDATE</code> or
701  <code>DELETE</code> statement. In addition,
702  SQL statements that return nothing, such as SQL DDL statements,
703  can be executed.
704 
705  @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
706  <code>DELETE</code> statement or an SQL statement that returns nothing
707  @return either the row count for <code>INSERT</code>, <code>UPDATE</code>
708  or <code>DELETE</code> statements, or 0 for SQL statements that return nothing
709 }
710 function TZPostgreSQLClassicPreparedStatement.ExecuteUpdate(const SQL: RawByteString): Integer;
711 var
712  QueryHandle: PZPostgreSQLResult;
713 begin
714  Result := -1;
715  ASQL := SQL; //Preprepares the SQL and Sets the AnsiSQL
716  QueryHandle := FPlainDriver.ExecuteQuery(FConnectionHandle, PAnsiChar(ASQL));
717  CheckPostgreSQLError(Connection, FPlainDriver, FConnectionHandle, lcExecute,
718  SSQL, QueryHandle);
719  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, SSQL);
720 
721  if QueryHandle <> nil then
722  begin
723  Result := StrToIntDef(String(FPlainDriver.GetCommandTuples(QueryHandle)), 0);
724  FPlainDriver.Clear(QueryHandle);
725  end;
726 
727  { Autocommit statement. }
728  if Connection.GetAutoCommit then
729  Connection.Commit;
730 end;
731 
732 {**
733  Executes an SQL statement that may return multiple results.
734  Under some (uncommon) situations a single SQL statement may return
735  multiple result sets and/or update counts. Normally you can ignore
736  this unless you are (1) executing a stored procedure that you know may
737  return multiple results or (2) you are dynamically executing an
738  unknown SQL string. The methods <code>execute</code>,
739  <code>getMoreResults</code>, <code>getResultSet</code>,
740  and <code>getUpdateCount</code> let you navigate through multiple results.
741 
742  The <code>execute</code> method executes an SQL statement and indicates the
743  form of the first result. You can then use the methods
744  <code>getResultSet</code> or <code>getUpdateCount</code>
745  to retrieve the result, and <code>getMoreResults</code> to
746  move to any subsequent result(s).
747 
748  @param sql any SQL statement
749  @return <code>true</code> if the next result is a <code>ResultSet</code> object;
750  <code>false</code> if it is an update count or there are no more results
751 }
752 function TZPostgreSQLClassicPreparedStatement.Execute(const SQL: RawByteString): Boolean;
753 var
754  QueryHandle: PZPostgreSQLResult;
755  ResultStatus: TZPostgreSQLExecStatusType;
756 begin
757  ASQL := SQL; //Preprepares the SQL and Sets the AnsiSQL
758  QueryHandle := FPlainDriver.ExecuteQuery(FConnectionHandle,
759  PAnsiChar(ASQL));
760  CheckPostgreSQLError(Connection, FPlainDriver, FConnectionHandle, lcExecute,
761  SSQL, QueryHandle);
762  DriverManager.LogMessage(lcExecute, FPlainDriver.GetProtocol, SSQL);
763 
764  { Process queries with result sets }
765  ResultStatus := FPlainDriver.GetResultStatus(QueryHandle);
766  case ResultStatus of
767  PGRES_TUPLES_OK:
768  begin
769  Result := True;
770  LastResultSet := CreateResultSet(QueryHandle);
771  end;
772  PGRES_COMMAND_OK:
773  begin
774  Result := False;
775  LastUpdateCount := StrToIntDef(String(
776  FPlainDriver.GetCommandTuples(QueryHandle)), 0);
777  FPlainDriver.Clear(QueryHandle);
778  end;
779  else
780  begin
781  Result := False;
782  LastUpdateCount := StrToIntDef(String(
783  FPlainDriver.GetCommandTuples(QueryHandle)), 0);
784  FPlainDriver.Clear(QueryHandle);
785  end;
786  end;
787 
788  { Autocommit statement. }
789  if not Result and Connection.GetAutoCommit then
790  Connection.Commit;
791 end;
792 
793 {**
794  Executes the SQL query in this <code>PreparedStatement</code> object
795  and returns the result set generated by the query.
796 
797  @return a <code>ResultSet</code> object that contains the data produced by the
798  query; never <code>null</code>
799 }
800 function TZPostgreSQLClassicPreparedStatement.ExecuteQueryPrepared: IZResultSet;
801 begin
802  Prepare;
803  Result := ExecuteQuery(FExecSQL);
804  inherited ExecuteQueryPrepared;
805 end;
806 
807 {**
808  Executes the SQL INSERT, UPDATE or DELETE statement
809  in this <code>PreparedStatement</code> object.
810  In addition,
811  SQL statements that return nothing, such as SQL DDL statements,
812  can be executed.
813 
814  @return either the row count for INSERT, UPDATE or DELETE statements;
815  or 0 for SQL statements that return nothing
816 }
817 function TZPostgreSQLClassicPreparedStatement.ExecuteUpdatePrepared: Integer;
818 begin
819  Prepare;
820  Result := ExecuteUpdate(FExecSQL);
821  inherited ExecuteUpdatePrepared;
822 end;
823 
824 {**
825  Executes any kind of SQL statement.
826  Some prepared statements return multiple results; the <code>execute</code>
827  method handles these complex statements as well as the simpler
828  form of statements handled by the methods <code>executeQuery</code>
829  and <code>executeUpdate</code>.
830  @see Statement#execute
831 }
832 function TZPostgreSQLClassicPreparedStatement.ExecutePrepared: Boolean;
833 begin
834  Prepare;
835  Result := Execute(FExecSQL);
836  inherited ExecutePrepared;
837 end;
838 
839 { TZPostgreSQLCAPIPreparedStatement }
840 
841 function TZPostgreSQLCAPIPreparedStatement.ExectuteInternal(const SQL: RawByteString;
842  const LogSQL: String; const LoggingCategory: TZLoggingCategory): PZPostgreSQLResult;
843 begin
844  case LoggingCategory of
845  lcPrepStmt:
846  begin
847  Result := FPlainDriver.Prepare(FConnectionHandle, PAnsiChar(RawByteString(FPlanName)),
848  PAnsiChar(SQL), InParamCount, nil);
849  Findeterminate_datatype := (CheckPostgreSQLError(Connection, FPlainDriver,
850  FConnectionHandle, LoggingCategory, LogSQL, Result) = '42P18');
851  DriverManager.LogMessage(LoggingCategory, FPlainDriver.GetProtocol, LogSQL);
852  if not Findeterminate_datatype then
853  FPostgreSQLConnection.RegisterPreparedStmtName(FPlanName);
854  Exit;
855  end;
856  lcExecPrepStmt:
857  Result := FPlainDriver.ExecPrepared(FConnectionHandle,
858  PAnsiChar(RawByteString(FPlanName)), InParamCount, FPQparamValues,
859  FPQparamLengths, FPQparamFormats, 0);
860  lcUnprepStmt:
861  if Assigned(FConnectionHandle) then
862  Result := FPlainDriver.ExecuteQuery(FConnectionHandle, PAnsiChar(SQL))
863  else Result := nil;
864  else
865  Result := FPlainDriver.ExecuteQuery(FConnectionHandle, PAnsiChar(SQL));
866  end;
867  if Assigned(FConnectionHandle) then
868  CheckPostgreSQLError(Connection, FPlainDriver, FConnectionHandle,
869  LoggingCategory, LogSQL, Result);
870  DriverManager.LogMessage(LoggingCategory, FPlainDriver.GetProtocol, LogSQL);
871 end;
872 procedure TZPostgreSQLCAPIPreparedStatement.SetPlanNames;
873 begin
874  FPlanName := IntToStr(Hash(ASQL)+Cardinal(FStatementId)+NativeUInt(FConnectionHandle));
875  FRawPlanName := {$IFDEF UNICODE}RawByteString{$ENDIF}(FPlanName);
876 end;
877 
878 procedure TZPostgreSQLCAPIPreparedStatement.SetASQL(const Value: RawByteString);
879 begin
880  if ( ASQL <> Value ) and Prepared then
881  Unprepare;
882  inherited SetASQL(Value);
883 end;
884 
885 procedure TZPostgreSQLCAPIPreparedStatement.SetWSQL(const Value: ZWideString);
886 begin
887  if ( WSQL <> Value ) and Prepared then
888  Unprepare;
889  inherited SetWSQL(Value);
890 end;
891 
892 procedure TZPostgreSQLCAPIPreparedStatement.PrepareInParameters;
893 begin
894  if not (Findeterminate_datatype) then
895  begin
896  SetLength(FPQparamValues, InParamCount);
897  SetLength(FPQparamLengths, InParamCount);
898  SetLength(FPQparamFormats, InParamCount);
899  end;
900 end;
901 
902 procedure TZPostgreSQLCAPIPreparedStatement.BindInParameters;
903 var
904  Value: TZVariant;
905  TempBlob: IZBlob;
906  TempStream: TStream;
907  WriteTempBlob: IZPostgreSQLBlob;
908  ParamIndex: Integer;
909  TempBytes: TByteDynArray;
910 
911  procedure UpdateNull(const Index: Integer);
912  begin
913  FreeMem(FPQparamValues[Index]);
914 
915  FPQparamValues[Index] := nil;
916  FPQparamLengths[Index] := 0;
917  FPQparamFormats[Index] := 0;
918  end;
919 
920  procedure UpdateString(Value: RawByteString; const Index: Integer);
921  begin
922  UpdateNull(Index);
923 
924  FPQparamValues[ParamIndex] := AllocMem(Length(Value) + 1);
925  {$IFDEF WITH_STRPCOPY_DEPRECATED}AnsiStrings.{$ENDIF}StrCopy(FPQparamValues[Index], PAnsiChar(Value));
926  end;
927 
928  procedure UpdateBinary(Value: Pointer; const Len, Index: Integer);
929  begin
930  UpdateNull(Index);
931 
932  FPQparamValues[Index] := AllocMem(Len);
933  System.Move(Value^, FPQparamValues[Index]^, Len);
934  FPQparamLengths[Index] := Len;
935  FPQparamFormats[Index] := 1;
936  end;
937 
938 begin
939  if InParamCount <> High(FPQparamValues)+1 then
940  raise EZSQLException.Create(SInvalidInputParameterCount);
941 
942  for ParamIndex := 0 to InParamCount -1 do
943  begin
944  Value := InParamValues[ParamIndex];
945  if DefVarManager.IsNull(Value) then
946  UpdateNull(ParamIndex)
947  else
948  case InParamTypes[ParamIndex] of
949  stBoolean:
950  UpdateString(RawByteString(UpperCase(BoolToStrEx(SoftVarManager.GetAsBoolean(Value)))), ParamIndex);
951  stByte, stShort, stInteger, stLong, stBigDecimal, stFloat, stDouble:
952  UpdateString(RawByteString(SoftVarManager.GetAsString(Value)), ParamIndex);
953  stBytes:
954  begin
955  TempBytes := SoftVarManager.GetAsBytes(Value);
956  UpdateBinary(PAnsiChar(TempBytes), Length(TempBytes), ParamIndex);
957  end;
958  stString:
959  UpdateString(ZPlainString(SoftVarManager.GetAsString(Value), GetConnection.GetEncoding), ParamIndex);
960  stUnicodeString:
961  UpdateString(ZPlainString(SoftVarManager.GetAsUnicodeString(Value)), ParamIndex);
962  stDate:
963  UpdateString(RawByteString(FormatDateTime('yyyy-mm-dd', SoftVarManager.GetAsDateTime(Value))), ParamIndex);
964  stTime:
965  UpdateString(RawByteString(FormatDateTime('hh":"mm":"ss"."zzz', SoftVarManager.GetAsDateTime(Value))), ParamIndex);
966  stTimestamp:
967  UpdateString(RawByteString(FormatDateTime('yyyy-mm-dd hh":"mm":"ss"."zzz', SoftVarManager.GetAsDateTime(Value))), ParamIndex);
968  stAsciiStream, stUnicodeStream, stBinaryStream:
969  begin
970  TempBlob := DefVarManager.GetAsInterface(Value) as IZBlob;
971  if not TempBlob.IsEmpty then
972  begin
973  case InParamTypes[ParamIndex] of
974  stBinaryStream:
975  if ((GetConnection as IZPostgreSQLConnection).IsOidAsBlob) or
976  StrToBoolDef(Info.Values['oidasblob'], False) then
977  begin
978  TempStream := TempBlob.GetStream;
979  try
980  WriteTempBlob := TZPostgreSQLBlob.Create(FPlainDriver, nil, 0,
981  FConnectionHandle, 0, ChunkSize);
982  WriteTempBlob.SetStream(TempStream);
983  WriteTempBlob.WriteBlob;
984  UpdateString(RawByteString(IntToStr(WriteTempBlob.GetBlobOid)), ParamIndex);
985  finally
986  WriteTempBlob := nil;
987  TempStream.Free;
988  end;
989  end
990  else
991  UpdateBinary(TempBlob.GetBuffer, TempBlob.Length, ParamIndex);
992  stAsciiStream, stUnicodeStream:
993  begin
994  UpdateString(GetValidatedAnsiStringFromBuffer(TempBlob.GetBuffer,
995  TempBlob.Length, TempBlob.WasDecoded, ConSettings), ParamIndex);
996  end;
997  end; {case..}
998  TempBlob := nil;
999  end
1000  else
1001  UpdateNull(ParamIndex);
1002  end; {if not TempBlob.IsEmpty then}
1003  end;
1004  end;
1005  inherited BindInParameters;
1006 end;
1007 
1008 {**
1009  Removes eventual structures for binding input parameters.
1010 }
1011 procedure TZPostgreSQLCAPIPreparedStatement.UnPrepareInParameters;
1012 var
1013  I: Integer;
1014 begin
1015  { release allocated memory }
1016  if not (Findeterminate_datatype) then
1017  begin
1018  for i := 0 to InParamCount-1 do
1019  begin
1020  FreeMem(FPQparamValues[i]);
1021  FPQparamValues[i] := nil;
1022  end;
1023  SetLength(FPQparamValues, 0);
1024  SetLength(FPQparamLengths, 0);
1025  SetLength(FPQparamFormats, 0);
1026  end;
1027 end;
1028 
1029 {**
1030  Prepares an SQL statement and inserts all data values.
1031  @return a prepared SQL statement.
1032 }
1033 function TZPostgreSQLCAPIPreparedStatement.PrepareAnsiSQLQuery: RawByteString;
1034 var
1035  I: Integer;
1036  ParamIndex: Integer;
1037  Tokens: TStrings;
1038 begin
1039  ParamIndex := 0;
1040  Result := '';
1041  Tokens := Connection.GetDriver.GetTokenizer.TokenizeBufferToList(SQL, [toUnifyWhitespaces]);
1042 
1043  for I := 0 to Tokens.Count - 1 do
1044  begin
1045  if Tokens[I] = '?' then
1046  begin
1047  if InParamCount <= ParamIndex then
1048  raise EZSQLException.Create(SInvalidInputParameterCount);
1049  Result := Result + PGPrepareAnsiSQLParam(InParamValues[ParamIndex],
1050  (Connection as IZPostgreSQLConnection), FPlainDriver, ChunkSize,
1051  InParamTypes[ParamIndex], Foidasblob, True, False, ConSettings);
1052  Inc(ParamIndex);
1053  end
1054  else
1055  Result := Result + ZPlainString(Tokens[I]);
1056  end;
1057  Tokens.Free;
1058 end;
1059 
1060 procedure TZPostgreSQLCAPIPreparedStatement.Prepare;
1061 var
1062  Tokens: TStrings;
1063  TempSQL: String;
1064  N, I: Integer;
1065 begin
1066  if not Prepared then
1067  begin
1068  N := 0;
1069  if Pos('?', SSQL) > 0 then
1070  begin
1071  TempSQL := ''; //init
1072  Tokens := Connection.GetDriver.GetTokenizer.
1073  TokenizeBufferToList(SSQL, [toUnifyWhitespaces]);
1074  try
1075  for I := 0 to Tokens.Count - 1 do
1076  begin
1077  if Tokens[I] = '?' then
1078  begin
1079  Inc(N);
1080  TempSQL := TempSQL + '$' + IntToStr(N);
1081  end else
1082  TempSQL := TempSQL + Tokens[I];
1083  end;
1084  finally
1085  Tokens.Free;
1086  end;
1087  end
1088  else TempSQL := SSQL;
1089 
1090  if ( N > 0 ) or ( ExecCount > 2 ) then //prepare only if Params are available or certain executions expected
1091  begin
1092  QueryHandle := ExectuteInternal(GetEncodedSQL(TempSQL), 'PREPARE '#39+TempSQL+#39, lcPrepStmt);
1093  if not (Findeterminate_datatype) then
1094  FPlainDriver.Clear(QueryHandle);
1095  inherited Prepare;
1096  end;
1097  end;
1098 end;
1099 
1100 procedure TZPostgreSQLCAPIPreparedStatement.Unprepare;
1101 var
1102  TempSQL: String;
1103 begin
1104  if Prepared then
1105  begin
1106  inherited Unprepare;
1107  if Assigned(FPostgreSQLConnection.GetConnectionHandle) and (not Findeterminate_datatype) then
1108  begin
1109  TempSQL := 'DEALLOCATE "'+FPlanName+'";';
1110  QueryHandle := ExectuteInternal(RawByteString(TempSQL), TempSQL, lcUnprepStmt);
1111  FPlainDriver.Clear(QueryHandle);
1112  FPostgreSQLConnection.UnregisterPreparedStmtName(FPlanName);
1113  end;
1114  end;
1115 end;
1116 
1117 function TZPostgreSQLCAPIPreparedStatement.ExecuteQueryPrepared: IZResultSet;
1118 begin
1119  Result := nil;
1120 
1121  Prepare;
1122  if Prepared then
1123  if Findeterminate_datatype then
1124  QueryHandle := ExectuteInternal(PrepareAnsiSQLQuery, SSQL, lcExecute)
1125  else
1126  begin
1127  BindInParameters;
1128  QueryHandle := ExectuteInternal(ASQL, SSQL, lcExecPrepStmt);
1129  end
1130  else
1131  QueryHandle := ExectuteInternal(ASQL, SSQL, lcExecute);
1132  if QueryHandle <> nil then
1133  Result := CreateResultSet(QueryHandle)
1134  else
1135  Result := nil;
1136  inherited ExecuteQueryPrepared;
1137 end;
1138 
1139 function TZPostgreSQLCAPIPreparedStatement.ExecuteUpdatePrepared: Integer;
1140 begin
1141  Result := -1;
1142  Prepare;
1143 
1144  if Prepared then
1145  if Findeterminate_datatype then
1146  QueryHandle := ExectuteInternal(PrepareAnsiSQLQuery, SSQL, lcExecute)
1147  else
1148  begin
1149  BindInParameters;
1150  QueryHandle := ExectuteInternal(ASQL, SSQL, lcExecPrepStmt);
1151  end
1152  else
1153  QueryHandle := ExectuteInternal(ASQL, SSQL, lcExecute);
1154 
1155  if QueryHandle <> nil then
1156  begin
1157  Result := StrToIntDef(String(FPlainDriver.GetCommandTuples(QueryHandle)), 0);
1158  FPlainDriver.Clear(QueryHandle);
1159  end;
1160 
1161  { Autocommit statement. }
1162  if Connection.GetAutoCommit then
1163  Connection.Commit;
1164 
1165  inherited ExecuteUpdatePrepared;
1166 end;
1167 
1168 function TZPostgreSQLCAPIPreparedStatement.ExecutePrepared: Boolean;
1169 var
1170  ResultStatus: TZPostgreSQLExecStatusType;
1171 begin
1172  Prepare;
1173 
1174  if Prepared then
1175  if Findeterminate_datatype then
1176  QueryHandle := ExectuteInternal(PrepareAnsiSQLQuery, SSQL, lcExecPrepStmt)
1177  else
1178  begin
1179  BindInParameters;
1180  QueryHandle := ExectuteInternal(ASQL, SSQL, lcExecPrepStmt);
1181  end
1182  else
1183  QueryHandle := ExectuteInternal(ASQL, SSQL, lcExecute);
1184 
1185  { Process queries with result sets }
1186  ResultStatus := FPlainDriver.GetResultStatus(QueryHandle);
1187  case ResultStatus of
1188  PGRES_TUPLES_OK:
1189  begin
1190  Result := True;
1191  LastResultSet := CreateResultSet(QueryHandle);
1192  end;
1193  PGRES_COMMAND_OK:
1194  begin
1195  Result := False;
1196  LastUpdateCount := StrToIntDef(String(
1197  FPlainDriver.GetCommandTuples(QueryHandle)), 0);
1198  FPlainDriver.Clear(QueryHandle);
1199  end;
1200  else
1201  begin
1202  Result := False;
1203  LastUpdateCount := StrToIntDef(String(
1204  FPlainDriver.GetCommandTuples(QueryHandle)), 0);
1205  FPlainDriver.Clear(QueryHandle);
1206  end;
1207  end;
1208 
1209  { Autocommit statement. }
1210  if not Result and Connection.GetAutoCommit then
1211  Connection.Commit;
1212 
1213  inherited ExecutePrepared;
1214 end;
1215 
1216 
1217 { TZPostgreSQLCallableStatement }
1218 
1219 {**
1220  Constructs this object and assignes the main properties.
1221  @param Connection a database connection object.
1222  @param Info a statement parameters.
1223  @param Handle a connection handle pointer.
1224 }
1225 constructor TZPostgreSQLCallableStatement.Create(
1226  Connection: IZConnection; const SQL: string; Info: TStrings);
1227 begin
1228  inherited Create(Connection, SQL, Info);
1229  ResultSetType := rtScrollInsensitive;
1230  FPlainDriver := (Connection as IZPostgreSQLConnection).GetPlainDriver;
1231  Foidasblob := StrToBoolDef(Self.Info.Values['oidasblob'], False) or
1232  (Connection as IZPostgreSQLConnection).IsOidAsBlob;
1233 end;
1234 
1235 {**
1236  Provides connection handle from the associated IConnection
1237  @return a PostgreSQL connection handle.
1238 }
1239 function TZPostgreSQLCallableStatement.GetConnectionHandle:PZPostgreSQLConnect;
1240 begin
1241  if Self.Connection = nil then
1242  Result := nil
1243  else
1244  Result := (self.Connection as IZPostgreSQLConnection).GetConnectionHandle;
1245 end;
1246 
1247 {**
1248  Creates a result set based on the current settings.
1249  @return a created result set object.
1250 }
1251 function TZPostgreSQLCallableStatement.CreateResultSet(const SQL: string;
1252  QueryHandle: PZPostgreSQLResult): IZResultSet;
1253 var
1254  NativeResultSet: TZPostgreSQLResultSet;
1255  CachedResultSet: TZCachedResultSet;
1256  ConnectionHandle: PZPostgreSQLConnect;
1257 begin
1258  ConnectionHandle := GetConnectionHandle();
1259  NativeResultSet := TZPostgreSQLResultSet.Create(GetPlainDriver, Self, SQL,
1260  ConnectionHandle, QueryHandle, ChunkSize);
1261  NativeResultSet.SetConcurrency(rcReadOnly);
1262  if GetResultSetConcurrency = rcUpdatable then
1263  begin
1264  CachedResultSet := TZCachedResultSet.Create(NativeResultSet, SQL, nil,
1265  ConSettings);
1266  CachedResultSet.SetConcurrency(rcUpdatable);
1267  CachedResultSet.SetResolver(TZPostgreSQLCachedResolver.Create(
1268  Self, NativeResultSet.GetMetadata));
1269  Result := CachedResultSet;
1270  end
1271  else
1272  Result := NativeResultSet;
1273 end;
1274 
1275 {**
1276  Returns plain draiver from connection object
1277  @return a PlainDriver object
1278 }
1279 function TZPostgreSQLCallableStatement.GetPlainDriver():IZPostgreSQLPlainDriver;
1280 begin
1281  if self.Connection <> nil then
1282  Result := (self.Connection as IZPostgreSQLConnection).GetPlainDriver
1283  else
1284  Result := nil;
1285 end;
1286 
1287 {**
1288  Prepares an SQL parameter for the query.
1289  @param ParameterIndex the first parameter is 1, the second is 2, ...
1290  @return a string representation of the parameter.
1291 }
1292 function TZPostgreSQLCallableStatement.PrepareAnsiSQLParam(
1293  ParamIndex: Integer): RawByteString;
1294 begin
1295  if InParamCount <= ParamIndex then
1296  raise EZSQLException.Create(SInvalidInputParameterCount);
1297 
1298  Result := PGPrepareAnsiSQLParam(InParamValues[ParamIndex],
1299  (Connection as IZPostgreSQLConnection), FPlainDriver, ChunkSize,
1300  InParamTypes[ParamIndex], Foidasblob, True, False, ConSettings);
1301 end;
1302 
1303 {**
1304  Executes an SQL statement that returns a single <code>ResultSet</code> object.
1305  @param sql typically this is a static SQL <code>SELECT</code> statement
1306  @return a <code>ResultSet</code> object that contains the data produced by the
1307  given query; never <code>null</code>
1308 }
1309 function TZPostgreSQLCallableStatement.ExecuteQuery(
1310  const SQL: RawByteString): IZResultSet;
1311 var
1312  QueryHandle: PZPostgreSQLResult;
1313  ConnectionHandle: PZPostgreSQLConnect;
1314 begin
1315  Result := nil;
1316  ConnectionHandle := GetConnectionHandle();
1317  ASQL := SQL; //Preprepares the SQL and Sets the AnsiSQL
1318  QueryHandle := GetPlainDriver.ExecuteQuery(ConnectionHandle,
1319  PAnsiChar(ASQL));
1320  CheckPostgreSQLError(Connection, GetPlainDriver, ConnectionHandle, lcExecute,
1321  LogSQL, QueryHandle);
1322  DriverManager.LogMessage(lcExecute, GetPlainDriver.GetProtocol, LogSQL);
1323  if QueryHandle <> nil then
1324  begin
1325  Result := CreateResultSet(SSQL, QueryHandle);
1326  FetchOutParams(Result);
1327  end
1328  else
1329  Result := nil;
1330 end;
1331 
1332 {**
1333  Prepares and executes an SQL statement that returns a single <code>ResultSet</code> object.
1334  @return a <code>ResultSet</code> object that contains the data produced by the
1335  given query; never <code>null</code>
1336 }
1337 function TZPostgreSQLCallableStatement.ExecuteQueryPrepared: IZResultSet;
1338 begin
1339  TrimInParameters;
1340  Result := ExecuteQuery(FillParams(GetProcedureSql));
1341 end;
1342 
1343 {**
1344  Create sql string for calling stored procedure.
1345  @return a Stored Procedure SQL string
1346 }
1347 function TZPostgreSQLCallableStatement.GetProcedureSql: string;
1348 
1349  function GenerateParamsStr(Count: integer): string;
1350  var
1351  I: integer;
1352  begin
1353  Result := '';
1354  for I := 0 to Count - 1 do
1355  begin
1356  if Result <> '' then
1357  Result := Result + ',';
1358  Result := Result + '?';
1359  end;
1360  end;
1361 
1362 var
1363  InParams: string;
1364 begin
1365  InParams := GenerateParamsStr(Length(InParamValues));
1366  Result := Format('SELECT * FROM %s(%s)', [SQL, InParams]);
1367 end;
1368 
1369 {**
1370  Fills the parameter (?) tokens with corresponding parameter value
1371  @return a prepared SQL query for execution
1372 }
1373 function TZPostgreSQLCallableStatement.FillParams(const ASql: String): RawByteString;
1374 var I: Integer;
1375  Tokens: TStrings;
1376  ParamIndex: Integer;
1377 begin
1378  if Pos('?', ASql) > 0 then
1379  begin
1380  Tokens := Connection.GetDriver.GetTokenizer.TokenizeBufferToList(ASql, [toUnifyWhitespaces]);
1381  try
1382  ParamIndex := 0;
1383  for I := 0 to Tokens.Count - 1 do
1384  if Tokens[I] = '?' then
1385  begin
1386  Result := Result + PrepareAnsiSQLParam(ParamIndex);
1387  Inc(ParamIndex);
1388  end
1389  else
1390  Result := Result + ZPlainString(Tokens[i]);
1391  finally
1392  Tokens.Free;
1393  end;
1394  end
1395  else
1396  Result := GetEncodedSQL(ASql);
1397 end;
1398 
1399 {**
1400  Executes an SQL <code>INSERT</code>, <code>UPDATE</code> or
1401  <code>DELETE</code> statement. In addition,
1402  SQL statements that return nothing, such as SQL DDL statements,
1403  can be executed.
1404 
1405  @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
1406  <code>DELETE</code> statement or an SQL statement that returns nothing
1407  @return either the row count for <code>INSERT</code>, <code>UPDATE</code>
1408  or <code>DELETE</code> statements, or 0 for SQL statements that return nothing
1409 }
1410 function TZPostgreSQLCallableStatement.ExecuteUpdate(const SQL: RawByteString): Integer;
1411 var
1412  QueryHandle: PZPostgreSQLResult;
1413  ConnectionHandle: PZPostgreSQLConnect;
1414 begin
1415  Result := -1;
1416  ConnectionHandle := GetConnectionHandle();
1417  ASQL := SQL; //Preprepares the SQL and Sets the AnsiSQL
1418  QueryHandle := GetPlainDriver.ExecuteQuery(ConnectionHandle,
1419  PAnsiChar(ASQL));
1420  CheckPostgreSQLError(Connection, GetPlainDriver, ConnectionHandle, lcExecute,
1421  SSQL, QueryHandle);
1422  DriverManager.LogMessage(lcExecute, GetPlainDriver.GetProtocol, SSQL);
1423 
1424  if QueryHandle <> nil then
1425  begin
1426  Result := StrToIntDef(String(GetPlainDriver.GetCommandTuples(QueryHandle)), 0);
1427  FetchOutParams(CreateResultSet(SSQL, QueryHandle));
1428  end;
1429 
1430  { Autocommit statement. }
1431  if Connection.GetAutoCommit then
1432  Connection.Commit;
1433 end;
1434 
1435 
1436 function TZPostgreSQLCallableStatement.ExecuteUpdatePrepared: Integer;
1437 begin
1438  TrimInParameters;
1439  Result := Self.ExecuteUpdate(FillParams(GetProcedureSql));
1440 end;
1441 
1442 {**
1443  Sets output parameters from a ResultSet
1444  @param Value a IZResultSet object.
1445 }
1446 procedure TZPostgreSQLCallableStatement.FetchOutParams(ResultSet: IZResultSet);
1447 var
1448  ParamIndex, I: Integer;
1449  Temp: TZVariant;
1450  HasRows: Boolean;
1451 begin
1452  ResultSet.BeforeFirst;
1453  HasRows := ResultSet.Next;
1454 
1455  I := 1;
1456  for ParamIndex := 0 to OutParamCount - 1 do
1457  begin
1458  if not (FDBParamTypes[ParamIndex] in [2, 3, 4]) then // ptOutput, ptInputOutput, ptResult
1459  Continue;
1460 
1461  if I > ResultSet.GetMetadata.GetColumnCount then
1462  Break;
1463 
1464  if (not HasRows) or (ResultSet.IsNull(I)) then
1465  DefVarManager.SetNull(Temp)
1466  else
1467  case ResultSet.GetMetadata.GetColumnType(I) of
1468  stBoolean:
1469  DefVarManager.SetAsBoolean(Temp, ResultSet.GetBoolean(I));
1470  stByte:
1471  DefVarManager.SetAsInteger(Temp, ResultSet.GetByte(I));
1472  stShort:
1473  DefVarManager.SetAsInteger(Temp, ResultSet.GetShort(I));
1474  stInteger:
1475  DefVarManager.SetAsInteger(Temp, ResultSet.GetInt(I));
1476  stLong:
1477  DefVarManager.SetAsInteger(Temp, ResultSet.GetLong(I));
1478  stFloat:
1479  DefVarManager.SetAsFloat(Temp, ResultSet.GetFloat(I));
1480  stDouble:
1481  DefVarManager.SetAsFloat(Temp, ResultSet.GetDouble(I));
1482  stBigDecimal:
1483  DefVarManager.SetAsFloat(Temp, ResultSet.GetBigDecimal(I));
1484  stString:
1485  DefVarManager.SetAsString(Temp, ResultSet.GetString(I));
1486  stUnicodeString:
1487  DefVarManager.SetAsUnicodeString(Temp, ResultSet.GetUnicodeString(I));
1488  stDate:
1489  DefVarManager.SetAsDateTime(Temp, ResultSet.GetDate(I));
1490  stTime:
1491  DefVarManager.SetAsDateTime(Temp, ResultSet.GetTime(I));
1492  stTimestamp:
1493  DefVarManager.SetAsDateTime(Temp, ResultSet.GetTimestamp(I));
1494  else
1495  DefVarManager.SetAsString(Temp, ResultSet.GetString(I));
1496  end;
1497  OutParamValues[ParamIndex] := Temp;
1498  Inc(I);
1499  end;
1500  ResultSet.BeforeFirst;
1501 end;
1502 
1503 {**
1504  Function removes ptResult, ptOutput parameters from
1505  InParamTypes and InParamValues
1506 }
1507 procedure TZPostgreSQLCallableStatement.TrimInParameters;
1508 var
1509  I: integer;
1510  ParamValues: TZVariantDynArray;
1511  ParamTypes: TZSQLTypeArray;
1512  ParamCount: Integer;
1513 begin
1514  ParamCount := 0;
1515  SetLength(ParamValues, InParamCount);
1516  SetLength(ParamTypes, InParamCount);
1517 
1518  for I := 0 to High(InParamTypes) do
1519  begin
1520  if (Self.FDBParamTypes[i] in [2, 4]) then //[ptResult, ptOutput]
1521  Continue;
1522  ParamTypes[ParamCount] := InParamTypes[I];
1523  ParamValues[ParamCount] := InParamValues[I];
1524  Inc(ParamCount);
1525  end;
1526 
1527  if ParamCount = InParamCount then
1528  Exit;
1529 
1530  InParamTypes := ParamTypes;
1531  InParamValues := ParamValues;
1532  SetInParamCount(ParamCount);
1533 end;
1534 
1535 { TZPostgreSQLCachedResolver }
1536 
1537 {**
1538  Checks is the specified column can be used in where clause.
1539  @param ColumnIndex an index of the column.
1540  @returns <code>true</code> if column can be included into where clause.
1541 }
1542 function TZPostgreSQLCachedResolver.CheckKeyColumn(ColumnIndex: Integer): Boolean;
1543 begin
1544  Result := (Metadata.GetTableName(ColumnIndex) <> '')
1545  and (Metadata.GetColumnName(ColumnIndex) <> '')
1546  and Metadata.IsSearchable(ColumnIndex)
1547  and not (Metadata.GetColumnType(ColumnIndex)
1548  in [stUnknown, stBinaryStream, stUnicodeStream]);
1549 end;
1550 
1551 
1552 
1553 end.
1554