mORMot and Open Source friends
Check-in [2950b39ac5]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:removed CreateWithCodePage() constructor: now the charset will be retrieved at connection, and used for CHAR/NVARCHAR2 fields - see ticket [a6a639ec43]
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 2950b39ac501931bff1459ac3173496faa380387
User & Date: abouchez 2013-12-09 15:33:43
Context
2013-12-09
18:14
will now set the current Windows code page if was not able to retrieve Oracle database charset check-in: ed46eecdad user: abouchez tags: trunk
15:33
removed CreateWithCodePage() constructor: now the charset will be retrieved at connection, and used for CHAR/NVARCHAR2 fields - see ticket [a6a639ec43] check-in: 2950b39ac5 user: abouchez tags: trunk
14:31
  • added DynArraySaveJSON() function to be used e.g. for custom record JSON serialization, using TDynArrayJSONCustomReader/Writer callbacks and/or TTextWriter.RegisterCustomJSONSerializerFromText()
  • added TTextWriter.AddCRAndIdent method
  • TTextWriter.RegisterCustomJSONSerializerFromText() now handles BOOLEAN kind of fields, and soWriteHumanReadable option when serializing into JSON context
  • enhanced testing by retrieving and processing some JSON real-life content from the official github RESTFull service
check-in: 9ebf6d9648 user: abouchez tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SynCommons.pas.

847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
...
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
...
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
....
8860
8861
8862
8863
8864
8865
8866
8867
8868
8869
8870
8871
8872
8873
8874
8875
8876
8877
8878
8879
8880
8881
8882
8883
8884
....
9091
9092
9093
9094
9095
9096
9097
9098
9099
9100
9101
9102
9103
9104
9105
  // - implementations of this class will handle efficiently all Code Pages
  // - this default implementation will use the Operating System APIs
  // - you should not create your own class instance by yourself, but should
  // better retrieve an instance using TSynAnsiConvert.Engine(), which will
  // initialize either a TSynAnsiFixedWidth or a TSynAnsiConvert instance on need
  TSynAnsiConvert = class
  protected
    fCodePage: Integer;
  public
    /// initialize the internal conversion engine
    constructor Create(aCodePage: integer); reintroduce; virtual;
    /// returns the engine corresponding to a given code page
    // - a global list of TSynAnsiConvert instances is handled by the unit -
    // therefore, caller should not release the returned instance
    // - will return nil in case of unhandled code page
    class function Engine(aCodePage: integer): TSynAnsiConvert;
    /// direct conversion of a PAnsiChar buffer into an Unicode buffer
    // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
    // - this default implementation will use the Operating System APIs
    function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; overload; virtual;
    /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded buffer
    // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
    // - a #0 char is appended at the end (and result will point to it)
................................................................................
      {$ifdef HASINLINE}inline;{$endif}
    /// convert any Ansi Text (providing a From converted) into Ansi Text
    function AnsiToAnsi(From: TSynAnsiConvert; const Source: RawByteString): RawByteString; overload; 
    /// convert any Ansi buffer (providing a From converted) into Ansi Text
    function AnsiToAnsi(From: TSynAnsiConvert; Source: PAnsiChar; SourceChars: cardinal): RawByteString; overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// corresponding code page
    property CodePage: Integer read fCodePage;
  end;

  /// a class to handle Ansi to/from Unicode translation of fixed width encoding
  // (i.e. non MBCS)
  // - this class will handle efficiently all Code Page availables without MBCS
  // encoding - like WinAnsi (1252) or Russian (1251)
  // - it will use internal fast look-up tables for such encodings
................................................................................
  // advantage of the internal lookup tables to provide some fast process
  TSynAnsiFixedWidth = class(TSynAnsiConvert)
  protected
    fAnsiToWide: TWordDynArray;
    fWideToAnsi: TByteDynArray;
  public
    /// initialize the internal conversion engine
    constructor Create(aCodePage: integer); override;
    /// direct conversion of a PAnsiChar buffer into an Unicode buffer
    // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
    function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; override;
    /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded buffer
    // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
    // - a #0 char is appended at the end (and result will point to it)
    function AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char; override;
................................................................................
    SetString(result,tmpU8,AnsiBufferToUTF8(tmpU8,Source,SourceChars)-tmpU8) else begin
    Getmem(U8,SourceChars*3);
    SetString(result,U8,AnsiBufferToUTF8(U8,Source,SourceChars)-U8);
    FreeMem(U8);
  end;
end;

constructor TSynAnsiConvert.Create(aCodePage: integer);
begin
  fCodePage := aCodePage;
end;

function IsFixedWidthCodePage(aCodePage: integer): boolean;
begin
  result := (aCodePage>=1250) and (aCodePage<=1258);
end;

class function TSynAnsiConvert.Engine(aCodePage: integer): TSynAnsiConvert;
var i: integer;
begin
  if SynAnsiConvertList=nil then
    GarbageCollectorFreeAndNil(SynAnsiConvertList,TObjectList.Create) else
    for i := 0 to SynAnsiConvertList.Count-1 do begin
      result := SynAnsiConvertList.List[i];
      if result.CodePage=aCodePage then
