zeoslib  UNKNOWN
 All Files
ZDbcInterbase6.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { Interbase Database Connectivity Classes }
5 { }
6 { Originally written by Sergey Merkuriev }
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 ZDbcInterbase6;
53 
54 interface
55 
56 {$I ZDbc.inc}
57 
58 uses
59  Types, Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} SysUtils, Contnrs,
60  ZPlainFirebirdDriver, ZPlainDriver, ZCompatibility, ZDbcUtils, ZDbcIntfs,
61  ZDbcConnection, ZPlainFirebirdInterbaseConstants, ZSysUtils, ZDbcLogging,
62  ZDbcInterbase6Utils, ZDbcGenericResolver, ZTokenizer, ZGenericSqlAnalyser,
63  ZURL;
64 
65 type
66 
67  {** Implements Interbase6 Database Driver. }
68  {$WARNINGS OFF}
69  TZInterbase6Driver = class(TZAbstractDriver)
70  public
71  constructor Create; override;
72  function Connect(const Url: TZURL): IZConnection; override;
73  function GetMajorVersion: Integer; override;
74  function GetMinorVersion: Integer; override;
75 
76  function GetTokenizer: IZTokenizer; override;
77  function GetStatementAnalyser: IZStatementAnalyser; override;
78  end;
79  {$WARNINGS ON}
80 
81  {** Represents a Interbase specific connection interface. }
82  IZInterbase6Connection = interface (IZConnection)
83  ['{E870E4FE-21EB-4725-B5D8-38B8A2B12D0B}']
84  function GetDBHandle: PISC_DB_HANDLE;
85  function GetTrHandle: PISC_TR_HANDLE;
86  function GetDialect: Word;
87  function GetPlainDriver: IZInterbasePlainDriver;
88  procedure CreateNewDatabase(const SQL: String);
89  end;
90 
91  {** Implements Interbase6 Database Connection. }
92 
93  { TZInterbase6Connection }
94 
95  TZInterbase6Connection = class(TZAbstractConnection, IZInterbase6Connection)
96  private
97  FDialect: Word;
98  FHandle: TISC_DB_HANDLE;
99  FTrHandle: TISC_TR_HANDLE;
100  FStatusVector: TARRAY_ISC_STATUS;
101  FHardCommit: boolean;
102  FDisposeClientCodePage: Boolean;
103  FHostVersion: Integer;
104  procedure CloseTransaction;
105  protected
106  procedure InternalCreate; override;
107  procedure OnPropertiesChange(Sender: TObject); override;
108  public
109  procedure StartTransaction;
110  procedure SetTransactionIsolation(Level: TZTransactIsolationLevel); override;
111  function GetHostVersion: Integer; override;
112  function GetDBHandle: PISC_DB_HANDLE;
113  function GetTrHandle: PISC_TR_HANDLE;
114  function GetDialect: Word;
115  function GetPlainDriver: IZInterbasePlainDriver;
116  procedure CreateNewDatabase(const SQL: String);
117 
118  function CreateRegularStatement(Info: TStrings): IZStatement; override;
119  function CreatePreparedStatement(const SQL: string; Info: TStrings):
120  IZPreparedStatement; override;
121  function CreateCallableStatement(const SQL: string; Info: TStrings):
122  IZCallableStatement; override;
123 
124  function CreateSequence(const Sequence: string; BlockSize: Integer):
125  IZSequence; override;
126 
127  procedure SetReadOnly(Value: Boolean); override;
128 
129  procedure Commit; override;
130  procedure Rollback; override;
131 
132  function PingServer: Integer; override;
133 
134  procedure Open; override;
135  procedure Close; override;
136 
137  function GetBinaryEscapeString(const Value: RawByteString): String; override;
138  function GetBinaryEscapeString(const Value: TByteDynArray): String; override;
139  function GetEscapeString(const Value: RawByteString): RawByteString; override;
140  function GetEscapeString(const Value: ZWideString): ZWideString; override;
141  end;
142 
143  {** Implements a specialized cached resolver for Interbase/Firebird. }
144  TZInterbase6CachedResolver = class(TZGenericCachedResolver)
145  public
146  function FormCalculateStatement(Columns: TObjectList): string; override;
147  end;
148 
149  {** Implements a Interbase 6 sequence. }
150  TZInterbase6Sequence = class(TZAbstractSequence)
151  public
152  function GetCurrentValue: Int64; override;
153  function GetNextValue: Int64; override;
154  function GetCurrentValueSQL: string; override;
155  function GetNextValueSQL: string; override;
156  end;
157 
158 
159 var
160  {** The common driver manager object. }
161  Interbase6Driver: IZDriver;
162 
163 implementation
164 
165 uses ZDbcInterbase6Statement, ZDbcInterbase6Metadata, ZEncoding,
166  ZInterbaseToken, ZInterbaseAnalyser
167  {$IFDEF WITH_UNITANSISTRINGS}, AnsiStrings{$ENDIF};
168 
169 { TZInterbase6Driver }
170 
171 {**
172  Attempts to make a database connection to the given URL.
173  The driver should return "null" if it realizes it is the wrong kind
174  of driver to connect to the given URL. This will be common, as when
175  the JDBC driver manager is asked to connect to a given URL it passes
176  the URL to each loaded driver in turn.
177 
178  <P>The driver should raise a SQLException if it is the right
179  driver to connect to the given URL, but has trouble connecting to
180  the database.
181 
182  <P>The java.util.Properties argument can be used to passed arbitrary
183  string tag/value pairs as connection arguments.
184  Normally at least "user" and "password" properties should be
185  included in the Properties.
186 
187  @param url the URL of the database to which to connect
188  @param info a list of arbitrary string tag/value pairs as
189  connection arguments. Normally at least a "user" and
190  "password" property should be included.
191  @return a <code>Connection</code> object that represents a
192  connection to the URL
193 }
194 {$WARNINGS OFF}
195 function TZInterbase6Driver.Connect(const Url: TZURL): IZConnection;
196 begin
197  Result := TZInterbase6Connection.Create(Url);
198 end;
199 {$WARNINGS ON}
200 
201 {**
202  Constructs this object with default properties.
203 }
204 constructor TZInterbase6Driver.Create;
205 begin
206  inherited Create;
207  AddSupportedProtocol(AddPlainDriverToCache(TZInterbase6PlainDriver.Create));
208  AddSupportedProtocol(AddPlainDriverToCache(TZFirebird10PlainDriver.Create));
209  AddSupportedProtocol(AddPlainDriverToCache(TZFirebird15PlainDriver.Create));
210  AddSupportedProtocol(AddPlainDriverToCache(TZFirebird20PlainDriver.Create));
211  AddSupportedProtocol(AddPlainDriverToCache(TZFirebird21PlainDriver.Create));
212  AddSupportedProtocol(AddPlainDriverToCache(TZFirebird25PlainDriver.Create));
213  // embedded drivers
214  AddSupportedProtocol(AddPlainDriverToCache(TZFirebirdD15PlainDriver.Create));
215  AddSupportedProtocol(AddPlainDriverToCache(TZFirebirdD20PlainDriver.Create));
216  AddSupportedProtocol(AddPlainDriverToCache(TZFirebirdD21PlainDriver.Create));
217  AddSupportedProtocol(AddPlainDriverToCache(TZFirebirdD25PlainDriver.Create));
218 end;
219 
220 {**
221  Gets the driver's major version number. Initially this should be 1.
222  @return this driver's major version number
223 }
224 function TZInterbase6Driver.GetMajorVersion: Integer;
225 begin
226  Result := 1;
227 end;
228 
229 {**
230  Gets the driver's minor version number. Initially this should be 0.
231  @return this driver's minor version number
232 }
233 function TZInterbase6Driver.GetMinorVersion: Integer;
234 begin
235  Result := 0;
236 end;
237 
238 {**
239  Gets a SQL syntax tokenizer.
240  @returns a SQL syntax tokenizer object.
241 }
242 function TZInterbase6Driver.GetTokenizer: IZTokenizer;
243 begin
244  Result := TZInterbaseTokenizer.Create;
245 end;
246 
247 {**
248  Creates a statement analyser object.
249  @returns a statement analyser object.
250 }
251 function TZInterbase6Driver.GetStatementAnalyser: IZStatementAnalyser;
252 begin
253  Result := TZInterbaseStatementAnalyser.Create; { thread save! Allways return a new Analyser! }
254 end;
255 
256 { TZInterbase6Connection }
257 
258 procedure TZInterbase6Connection.CloseTransaction;
259 begin
260  if FTrHandle <> 0 then
261  begin
262  if AutoCommit then
263  begin
264  GetPlainDriver.isc_commit_transaction(@FStatusVector, @FTrHandle);
265  DriverManager.LogMessage(lcTransaction, PlainDriver.GetProtocol,
266  Format('COMMIT TRANSACTION "%s"', [Database]));
267  end
268  else
269  begin
270  GetPlainDriver.isc_rollback_transaction(@FStatusVector, @FTrHandle);
271  DriverManager.LogMessage(lcTransaction, PlainDriver.GetProtocol,
272  Format('ROLLBACK TRANSACTION "%s"', [Database]));
273  end;
274  FTrHandle := 0;
275  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcDisconnect);
276  end;
277 end;
278 
279 {**
280  Releases a Connection's database and JDBC resources
281  immediately instead of waiting for
282  them to be automatically released.
283 
284  <P><B>Note:</B> A Connection is automatically closed when it is
285  garbage collected. Certain fatal errors also result in a closed
286  Connection.
287 }
288 procedure TZInterbase6Connection.Close;
289 begin
290  if Closed or (not Assigned(PlainDriver)) then
291  Exit;
292 
293  CloseTransaction;
294 
295  if FHandle <> 0 then
296  begin
297  GetPlainDriver.isc_detach_database(@FStatusVector, @FHandle);
298  FHandle := 0;
299  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcDisconnect);
300  end;
301 
302  DriverManager.LogMessage(lcConnect, PlainDriver.GetProtocol,
303  Format('DISCONNECT FROM "%s"', [Database]));
304 
305  inherited Close;
306 end;
307 
308 {**
309  Commit current transaction
310 }
311 procedure TZInterbase6Connection.Commit;
312 begin
313  if Closed then
314  Exit;
315 
316  if FTrHandle <> 0 then
317  begin
318  if FHardCommit then
319  begin
320  GetPlainDriver.isc_commit_transaction(@FStatusVector, @FTrHandle);
321  FTrHandle := 0; //normaly not required! Old server code?
322  end
323  else
324  GetPlainDriver.isc_commit_retaining(@FStatusVector, @FTrHandle);
325 
326  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcTransaction);
327  DriverManager.LogMessage(lcTransaction,
328  PlainDriver.GetProtocol, 'TRANSACTION COMMIT');
329  end;
330 end;
331 
332 {**
333  Constructs this object and assignes the main properties.
334 }
335 procedure TZInterbase6Connection.InternalCreate;
336 var
337  RoleName: string;
338  ConnectTimeout : integer;
339 begin
340  FDisposeClientCodePage := False;
341  Self.FMetadata := TZInterbase6DatabaseMetadata.Create(Self, Url);
342 
343  FHardCommit := StrToBoolEx(URL.Properties.Values['hard_commit']);
344  { Sets a default Interbase port }
345 
346  if Self.Port = 0 then
347  Self.Port := 3050;
348 
349  { set default sql dialect it can be overriden }
350  FDialect := 3;
351 
352  FDialect := StrToIntDef(URL.Properties.Values['dialect'], FDialect);
353 
354  { Processes connection properties. }
355  self.Info.Values['isc_dpb_username'] := Url.UserName;
356  self.Info.Values['isc_dpb_password'] := Url.Password;
357 
358  if FClientCodePage = '' then //was set on inherited Create(...)
359  if URL.Properties.Values['isc_dpb_lc_ctype'] <> '' then //Check if Dev set's it manually
360  begin
361  FClientCodePage := URL.Properties.Values['isc_dpb_lc_ctype'];
362  CheckCharEncoding(FClientCodePage, True);
363  end;
364  URL.Properties.Values['isc_dpb_lc_ctype'] := FClientCodePage;
365 
366  RoleName := Trim(URL.Properties.Values['rolename']);
367  if RoleName <> '' then
368  URL.Properties.Values['isc_dpb_sql_role_name'] := UpperCase(RoleName);
369 
370  ConnectTimeout := StrToIntDef(URL.Properties.Values['timeout'], -1);
371  if ConnectTimeout >= 0 then
372  URL.Properties.Values['isc_dpb_connect_timeout'] := IntToStr(ConnectTimeout);
373 
374  FHandle := 0;
375 end;
376 
377 procedure TZInterbase6Connection.OnPropertiesChange(Sender: TObject);
378 begin
379  if StrToBoolEx(Info.Values['hard_commit']) <> FHardCommit then
380  begin
381  if FTrHandle <> 0 then CloseTransaction;
382  FHardCommit := StrToBoolEx(Info.Values['hard_commit']);
383  end;
384 end;
385 
386 {**
387  Creates a <code>Statement</code> object for sending
388  SQL statements to the database.
389  SQL statements without parameters are normally
390  executed using Statement objects. If the same SQL statement
391  is executed many times, it is more efficient to use a
392  <code>PreparedStatement</code> object.
393  <P>
394  Result sets created using the returned <code>Statement</code>
395  object will by default have forward-only type and read-only concurrency.
396 
397  @param Info a statement parameters.
398  @return a new Statement object
399 }
400 function TZInterbase6Connection.CreateRegularStatement(Info: TStrings):
401  IZStatement;
402 begin
403  if IsClosed then
404  Open;
405  Result := TZInterbase6Statement.Create(Self, Info);
406 end;
407 
408 {**
409  Gets the host's full version number. Initially this should be 0.
410  The format of the version returned must be XYYYZZZ where
411  X = Major version
412  YYY = Minor version
413  ZZZ = Sub version
414  @return this server's full version number
415 }
416 function TZInterbase6Connection.GetHostVersion: Integer;
417 begin
418  Result := FHostVersion;
419 end;
420 
421 {**
422  Get database connection handle.
423  @return database handle
424 }
425 function TZInterbase6Connection.GetDBHandle: PISC_DB_HANDLE;
426 begin
427  Result := @FHandle;
428 end;
429 
430 {**
431  Return Interbase dialect number. Dialect a dialect Interbase SQL
432  must be 1 or 2 or 3.
433  @return dialect number
434 }
435 function TZInterbase6Connection.GetDialect: Word;
436 begin
437  Result := FDialect;
438 end;
439 
440 {**
441  Return native interbase plain driver
442  @return plain driver
443 }
444 function TZInterbase6Connection.GetPlainDriver: IZInterbasePlainDriver;
445 begin
446  Result := PlainDriver as IZInterbasePlainDriver;
447 end;
448 
449 {**
450  Get Interbase transaction handle
451  @return transaction handle
452 }
453 function TZInterbase6Connection.GetTrHandle: PISC_TR_HANDLE;
454 begin
455  if (FTrHandle = 0) and not Closed then
456  StartTransaction;
457  Result := @FTrHandle;
458 end;
459 
460 {**
461  Opens a connection to database server with specified parameters.
462 }
463 procedure TZInterbase6Connection.Open;
464 const sCS_NONE = 'NONE';
465 var
466  DPB: PAnsiChar;
467  FDPBLength: Word;
468  DBName: array[0..512] of AnsiChar;
469  TmpClientCodePageOld, TmpClientCodePageNew: PZCodePage;
470  tmp: String;
471  i: Integer;
472 begin
473  if not Closed then
474  Exit;
475 
476  if TransactIsolationLevel = tiReadUncommitted then
477  raise EZSQLException.Create('Isolation level do not capable');
478  if ConSettings^.ClientCodePage = nil then
479  CheckCharEncoding(FClientCodePage, True);
480 
481  DPB := GenerateDPB(Info, FDPBLength, FDialect);
482 
483  if HostName <> '' then
484  begin
485  if Port <> 3050 then
486  {$IFDEF WITH_STRPCOPY_DEPRECATED}AnsiStrings.{$ENDIF}StrPCopy(DBName, ZPlainString(HostName + '/' + IntToStr(Port) + ':' + Database))
487  else
488  {$IFDEF WITH_STRPCOPY_DEPRECATED}AnsiStrings.{$ENDIF}StrPCopy(DBName, ZPlainString(HostName + ':' + Database))
489  end
490  else
491  {$IFDEF WITH_STRPCOPY_DEPRECATED}AnsiStrings.{$ENDIF}StrPCopy(DBName, ZPlainString(Database));
492 
493  try
494  { Create new db if needed }
495  if Info.Values['createNewDatabase'] <> '' then
496  begin
497  CreateNewDatabase(Info.Values['createNewDatabase']);
498  { Logging connection action }
499  DriverManager.LogMessage(lcConnect, PlainDriver.GetProtocol,
500  Format('CREATE DATABASE "%s" AS USER "%s"', [Info.Values['createNewDatabase'], User]));
501  URL.Properties.Values['createNewDatabase'] := '';
502  end;
503 
504  FHandle := 0;
505  { Connect to Interbase6 database. }
506  GetPlainDriver.isc_attach_database(@FStatusVector,
507  {$IFDEF WITH_STRLEN_DEPRECATED}AnsiStrings.{$ENDIF}StrLen(DBName), DBName,
508  @FHandle, FDPBLength, DPB);
509 
510  { Check connection error }
511  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcConnect);
512 
513  (GetMetadata.GetDatabaseInfo as IZInterbaseDatabaseInfo).CollectServerInformations; //keep this one first!
514  tmp := GetMetadata.GetDatabaseInfo.GetDatabaseProductVersion;
515  I := Pos('.', tmp);
516  FHostVersion := StrToInt(Copy(tmp, 1, i-1))*1000000;
517  if Pos(' ', tmp) > 0 then //possible beta or alfa release
518  tmp := Copy(tmp, i+1, Pos(' ', tmp)-i-1)
519  else
520  tmp := Copy(tmp, i+1, Length(tmp)-i);
521  FHostVersion := FHostVersion + StrToInt(tmp)*100000;
522  { Logging connection action }
523  DriverManager.LogMessage(lcConnect, PlainDriver.GetProtocol,
524  Format('CONNECT TO "%s" AS USER "%s"', [Database, User]));
525 
526  { Start transaction }
527  if not FHardCommit then
528  StartTransaction;
529 
530  inherited Open;
531 
532  {Check for ClientCodePage: if empty switch to database-defaults
533  and/or check for charset 'NONE' which has a different byte-width
534  and no conversations where done except the collumns using collations}
535  with GetMetadata.GetCollationAndCharSet('', '', '', '') do
536  begin
537  if Next then
538  if FCLientCodePage = '' then
539  begin
540  FCLientCodePage := GetString(6);
541  CheckCharEncoding(FClientCodePage);
542  end
543  else
544  if GetString(6) = sCS_NONE then
545  if not ( FClientCodePage = sCS_NONE ) then
546  begin
547  URL.Properties.Values['isc_dpb_lc_ctype'] := sCS_NONE;
548  FClientCodePage := sCS_NONE;
549  {save the user wanted CodePage-Informations}
550  TmpClientCodePageOld := ConSettings.ClientCodePage;
551  { charset 'NONE' can't converty anything and write 'Data as is'!
552  If another charset was set on attaching the Server then all
553  column collations are retrieved with newly choosen collation.
554  BUT NO string convertations where done! So we need a
555  reopen (since we can set the Client-CharacterSet only on
556  connecting) to determine charset 'NONE' corectly. Then the column
557  collations have there proper CharsetID's to encode all strings
558  correctly. }
559  Self.Close;
560  Self.Open;
561  { Create a new PZCodePage for the new environment-variables }
562  TmpClientCodePageNew := New(PZCodePage);
563  TmpClientCodePageNew.Name := sCS_NONE;
564  TmpClientCodePageNew.ID := CS_NONE;
565  TmpClientCodePageNew.CharWidth := 1;
566  TmpClientCodePageNew.Encoding := TmpClientCodePageOld.Encoding;
567  TmpClientCodePageNew.CP := TmpClientCodePageOld.CP;
568  TmpClientCodePageNew.ZAlias := '';
569  TmpClientCodePageNew.IsStringFieldCPConsistent := False;
570  ConSettings.ClientCodePage := TmpClientCodePageNew;
571  SetConvertFunctions(ConSettings); //now let's the converters again
572  FDisposeClientCodePage := True;
573  {Also reset the MetaData ConSettings}
574  (FMetadata as TZInterbase6DatabaseMetadata).ConSettings := ConSettings;
575  { now we're able to read and write strings for columns without a
576  spezial declared collation for charset 'NONE' with the user
577  choosen CodePage and Encoding }
578  end;
579  Close;
580  end;
581  if FClientCodePage = sCS_NONE then
582  ConSettings.AutoEncode := True; //Must be set!
583  finally
584  {$IFDEF WITH_STRDISPOSE_DEPRECATED}AnsiStrings.{$ENDIF}StrDispose(DPB);
585  end;
586 end;
587 
588 {**
589  Creates a <code>PreparedStatement</code> object for sending
590  parameterized SQL statements to the database.
591 
592  A SQL statement with or without IN parameters can be
593  pre-compiled and stored in a PreparedStatement object. This
594  object can then be used to efficiently execute this statement
595  multiple times.
596 
597  <P><B>Note:</B> This method is optimized for handling
598  parametric SQL statements that benefit from precompilation. If
599  the driver supports precompilation,
600  the method <code>prepareStatement</code> will send
601  the statement to the database for precompilation. Some drivers
602  may not support precompilation. In this case, the statement may
603  not be sent to the database until the <code>PreparedStatement</code> is
604  executed. This has no direct effect on users; however, it does
605  affect which method throws certain SQLExceptions.
606 
607  Result sets created using the returned PreparedStatement will have
608  forward-only type and read-only concurrency, by default.
609 
610  @param sql a SQL statement that may contain one or more '?' IN
611  parameter placeholders
612  @return a new PreparedStatement object containing the
613  pre-compiled statement
614 }
615 function TZInterbase6Connection.CreatePreparedStatement(
616  const SQL: string; Info: TStrings): IZPreparedStatement;
617 begin
618  if IsClosed then
619  Open;
620  Result := TZInterbase6PreparedStatement.Create(Self, SQL, Info);
621 end;
622 
623 {**
624  Creates a <code>CallableStatement</code> object for calling
625  database stored procedures.
626  The <code>CallableStatement</code> object provides
627  methods for setting up its IN and OUT parameters, and
628  methods for executing the call to a stored procedure.
629 
630  <P><B>Note:</B> This method is optimized for handling stored
631  procedure call statements. Some drivers may send the call
632  statement to the database when the method <code>prepareCall</code>
633  is done; others
634  may wait until the <code>CallableStatement</code> object
635  is executed. This has no
636  direct effect on users; however, it does affect which method
637  throws certain SQLExceptions.
638 
639  Result sets created using the returned CallableStatement will have
640  forward-only type and read-only concurrency, by default.
641 
642  @param sql a SQL statement that may contain one or more '?'
643  parameter placeholders. Typically this statement is a JDBC
644  function call escape string.
645  @param Info a statement parameters.
646  @return a new CallableStatement object containing the
647  pre-compiled SQL statement
648 }
649 function TZInterbase6Connection.CreateCallableStatement(const SQL: string;
650  Info: TStrings): IZCallableStatement;
651 begin
652  if IsClosed then
653  Open;
654  Result := TZInterbase6CallableStatement.Create(Self, SQL, Info);
655 end;
656 
657 {**
658  Drops all changes made since the previous
659  commit/rollback and releases any database locks currently held
660  by this Connection. This method should be used only when auto-
661  commit has been disabled.
662  @see #setAutoCommit
663 }
664 procedure TZInterbase6Connection.Rollback;
665 begin
666  if FTrHandle <> 0 then
667  begin
668  if FHardCommit then
669  begin
670  GetPlainDriver.isc_rollback_transaction(@FStatusVector, @FTrHandle);
671  FTrHandle := 0;
672  end
673  else
674  GetPlainDriver.isc_rollback_retaining(@FStatusVector, @FTrHandle);
675  CheckInterbase6Error(GetPlainDriver, FStatusVector);
676  DriverManager.LogMessage(lcTransaction, PlainDriver.GetProtocol, 'TRANSACTION ROLLBACK');
677  end;
678 end;
679 
680 {**
681  Checks if a connection is still alive by doing a call to isc_database_info
682  It does not matter what info we request, we are not looking at it, as long
683  as it is something which should _always_ work if the connection is there.
684  We check if the error returned is one of the net_* errors described in the
685  firebird client documentation (335544721 .. 335544727).
686  Returns 0 if the connection is OK
687  Returns non zero if the connection is not OK
688 }
689 function TZInterbase6Connection.PingServer: integer;
690 var
691  DatabaseInfoCommand: Char;
692  Buffer: array[0..IBBigLocalBufferLength - 1] of AnsiChar;
693  ErrorCode: ISC_STATUS;
694 begin
695  DatabaseInfoCommand := Char(isc_info_reads);
696 
697  ErrorCode := GetPlainDriver.isc_database_info(@FStatusVector, @FHandle, 1, @DatabaseInfoCommand,
698  IBLocalBufferLength, Buffer);
699 
700  if (ErrorCode >= 335544721) and (ErrorCode <= 335544727) then
701  result := -1
702  else
703  result := 0;
704 end;
705 
706 {**
707  Start Interbase transaction
708 }
709 procedure TZInterbase6Connection.StartTransaction;
710 const tpb_Access: array[boolean] of String = ('isc_tpb_write','isc_tpb_read');
711 
712 {EH: We do NOT handle the isc_tpb_autocommit of FB because we noticed a huge
713  performance drop especially for Batch executions. Note Zeos handles one Batch
714  Execution as one Update and loops until all batch array are send. FB with this
715  param commits after each "execute block" which definitally kills the idea and
716  the expected performance!}
717 //const tpb_AutoCommit: array[boolean] of String = ('','isc_tpb_autocommit');
718 var
719  Params: TStrings;
720  PTEB: PISC_TEB;
721 begin
722  if FHandle <> 0 then
723  begin
724  if FTrHandle <> 0 then
725  begin {CLOSE Last Transaction first!}
726  GetPlainDriver.isc_commit_transaction(@FStatusVector, @FTrHandle);
727  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcTransaction);
728  FTrHandle := 0;
729  end;
730  PTEB := nil;
731  Params := TStringList.Create;
732 
733  { Set transaction parameters by TransactIsolationLevel }
734  Params.Add('isc_tpb_version3');
735  case TransactIsolationLevel of
736  tiReadCommitted:
737  begin
738  Params.Add(tpb_Access[ReadOnly]);
739  Params.Add('isc_tpb_read_committed');
740  Params.Add('isc_tpb_rec_version');
741  Params.Add('isc_tpb_nowait');
742  end;
743  tiRepeatableRead:
744  begin
745  Params.Add(tpb_Access[ReadOnly]);
746  Params.Add('isc_tpb_concurrency');
747  Params.Add('isc_tpb_nowait');
748  end;
749  tiSerializable:
750  begin
751  Params.Add(tpb_Access[ReadOnly]);
752  Params.Add('isc_tpb_consistency');
753  end;
754  else
755  { Add user defined parameters for transaction }
756  if Pos('isc_tpb_', Info.Text) > 0 then
757  begin
758  Params.Clear;
759  Params.AddStrings(Info);
760  end
761  else
762  begin
763  {extend the firebird defaults by ReadOnly}
764  Params.Add(tpb_Access[ReadOnly]);
765  Params.Add('isc_tpb_concurrency');
766  Params.Add('isc_tpb_wait');
767  end;
768  end;
769 
770  try
771  { GenerateTPB return PTEB with null pointer tpb_address from default
772  transaction }
773  PTEB := GenerateTPB(Params, FHandle);
774  GetPlainDriver.isc_start_multiple(@FStatusVector, @FTrHandle, 1, PTEB);
775  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcTransaction);
776  DriverManager.LogMessage(lcTransaction, GetPlainDriver.GetProtocol,
777  'TRANSACTION STARTED.');
778  finally
779  FreeAndNil(Params);
780  {$IFDEF WITH_STRDISPOSE_DEPRECATED}AnsiStrings.{$ENDIF}StrDispose(PTEB.tpb_address);
781  FreeMem(PTEB);
782  end
783  end;
784 end;
785 
786 procedure TZInterbase6Connection.SetTransactionIsolation(Level: TZTransactIsolationLevel);
787 begin
788  if (Level <> TransactIsolationLevel) and (FHandle <> 0) then
789  CloseTransaction;
790  Inherited SetTransactionIsolation(Level);
791 end;
792 
793 {**
794  Creates new database
795  @param SQL a sql strinf for creation database
796 }
797 procedure TZInterbase6Connection.CreateNewDatabase(const SQL: String);
798 var
799  TrHandle: TISC_TR_HANDLE;
800 begin
801  TrHandle := 0;
802  GetPlainDriver.isc_dsql_execute_immediate(@FStatusVector, @FHandle, @TrHandle,
803  0, PAnsiChar({$IFDEF UNICODE}AnsiString{$ENDIF}(sql)), FDialect, nil);
804  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcExecute, SQL);
805  //disconnect from the newly created database because the connection character set is NONE,
806  //which usually nobody wants
807  GetPlainDriver.isc_detach_database(@FStatusVector, @FHandle);
808  CheckInterbase6Error(GetPlainDriver, FStatusVector, lcExecute, SQL);
809 end;
810 
811 function TZInterbase6Connection.GetBinaryEscapeString(const Value: RawByteString): String;
812 begin
813  //http://tracker.firebirdsql.org/browse/CORE-2789
814  if EndsWith(GetPlainDriver.GetProtocol, '2.5') then
815  if (Length(Value)*2+3) < 32*1024 then
816  Result := GetSQLHexString(PAnsiChar(Value), Length(Value))
817  else
818  raise Exception.Create('Binary data out of range! Use parameters!')
819  else
820  raise Exception.Create('Your Firebird-Version does''t support Binary-Data in SQL-Statements! Use parameters!');
821 end;
822 
823 function TZInterbase6Connection.GetBinaryEscapeString(const Value: TByteDynArray): String;
824 begin
825  //http://tracker.firebirdsql.org/browse/CORE-2789
826  if EndsWith(GetPlainDriver.GetProtocol, '2.5') then
827  if (Length(Value)*2+3) < 32*1024 then
828  Result := GetSQLHexString(PAnsiChar(Value), Length(Value))
829  else
830  raise Exception.Create('Binary data out of range! Use parameters!')
831  else
832  raise Exception.Create('Your Firebird-Version does''t support Binary-Data in SQL-Statements! Use parameters!');
833 end;
834 
835 function TZInterbase6Connection.GetEscapeString(const Value: RawByteString): RawByteString;
836 begin
837  //http://www.firebirdsql.org/manual/qsg10-firebird-sql.html
838  if GetAutoEncodeStrings then
839  if StartsWith(Value, RawByteString('''')) and EndsWith(Value, RawByteString('''')) then
840  {$IFDEF UNICODE}
841  Result := Value
842  {$ELSE}
843  Result := GetDriver.GetTokenizer.GetEscapeString(Value)
844  {$ENDIF}
845  else
846  {$IFDEF UNICODE}
847  Result := #39+{$IFDEF WITH_UNITANSISTRINGS}AnsiStrings.{$ENDIF}StringReplace(Value, #39, #39#39, [rfReplaceAll])+#39
848  {$ELSE}
849  Result := GetDriver.GetTokenizer.GetEscapeString(#39+StringReplace(Value, #39, #39#39, [rfReplaceAll])+#39)
850  {$ENDIF}
851  else
852  if StartsWith(Value, RawByteString('''')) and EndsWith(Value, RawByteString('''')) then
853  Result := Value
854  else
855  Result := #39+{$IFDEF WITH_UNITANSISTRINGS}AnsiStrings.{$ENDIF}StringReplace(Value, #39, #39#39, [rfReplaceAll])+#39;
856 end;
857 
858 function TZInterbase6Connection.GetEscapeString(const Value: ZWideString): ZWideString;
859 begin
860  //http://www.firebirdsql.org/manual/qsg10-firebird-sql.html
861  if GetAutoEncodeStrings then
862  if StartsWith(Value, ZWideString('''')) and EndsWith(Value, ZWideString('''')) then
863  {$IFDEF UNICODE}
864  Result := GetDriver.GetTokenizer.GetEscapeString(Value)
865  {$ELSE}
866  Result := Value
867  {$ENDIF}
868  else
869  {$IFDEF UNICODE}
870  Result := GetDriver.GetTokenizer.GetEscapeString(#39+StringReplace(Value, #39, #39#39, [rfReplaceAll])+#39)
871  {$ELSE}
872  Result := ZDbcUnicodeString(GetDriver.GetTokenizer.GetEscapeString(#39+StringReplace(ZPlainString(Value), #39, #39#39, [rfReplaceAll])+#39))
873  {$ENDIF}
874  else
875  if StartsWith(Value, ZWideString('''')) and EndsWith(Value, ZWideString('''')) then
876  Result := Value
877  else
878  {$IFDEF UNICODE}
879  Result := #39+StringReplace(Value, #39, #39#39, [rfReplaceAll])+#39;
880  {$ELSE}
881  Result := ZDbcUnicodeString(#39+StringReplace(ZPlainString(Value), #39, #39#39, [rfReplaceAll])+#39);
882  {$ENDIF}
883 end;
884 {**
885  Creates a sequence generator object.
886  @param Sequence a name of the sequence generator.
887  @param BlockSize a number of unique keys requested in one trip to SQL server.
888  @returns a created sequence object.
889 }
890 function TZInterbase6Connection.CreateSequence(const Sequence: string;
891  BlockSize: Integer): IZSequence;
892 begin
893  Result := TZInterbase6Sequence.Create(Self, Sequence, BlockSize);
894 end;
895 
896 procedure TZInterbase6Connection.SetReadOnly(Value: Boolean);
897 begin
898  if (ReadOnly <> Value) and (FTrHandle <> 0) then
899  CloseTransaction;
900  ReadOnly := Value;
901 end;
902 
903 { TZInterbase6CachedResolver }
904 
905 {**
906  Forms a where clause for SELECT statements to calculate default values.
907  @param Columns a collection of key columns.
908  @param OldRowAccessor an accessor object to old column values.
909 }
910 function TZInterbase6CachedResolver.FormCalculateStatement(
911  Columns: TObjectList): string;
912 // --> ms, 30/10/2005
913 var
914  iPos: Integer;
915 begin
916  Result := inherited FormCalculateStatement(Columns);
917  if Result <> '' then
918  begin
919  iPos := pos('FROM', uppercase(Result));
920  if iPos > 0 then
921  begin
922  Result := copy(Result, 1, iPos+3) + ' RDB$DATABASE';
923  end
924  else
925  begin
926  Result := Result + ' FROM RDB$DATABASE';
927  end;
928  end;
929 // <-- ms
930 end;
931 
932 { TZInterbase6Sequence }
933 
934 {**
935  Gets the current unique key generated by this sequence.
936  @param the next generated unique key.
937 }
938 function TZInterbase6Sequence.GetCurrentValue: Int64;
939 var
940  Statement: IZStatement;
941  ResultSet: IZResultSet;
942 begin
943  Statement := Connection.CreateStatement;
944  ResultSet := Statement.ExecuteQuery(Format(
945  'SELECT GEN_ID("%s", 0) FROM rdb$generators ' +
946  'WHERE rdb$generators.rdb$generator_name = ''%s''', [Name, Name]));
947  if ResultSet.Next then
948  Result := ResultSet.GetLong(1)
949  else
950  Result := inherited GetCurrentValue;
951  ResultSet.Close;
952  Statement.Close;
953 end;
954 
955 {**
956  Gets the next unique key generated by this sequence.
957  @param the next generated unique key.
958 }
959 function TZInterbase6Sequence.GetCurrentValueSQL: string;
960 begin
961  Result := Format(' GEN_ID("%s", 0) ', [Name]);
962 end;
963 
964 function TZInterbase6Sequence.GetNextValue: Int64;
965 var
966  Statement: IZStatement;
967  ResultSet: IZResultSet;
968 begin
969  Statement := Connection.CreateStatement;
970  ResultSet := Statement.ExecuteQuery(Format(
971  'SELECT GEN_ID("%s", %d) FROM rdb$generators ' +
972  'WHERE rdb$generators.rdb$generator_name = ''%s''', [Name, BlockSize, Name]));
973  if ResultSet.Next then
974  Result := ResultSet.GetLong(1)
975  else
976  Result := inherited GetNextValue;
977  ResultSet.Close;
978  Statement.Close;
979 end;
980 
981 function TZInterbase6Sequence.GetNextValueSQL: string;
982 begin
983  Result := Format(' GEN_ID("%s", %d) ', [Name, BlockSize]);
984 end;
985 
986 initialization
987  Interbase6Driver := TZInterbase6Driver.Create;
988  DriverManager.RegisterDriver(Interbase6Driver);
989 
990 finalization
991  if Assigned(DriverManager) then
992  DriverManager.DeregisterDriver(Interbase6Driver);
993  Interbase6Driver := nil;
994 end.