................................................................................
  if SourceChars=0 then
    result := '' else begin
    SetString(result,nil,SourceChars*2+1);
    AnsiBufferToUnicode(pointer(result),Source,SourceChars)^ := #0;
  end;
end;

constructor TSynAnsiFixedWidth.Create(aCodePage: integer);
var i: integer;
    A256: array[0..256] of AnsiChar;
    U256: array[0..256] of WideChar; // AnsiBufferToUnicode() write a last #0
    {$ifopt C+}
    PW: PWideChar;
    {$endif}
begin






|


|




|







 







|







 







|







 







|




|




|







 







|







847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
...
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
...
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
....
8860
8861
8862
8863
8864
8865
8866
8867
8868
8869
8870
8871
8872
8873
8874
8875
8876
8877
8878
8879
8880
8881
8882
8883
8884
....
9091
9092
9093
9094
9095
9096
9097
9098
9099
9100
9101
9102
9103
9104
9105
  // - implementations of this class will handle efficiently all Code Pages
  // - this default implementation will use the Operating System APIs
  // - you should not create your own class instance by yourself, but should
  // better retrieve an instance using TSynAnsiConvert.Engine(), which will
  // initialize either a TSynAnsiFixedWidth or a TSynAnsiConvert instance on need
  TSynAnsiConvert = class
  protected
    fCodePage: cardinal;
  public
    /// initialize the internal conversion engine
    constructor Create(aCodePage: cardinal); reintroduce; virtual;
    /// returns the engine corresponding to a given code page
    // - a global list of TSynAnsiConvert instances is handled by the unit -
    // therefore, caller should not release the returned instance
    // - will return nil in case of unhandled code page
    class function Engine(aCodePage: cardinal): TSynAnsiConvert;
    /// direct conversion of a PAnsiChar buffer into an Unicode buffer
    // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
    // - this default implementation will use the Operating System APIs
    function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; overload; virtual;
    /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded buffer
    // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
    // - a #0 char is appended at the end (and result will point to it)
................................................................................
      {$ifdef HASINLINE}inline;{$endif}
    /// convert any Ansi Text (providing a From converted) into Ansi Text
    function AnsiToAnsi(From: TSynAnsiConvert; const Source: RawByteString): RawByteString; overload; 
    /// convert any Ansi buffer (providing a From converted) into Ansi Text
    function AnsiToAnsi(From: TSynAnsiConvert; Source: PAnsiChar; SourceChars: cardinal): RawByteString; overload;
      {$ifdef HASINLINE}inline;{$endif}
    /// corresponding code page
    property CodePage: Cardinal read fCodePage;
  end;

  /// a class to handle Ansi to/from Unicode translation of fixed width encoding
  // (i.e. non MBCS)
  // - this class will handle efficiently all Code Page availables without MBCS
  // encoding - like WinAnsi (1252) or Russian (1251)
  // - it will use internal fast look-up tables for such encodings
................................................................................
  // advantage of the internal lookup tables to provide some fast process
  TSynAnsiFixedWidth = class(TSynAnsiConvert)
  protected
    fAnsiToWide: TWordDynArray;
    fWideToAnsi: TByteDynArray;
  public
    /// initialize the internal conversion engine
    constructor Create(aCodePage: cardinal); override;
    /// direct conversion of a PAnsiChar buffer into an Unicode buffer
    // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
    function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; override;
    /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded buffer
    // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
    // - a #0 char is appended at the end (and result will point to it)
    function AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char; override;
................................................................................
    SetString(result,tmpU8,AnsiBufferToUTF8(tmpU8,Source,SourceChars)-tmpU8) else begin
    Getmem(U8,SourceChars*3);
    SetString(result,U8,AnsiBufferToUTF8(U8,Source,SourceChars)-U8);
    FreeMem(U8);
  end;
end;

constructor TSynAnsiConvert.Create(aCodePage: cardinal);
begin
  fCodePage := aCodePage;
end;

function IsFixedWidthCodePage(aCodePage: cardinal): boolean;
begin
  result := (aCodePage>=1250) and (aCodePage<=1258);
end;

class function TSynAnsiConvert.Engine(aCodePage: cardinal): TSynAnsiConvert;
var i: integer;
begin
  if SynAnsiConvertList=nil then
    GarbageCollectorFreeAndNil(SynAnsiConvertList,TObjectList.Create) else
    for i := 0 to SynAnsiConvertList.Count-1 do begin
      result := SynAnsiConvertList.List[i];
      if result.CodePage=aCodePage then
................................................................................
  if SourceChars=0 then
    result := '' else begin
    SetString(result,nil,SourceChars*2+1);
    AnsiBufferToUnicode(pointer(result),Source,SourceChars)^ := #0;
  end;
end;

constructor TSynAnsiFixedWidth.Create(aCodePage: cardinal);
var i: integer;
    A256: array[0..256] of AnsiChar;
    U256: array[0..256] of WideChar; // AnsiBufferToUnicode() write a last #0
    {$ifopt C+}
    PW: PWideChar;
    {$endif}
begin

Changes to SynDBOracle.pas.

25
26
27
28
29
30
31


32
33
34
35
36
37
38
..
99
100
101
102
103
104
105


106
107
108
109
110
111
112
...
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200

201
202
203
204
205
206
207
208
209




210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
...
254
255
256
257
258
259
260

261
262
263
264
265
266
267
....
1225
1226
1227
1228
1229
1230
1231

1232
1233
1234
1235
1236
1237
1238
1239
1240
....
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389

1390
1391
1392
1393
1394
1395
1396
1397
1398
....
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438












1439
1440
1441
1442

1443
1444








1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460


1461
1462
1463
1464
1465
1466
1467
....
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543










1544
1545
1546
1547
1548
1549
1550
....
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
....
1615
1616
1617
1618
1619
1620
1621














1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
....
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2013
  the Initial Developer. All Rights Reserved.

  Contributor(s):


  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
................................................................................
    retrieved value
  - added SQLT_INTERVAL_YM/SQLT_INTERVAL_DS column support (as JSON text)
  - added SynDBOracleOCIpath global variable to force oci.dll folder
  - will search for OracleInstantClient\oci.dll in executable sub-folder (can be
    used e.g. for Win64)
  - enhance logging at connection to specify the client version, database name
    and charset/codepage


  - added some WIN1252 charset aliases for DB (ISO-8859-1/15)
  - force ftCurrency to use '.' as decimal separator in returned SQLT_STR buffer
    (as expected by JSON) - previous fix in 1.17 revision did not work
  - added TSQLDBOracleConnectionProperties.ExtractTnsName() class method
  - when a column charset does not match the connection value, log a warning and
    force connection-level code page instead of raising an Exception
  - fixed ticket [4c68975022] about broken SQL statement when logging active
................................................................................
  {$A+}
  /// wrapper to an array of TOracleDate items
  TOracleDateArray = array[0..(maxInt div sizeof(TOracleDate))-1] of TOracleDate;

  /// will implement properties shared by native Oracle Client Interface connections
  TSQLDBOracleConnectionProperties = class(TSQLDBConnectionPropertiesThreadSafe)
  protected
    fCodePage: cardinal;
    fAnsiConvert: TSynAnsiConvert;
    fRowsPrefetchSize: Integer;
    fBlobPrefetchSize: Integer;
    fStatementCacheSize: integer;
    fInternalBufferSize: integer;
    fEnvironmentInitializationMode: integer;
    function GetClientVersion: RawUTF8;
    /// initialize fForeignKeys content with all foreign keys of this DB
    // - used by GetForeignKey method
    procedure GetForeignKeys; override;
  public
    /// initialize the connection properties
    // - aDatabaseName is not used for Oracle: only aServerName is to be set
    // - this overriden method will force the code page to be zero: you shall
    // better call the CreateWithCodePage constructor instead of this generic method
    constructor Create(const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8); override;
    /// initialize the OCI connection properties
    // - we don't need a database name parameter for Oracle connection

    // - you may specify the TNSName in aServerName, or a connection string
    // like '//host[:port]/[service_name]', e.g. '//sales-server:1523/sales'
    // - since the OCI client will make conversion when returning column data,
    // to avoid any truncate when retrieving VARCHAR2 or CHAR fields into the
    // internal fixed-sized buffer, you may specify here the exact database
    // code page, as existing on the server (e.g. CODEPAGE_US=1252 for default
    // WinAnsi western encoding) - if aCodePage is set to 0, either the global
    // NLS_LANG environnement variable is used, either the thread setting (GetACP)
    constructor CreateWithCodePage(const aServerName, aUserID, aPassWord: RawUTF8; aCodePage: integer); virtual;




    /// create a new connection
    // - call this method if the shared MainConnection is not enough (e.g. for
    // multi-thread access)
    // - the caller is responsible of freeing this instance
    // - this overriden method will create an TSQLDBOracleConnection instance
    function NewConnection: TSQLDBConnection; override;
    /// extract the TNS listener name from a Oracle full connection string
    // - e.g. ExtractTnsName('1.2.3.4:1521/dbname') returns 'dbname'
    class function ExtractTnsName(const aServerName: RawUTF8): RawUTF8;
  published
    /// returns the Client version e.g. '11.2.0.1 at oci.dll'
    property ClientVersion: RawUTF8 read GetClientVersion;
    /// the code page used for the connection
    // - e.g. 1252 for default CODEPAGE_US
    // - connection is opened globaly as UTF-8, to match the internal encoding
    // of our units; but CHAR / NVARCHAR2 fields will use this code page encoding
    // to avoid any column truncation when retrieved from the server
    property CodePage: cardinal read fCodePage;
    /// the OCI initialization mode used for the connection
    // - equals OCI_EVENTS or OCI_THREADED by default, since will likely be
    // used in a multi-threaded context (even if this class is inheriting from
    // TSQLDBConnectionPropertiesThreadSafe), and  OCI_EVENTS is needed to support
    // Oracle RAC Connection Load Balancing
    // - can be tuned depending on the configuration or the Oracle version
    property EnvironmentInitializationMode: integer
................................................................................
    fEnv: pointer;
    fError: pointer;
    fServer: pointer;
    fContext: pointer;
    fSession: pointer;
    fTrans: pointer;
    fOCICharSet: integer;

    function DateTimeToDescriptor(aDateTime: TDateTime): pointer;
    procedure STRToUTF8(P: PAnsiChar; var result: RawUTF8;
      ColumnDBCharSet,ColumnDBForm: integer);
    {$ifndef UNICODE}
    procedure STRToAnsiString(P: PAnsiChar; var result: AnsiString;
      ColumnDBCharSet,ColumnDBForm: integer);
    {$endif}
................................................................................
    /// load the oci.dll library
    // - and retrieve all Oci*() addresses for OCI_ENTRIES[] items
    constructor Create;
    /// retrieve the client version as '11.2.0.1 at oci.dll'
    function ClientRevision: RawUTF8;
    /// retrieve the OCI charset ID from a Windows Code Page
    // - will only handle most known Windows Code Page

    // - will use 'WE8MSWIN1252' (CODEPAGE_US) if the Code Page is unknown
    function CodePageToCharSet(env: pointer; aCodePage: integer): integer;
    /// raise an exception on error
    procedure Check(Status: Integer; ErrorHandle: POCIError;
      InfoRaiseException: Boolean=false; LogLevelNoRaise: TSynLogInfo=sllNone);
      {$ifdef HASINLINE} inline; {$endif}
    /// retrieve some BLOB content
    procedure BlobFromDescriptor(svchp: POCISvcCtx; errhp: POCIError;
      locp: POCIDescriptor; out result: RawByteString); overload;
................................................................................
    result := FormatUTF8(EXE_FMT,[major_version,minor_version,update_num,patch_num,fLibraryPath]);
  end;
end;

const
  // http://download.oracle.com/docs/cd/B19306_01/server.102/b14225/applocaledata.htm#i635016
  // http://www.mydul.net/charsets.html
  CODEPAGES: array[0..14] of record Num, Charset: integer; Text: PUTF8Char end = (
    (Num: 1252; Charset: OCI_WE8MSWIN1252; Text: 'WE8MSWIN1252'),
    (Num: 1250; Charset: 170; Text: 'EE8MSWIN1250'),
    (Num: 1251; Charset: 171; Text: 'CL8MSWIN1251'),
    (Num: 1253; Charset: 174; Text: 'EL8MSWIN1253'),
    (Num: 1254; Charset: 177; Text: 'TR8MSWIN1254'),
    (Num: 1255; Charset: 175; Text: 'IW8MSWIN1255'),
    (Num: 1256; Charset: 560; Text: 'AR8MSWIN1256'),
    (Num: 1257; Charset: 179; Text: 'BLT8MSWIN1257'),
    (Num:  874; Charset: 41;  Text: 'TH8TISASCII'),
    (Num:  932; Charset: 832; Text: 'JA16SJIS'),
    (Num:  949; Charset: 846; Text: 'KO16MSWIN949'),
    (Num:  936; Charset: 850; Text: 'ZHS16CGB231280'),

    (Num: 1258; Charset: 45;  Text: 'VN8MSWIN1258'),
     // handle some WIN1252 aliases
    (Num: 1252; Charset: 46;  Text: 'WE8ISO8859P15'),
    (Num: 1252; Charset: 31;  Text: 'WE8ISO8859P1'));

function EnvVariableToCodePage: integer;
var i: integer;
    nlslang: array[byte] of AnsiChar;
begin
................................................................................
    for i2 := 0 to High(CODEPAGES) do
      if (CODEPAGES[i2].Charset=aCharset2) and
         (CODEPAGES[i1].Num=CODEPAGES[i2].Num) then
        exit; // aliases are allowed
  result := false;
end;

function OracleCharSetName(aCharset: integer): PUTF8Char;
var i: integer;
begin
  for i := 0 to high(CODEPAGES) do
    with CODEPAGES[i] do
    if Charset=aCharset then begin
      result := Text;
      exit;
    end;
  result := '?';
end;













function TSQLDBOracleLib.CodePageToCharSet(env: pointer;
  aCodePage: integer): integer;
var ocp: PUTF8Char;
    i: integer;

begin
  case aCodePage of








  CP_UTF8:
    result := OCI_UTF8;
  CP_UTF16:
    result := OCI_UTF16ID;
  else begin
    ocp := CODEPAGES[0].Text; // default is MS Windows Code Page 1252
    for i := 0 to high(CODEPAGES) do
      if aCodePage=CODEPAGES[i].Num then begin
        ocp := CODEPAGES[i].Text;
        break;
      end;
    result := NlsCharSetNameToID(env,ocp);
    if result=0 then
      result := OCI_WE8MSWIN1252;
  end;
  end;


end;

constructor TSQLDBOracleLib.Create;
var P: PPointer;
    i: integer;
    orhome: array[byte] of Char;
begin
................................................................................

var
  OCI: TSQLDBOracleLib = nil;


{ TSQLDBOracleConnectionProperties }

constructor TSQLDBOracleConnectionProperties.CreateWithCodePage(const aServerName,
  aUserID, aPassWord: RawUTF8; aCodePage: integer);
begin
  fDBMS := dOracle;
  inherited Create(aServerName,'',aUserID,aPassWord);
  if OCI=nil then
    GarbageCollectorFreeAndNil(OCI,TSQLDBOracleLib.Create);
  if aCodePage=0 then
    aCodePage := EnvVariableToCodePage;
  fCodePage := aCodePage;
  fAnsiConvert := TSynAnsiConvert.Engine(aCodePage);
  fBlobPrefetchSize := 4096;
  fRowsPrefetchSize := 128*1024;
  fStatementCacheSize := 30; // default is 20
  fInternalBufferSize := 128*1024; // 128 KB
  fBatchSendingAbilities := [cCreate,cUpdate,cDelete]; // array DML feature
  fBatchMaxSentAtOnce := 10000;  // iters <= 32767 for better performance
  fEnvironmentInitializationMode := OCI_EVENTS or OCI_THREADED;
end;

class function TSQLDBOracleConnectionProperties.ExtractTnsName(
  const aServerName: RawUTF8): RawUTF8;
var i: integer;
begin
  i := PosEx('/',aServerName);
  if i=0 then
    result := aServerName else
    result := copy(aServerName,i+1,100);
end;

constructor TSQLDBOracleConnectionProperties.Create(const aServerName,
  aDatabaseName, aUserID, aPassWord: RawUTF8);
begin
  CreateWithCodePage(aServerName,aUserID,aPassWord,0);










end;

function TSQLDBOracleConnectionProperties.GetClientVersion: RawUTF8;
begin
  result := OCI.ClientRevision;
end;

................................................................................
  with OCI do
  try
    if fEnv=nil then
      // will use UTF-8 encoding by default, in a multi-threaded context
      // OCI_EVENTS is needed to support Oracle RAC Connection Load Balancing
      EnvNlsCreate(fEnv,Props.EnvironmentInitializationMode,
        nil,nil,nil,nil,0,nil,OCI_UTF8,OCI_UTF8);
    if fOCICharSet=0 then
      // retrieve the charset to be used for inlined CHAR / VARCHAR2 fields
      fOCICharSet := CodePageToCharSet(fEnv,Props.CodePage);
    HandleAlloc(fEnv,fError,OCI_HTYPE_ERROR);
    HandleAlloc(fEnv,fServer,OCI_HTYPE_SERVER);
    HandleAlloc(fEnv,fContext,OCI_HTYPE_SVCCTX);
    Check(ServerAttach(fServer,fError,pointer(Props.ServerName),
      length(Props.ServerName),0),fError);
    // we don't catch all errors here, since Client may ignore unhandled ATTR
    AttrSet(fContext,OCI_HTYPE_SVCCTX,fServer,0,OCI_ATTR_SERVER,fError);
................................................................................
    if Props.UseCache then begin
      AttrSet(fContext,OCI_HTYPE_SVCCTX,@Props.fStatementCacheSize,0,
        OCI_ATTR_STMTCACHESIZE,fError);
      mode := OCI_STMT_CACHE;
    end else
      mode := OCI_DEFAULT;
    Check(SessionBegin(fContext,fError,fSession,OCI_CRED_RDBMS,mode),fError);














    {$ifndef DELPHI5OROLDER}
    Log.Log(sllInfo,'Connected to % as % with %, codepage % (%/%)',
      [Props.ServerName,Props.UserID,Props.ClientVersion,Props.CodePage,
       fOCICharSet,OracleCharSetName(fOCICharSet)],self);
    {$endif}
    with NewStatement do
    try // ORM will send date/time as ISO8601 text -> force encoding
      Execute('ALTER SESSION SET NLS_DATE_FORMAT=''YYYY-MM-DD-HH24:MI:SS''',false);
    finally
      Free;
................................................................................
  ColumnDBCharSet,ColumnDBForm: integer);
var L: integer;
begin
  L := StrLen(PUTF8Char(P));
  if (L=0) or (ColumnDBCharSet=OCI_UTF8) or (ColumnDBForm=SQLCS_NCHAR) or
     (fOCICharSet=OCI_UTF8) then
    SetString(result,P,L) else
    result := TSQLDBOracleConnectionProperties(Properties).fAnsiConvert.
      AnsiBufferToRawUTF8(P,L);
end;

{$ifndef UNICODE}
procedure TSQLDBOracleConnection.STRToAnsiString(P: PAnsiChar; var result: AnsiString;
  ColumnDBCharSet,ColumnDBForm: integer);
var L: integer;
begin
  L := StrLen(PUTF8Char(P));
  with TSQLDBOracleConnectionProperties(Properties) do
  if (L=0) or ((ColumnDBCharSet<>OCI_UTF8) and (ColumnDBForm<>SQLCS_NCHAR) and
     (fCodePage=GetACP)) then
    SetString(result,P,L) else
    result := CurrentAnsiConvert.AnsiToAnsi(
      TSQLDBOracleConnectionProperties(Properties).fAnsiConvert,P,L);
end;
{$endif}



{ TSQLDBOracleStatement }







>
>







 







>
>







 







<
<











<
<
<
<
<
|
>


<
<
<
<
<
<
<
>
>
>
>












<
<
<
<
<
<







 







>







 







>

|







 







|











|
>
|
|







 







|




|






>
>
>
>
>
>
>
>
>
>
>
>
|



>


>
>
>
>
>
>
>
>












<
<


>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<













|
>
>
>
>
>
>
>
>
>
>







 







<
<
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>


|







 







<
|








<

|

|
<







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
...
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
179
180
181
182
183
184
185


186
187
188
189
190
191
192
193
194
195
196





197
198
199
200







201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216






217
218
219
220
221
222
223
...
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
....
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
....
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
....
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469


1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
....
1516
1517
1518
1519
1520
1521
1522




















1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
....
1591
1592
1593
1594
1595
1596
1597



1598
1599
1600
1601
1602
1603
1604
....
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
....
1778
1779
1780
1781
1782
1783
1784

1785
1786
1787
1788
1789
1790
1791
1792
1793

1794
1795
1796
1797

1798
1799
1800
1801
1802
1803
1804
  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2013
  the Initial Developer. All Rights Reserved.

  Contributor(s):
  - richard6688
  
  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
................................................................................
    retrieved value
  - added SQLT_INTERVAL_YM/SQLT_INTERVAL_DS column support (as JSON text)
  - added SynDBOracleOCIpath global variable to force oci.dll folder
  - will search for OracleInstantClient\oci.dll in executable sub-folder (can be
    used e.g. for Win64)
  - enhance logging at connection to specify the client version, database name
    and charset/codepage
  - removed CreateWithCodePage() constructor: now the charset will be retrieved
    at connection, and used for CHAR/NVARCHAR2 fields - see ticket [a6a639ec43]
  - added some WIN1252 charset aliases for DB (ISO-8859-1/15)
  - force ftCurrency to use '.' as decimal separator in returned SQLT_STR buffer
    (as expected by JSON) - previous fix in 1.17 revision did not work
  - added TSQLDBOracleConnectionProperties.ExtractTnsName() class method
  - when a column charset does not match the connection value, log a warning and
    force connection-level code page instead of raising an Exception
  - fixed ticket [4c68975022] about broken SQL statement when logging active
................................................................................
  {$A+}
  /// wrapper to an array of TOracleDate items
  TOracleDateArray = array[0..(maxInt div sizeof(TOracleDate))-1] of TOracleDate;

  /// will implement properties shared by native Oracle Client Interface connections
  TSQLDBOracleConnectionProperties = class(TSQLDBConnectionPropertiesThreadSafe)
  protected


    fRowsPrefetchSize: Integer;
    fBlobPrefetchSize: Integer;
    fStatementCacheSize: integer;
    fInternalBufferSize: integer;
    fEnvironmentInitializationMode: integer;
    function GetClientVersion: RawUTF8;
    /// initialize fForeignKeys content with all foreign keys of this DB
    // - used by GetForeignKey method
    procedure GetForeignKeys; override;
  public
    /// initialize the connection properties





    // - we don't need a database name parameter for Oracle connection: only
    // aServerName is to be set
    // - you may specify the TNSName in aServerName, or a connection string
    // like '//host[:port]/[service_name]', e.g. '//sales-server:1523/sales'







    // - connection is opened globaly as UTF-8, to match the internal encoding
    // of our units; but CHAR / NVARCHAR2 fields will use the Oracle charset
    // as retrieved from the opened connection (to avoid any conversion error)
    constructor Create(const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8); override;
    /// create a new connection
    // - call this method if the shared MainConnection is not enough (e.g. for
    // multi-thread access)
    // - the caller is responsible of freeing this instance
    // - this overriden method will create an TSQLDBOracleConnection instance
    function NewConnection: TSQLDBConnection; override;
    /// extract the TNS listener name from a Oracle full connection string
    // - e.g. ExtractTnsName('1.2.3.4:1521/dbname') returns 'dbname'
    class function ExtractTnsName(const aServerName: RawUTF8): RawUTF8;
  published
    /// returns the Client version e.g. '11.2.0.1 at oci.dll'
    property ClientVersion: RawUTF8 read GetClientVersion;






    /// the OCI initialization mode used for the connection
    // - equals OCI_EVENTS or OCI_THREADED by default, since will likely be
    // used in a multi-threaded context (even if this class is inheriting from
    // TSQLDBConnectionPropertiesThreadSafe), and  OCI_EVENTS is needed to support
    // Oracle RAC Connection Load Balancing
    // - can be tuned depending on the configuration or the Oracle version
    property EnvironmentInitializationMode: integer
................................................................................
    fEnv: pointer;
    fError: pointer;
    fServer: pointer;
    fContext: pointer;
    fSession: pointer;
    fTrans: pointer;
    fOCICharSet: integer;
    fAnsiConvert: TSynAnsiConvert;
    function DateTimeToDescriptor(aDateTime: TDateTime): pointer;
    procedure STRToUTF8(P: PAnsiChar; var result: RawUTF8;
      ColumnDBCharSet,ColumnDBForm: integer);
    {$ifndef UNICODE}
    procedure STRToAnsiString(P: PAnsiChar; var result: AnsiString;
      ColumnDBCharSet,ColumnDBForm: integer);
    {$endif}
................................................................................
    /// load the oci.dll library
    // - and retrieve all Oci*() addresses for OCI_ENTRIES[] items
    constructor Create;
    /// retrieve the client version as '11.2.0.1 at oci.dll'
    function ClientRevision: RawUTF8;
    /// retrieve the OCI charset ID from a Windows Code Page
    // - will only handle most known Windows Code Page
    // - if aCodePage=0, will use the NLS_LANG environment variable
    // - will use 'WE8MSWIN1252' (CODEPAGE_US) if the Code Page is unknown
    function CodePageToCharSetID(env: pointer; aCodePage: integer): integer;
    /// raise an exception on error
    procedure Check(Status: Integer; ErrorHandle: POCIError;
      InfoRaiseException: Boolean=false; LogLevelNoRaise: TSynLogInfo=sllNone);
      {$ifdef HASINLINE} inline; {$endif}
    /// retrieve some BLOB content
    procedure BlobFromDescriptor(svchp: POCISvcCtx; errhp: POCIError;
      locp: POCIDescriptor; out result: RawByteString); overload;
................................................................................
    result := FormatUTF8(EXE_FMT,[major_version,minor_version,update_num,patch_num,fLibraryPath]);
  end;
end;

const
  // http://download.oracle.com/docs/cd/B19306_01/server.102/b14225/applocaledata.htm#i635016
  // http://www.mydul.net/charsets.html
  CODEPAGES: array[0..15] of record Num, Charset: integer; Text: PUTF8Char end = (
    (Num: 1252; Charset: OCI_WE8MSWIN1252; Text: 'WE8MSWIN1252'),
    (Num: 1250; Charset: 170; Text: 'EE8MSWIN1250'),
    (Num: 1251; Charset: 171; Text: 'CL8MSWIN1251'),
    (Num: 1253; Charset: 174; Text: 'EL8MSWIN1253'),
    (Num: 1254; Charset: 177; Text: 'TR8MSWIN1254'),
    (Num: 1255; Charset: 175; Text: 'IW8MSWIN1255'),
    (Num: 1256; Charset: 560; Text: 'AR8MSWIN1256'),
    (Num: 1257; Charset: 179; Text: 'BLT8MSWIN1257'),
    (Num:  874; Charset: 41;  Text: 'TH8TISASCII'),
    (Num:  932; Charset: 832; Text: 'JA16SJIS'),
    (Num:  949; Charset: 846; Text: 'KO16MSWIN949'),
    (Num:  936; Charset: 852; Text: 'ZHS16GBK'),
    (Num:  950; Charset: 867; Text: 'ZHT16MSWIN950'),
    (Num: 1258; Charset: 45;  Text: 'ZHT16HKSCS'),
     // handle some aliases
    (Num: 1252; Charset: 46;  Text: 'WE8ISO8859P15'),
    (Num: 1252; Charset: 31;  Text: 'WE8ISO8859P1'));

function EnvVariableToCodePage: integer;
var i: integer;
    nlslang: array[byte] of AnsiChar;
begin
................................................................................
    for i2 := 0 to High(CODEPAGES) do
      if (CODEPAGES[i2].Charset=aCharset2) and
         (CODEPAGES[i1].Num=CODEPAGES[i2].Num) then
        exit; // aliases are allowed
  result := false;
end;

function OracleCharSetName(aCharsetID: integer): PUTF8Char;
var i: integer;
begin
  for i := 0 to high(CODEPAGES) do
    with CODEPAGES[i] do
    if Charset=aCharsetID then begin
      result := Text;
      exit;
    end;
  result := '?';
end;

function CharSetIDToCodePage(aCharSetID: integer): integer;
var i: integer;
begin
  for i := 0 to high(CODEPAGES) do
    with CODEPAGES[i] do
    if Charset=aCharsetID then begin
      result := Num;
      exit;
    end;
  result := GetACP; // return the default OS code page if not found
end;

function TSQLDBOracleLib.CodePageToCharSetID(env: pointer;
  aCodePage: integer): integer;
var ocp: PUTF8Char;
    i: integer;
    nlslang: array[byte] of AnsiChar;
begin
  case aCodePage of
  0: begin
    i := GetEnvironmentVariableA('NLS_LANG',nlslang,sizeof(nlslang));
    if i<>0 then begin
      nlslang[i] := #0;
      result := NlsCharSetNameToID(env,nlslang);
    end else
      result := 0;
  end;
  CP_UTF8:
    result := OCI_UTF8;
  CP_UTF16:
    result := OCI_UTF16ID;
  else begin
    ocp := CODEPAGES[0].Text; // default is MS Windows Code Page 1252
    for i := 0 to high(CODEPAGES) do
      if aCodePage=CODEPAGES[i].Num then begin
        ocp := CODEPAGES[i].Text;
        break;
      end;
    result := NlsCharSetNameToID(env,ocp);


  end;
  end;
  if result=0 then
    result := OCI_WE8MSWIN1252;
end;

constructor TSQLDBOracleLib.Create;
var P: PPointer;
    i: integer;
    orhome: array[byte] of Char;
begin
................................................................................

var
  OCI: TSQLDBOracleLib = nil;


{ TSQLDBOracleConnectionProperties }





















class function TSQLDBOracleConnectionProperties.ExtractTnsName(
  const aServerName: RawUTF8): RawUTF8;
var i: integer;
begin
  i := PosEx('/',aServerName);
  if i=0 then
    result := aServerName else
    result := copy(aServerName,i+1,100);
end;

constructor TSQLDBOracleConnectionProperties.Create(const aServerName,
  aDatabaseName, aUserID, aPassWord: RawUTF8);
begin
  fDBMS := dOracle;
  inherited Create(aServerName,'',aUserID,aPassWord);
  if OCI=nil then
    GarbageCollectorFreeAndNil(OCI,TSQLDBOracleLib.Create);
  fBlobPrefetchSize := 4096;
  fRowsPrefetchSize := 128*1024;
  fStatementCacheSize := 30; // default is 20
  fInternalBufferSize := 128*1024; // 128 KB
  fBatchSendingAbilities := [cCreate,cUpdate,cDelete]; // array DML feature
  fBatchMaxSentAtOnce := 10000;  // iters <= 32767 for better performance
  fEnvironmentInitializationMode := OCI_EVENTS or OCI_THREADED;
end;

function TSQLDBOracleConnectionProperties.GetClientVersion: RawUTF8;
begin
  result := OCI.ClientRevision;
end;

................................................................................
  with OCI do
  try
    if fEnv=nil then
      // will use UTF-8 encoding by default, in a multi-threaded context
      // OCI_EVENTS is needed to support Oracle RAC Connection Load Balancing
      EnvNlsCreate(fEnv,Props.EnvironmentInitializationMode,
        nil,nil,nil,nil,0,nil,OCI_UTF8,OCI_UTF8);



    HandleAlloc(fEnv,fError,OCI_HTYPE_ERROR);
    HandleAlloc(fEnv,fServer,OCI_HTYPE_SERVER);
    HandleAlloc(fEnv,fContext,OCI_HTYPE_SVCCTX);
    Check(ServerAttach(fServer,fError,pointer(Props.ServerName),
      length(Props.ServerName),0),fError);
    // we don't catch all errors here, since Client may ignore unhandled ATTR
    AttrSet(fContext,OCI_HTYPE_SVCCTX,fServer,0,OCI_ATTR_SERVER,fError);
................................................................................
    if Props.UseCache then begin
      AttrSet(fContext,OCI_HTYPE_SVCCTX,@Props.fStatementCacheSize,0,
        OCI_ATTR_STMTCACHESIZE,fError);
      mode := OCI_STMT_CACHE;
    end else
      mode := OCI_DEFAULT;
    Check(SessionBegin(fContext,fError,fSession,OCI_CRED_RDBMS,mode),fError);
    if fOCICharSet=0 then begin
      // retrieve the charset to be used for inlined CHAR / VARCHAR2 fields
      with NewStatement do
      try
        Execute('SELECT NLS_CHARSET_ID(PROPERTY_VALUE) FROM DATABASE_PROPERTIES'+
          ' WHERE PROPERTY_NAME=''NLS_CHARACTERSET''',true);
        if Step then
          fOCICharSet := ColumnInt(0) else
          fOCICharSet := CodePageToCharSetID(fEnv,0); // retrieve from NLS_LANG
      finally
        Free;
      end;
      fAnsiConvert := TSynAnsiConvert.Engine(CharSetIDToCodePage(fOCICharSet));
    end;
    {$ifndef DELPHI5OROLDER}
    Log.Log(sllInfo,'Connected to % as % with %, codepage % (%/%)',
      [Props.ServerName,Props.UserID,Props.ClientVersion,fAnsiConvert.CodePage,
       fOCICharSet,OracleCharSetName(fOCICharSet)],self);
    {$endif}
    with NewStatement do
    try // ORM will send date/time as ISO8601 text -> force encoding
      Execute('ALTER SESSION SET NLS_DATE_FORMAT=''YYYY-MM-DD-HH24:MI:SS''',false);
    finally
      Free;
................................................................................
  ColumnDBCharSet,ColumnDBForm: integer);
var L: integer;
begin
  L := StrLen(PUTF8Char(P));
  if (L=0) or (ColumnDBCharSet=OCI_UTF8) or (ColumnDBForm=SQLCS_NCHAR) or
     (fOCICharSet=OCI_UTF8) then
    SetString(result,P,L) else

    result := fAnsiConvert.AnsiBufferToRawUTF8(P,L);
end;

{$ifndef UNICODE}
procedure TSQLDBOracleConnection.STRToAnsiString(P: PAnsiChar; var result: AnsiString;
  ColumnDBCharSet,ColumnDBForm: integer);
var L: integer;
begin
  L := StrLen(PUTF8Char(P));

  if (L=0) or ((ColumnDBCharSet<>OCI_UTF8) and (ColumnDBForm<>SQLCS_NCHAR) and
     (fAnsiConvert.CodePage=GetACP)) then
    SetString(result,P,L) else
    result := CurrentAnsiConvert.AnsiToAnsi(fAnsiConvert,P,L);

end;
{$endif}



{ TSQLDBOracleStatement }