mORMot and Open Source friends
Check-in [1f921ffa18]
Not logged in

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

Overview
Comment:{1126} introducing new TSQLDBConnectionProperties.OnStatementInfo event, to be used e.g. to track ORA-28001 or ORA-28002 warnings about account expire
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 1f921ffa18cd9f0839afd07d09dce829521195a1
User & Date: ab 2015-03-24 09:40:22
Context
2015-03-24
10:14
{1127} ensure TSQLHttpClientWebsockets may upgrade to our WebSockets bidirectional protocols after a warm up as regular HTTP/1.1 client check-in: 7615425a9f user: ab tags: trunk
09:40
{1126} introducing new TSQLDBConnectionProperties.OnStatementInfo event, to be used e.g. to track ORA-28001 or ORA-28002 warnings about account expire check-in: 1f921ffa18 user: ab tags: trunk
07:27
{1125} new speConnected / speDisconnected and speStartTransaction / speCommit / speRollback events for TSQLDBConnectionProperties/TSQLDBConnection.OnProcess event handlers check-in: 046e9e17fe user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SynDB.pas.

154
155
156
157
158
159
160

161
162
163
164
165
166
167
...
993
994
995
996
997
998
999








1000
1001
1002
1003
1004
1005
1006
....
1057
1058
1059
1060
1061
1062
1063

1064
1065
1066
1067
1068
1069
1070
....
1445
1446
1447
1448
1449
1450
1451
1452

1453
1454



1455
1456
1457
1458
1459
1460
1461
    individual reading or writing speed by a factor of 4x
  - TSQLDBConnectionProperties.Create will set ForcedSchemaName := 'dbo'
    ("DataBase Owner") by default for dMSSQL kind of database engine
  - introducing TSQLDBConnectionProperties DefinitionTo/DefinitionToJSON/
    DefinitionToFile methods and CreateFrom*() class methods to persist the
    database connection properties, and the associated class, as JSON 
  - new TSQLDBConnectionProperties/TSQLDBConnection.OnProcess event handlers

  - added TSQLDBConnectionProperties.StoreVoidStringAsNull, which will be
    set e.g. for MS SQL and Jet databases which do not allow by default to
    store '' values, but expect NULL instead
  - TSQLDBConnection.Connect will now trigger OnProcess(speReconnected) and
    update the new TSQLDBConnection.TotalConnectionCount property
  - TSQLDBConnection.Disconnect will now flush internal statement cache
  - TQuery.Execute() is now able to try to re-connect once in case of failure
................................................................................
    speStartTransaction, speCommit, speRollback);

  /// event handler called during all external DB process
  // - event handler is specified by TSQLDBConnectionProperties.OnProcess or
  // TSQLDBConnection.OnProperties properties
  TOnSQLDBProcess = procedure(Sender: TSQLDBConnection; Event: TOnSQLDBProcessEvent) of object;









  /// actions implemented by TSQLDBConnectionProperties.SharedTransaction()
  TSQLDBSharedTransactionAction = (transBegin, transCommit, transRollback);

  /// defines a callback signature able to handle multiple INSERT
  // - may execute e.g. for 2 fields and 3 data rows on a database engine
  // implementing INSERT with multiple VALUES (like MySQL, PostgreSQL, NexusDB,
  // MSSQL or SQlite3), as implemented by
................................................................................
    fForeignKeys: TSynNameValue;
    fSQLCreateField: TSQLDBFieldTypeDefinition;
    fSQLCreateFieldMax: cardinal;
    fSQLGetServerTimeStamp: RawUTF8;
    fEngineName: RawUTF8;
    fDBMS: TSQLDBDefinition;
    fOnProcess: TOnSQLDBProcess;

    fConnectionTimeOutTicks: Int64;
    fSharedTransactions: array of record
      SessionID: cardinal;
      RefCount: integer;
      Connection: TSQLDBConnection;
    end;
    procedure SetConnectionTimeOutMinutes(minutes: cardinal);
................................................................................
    // - not published, for security reasons (may be serialized otherwise)
    property PassWord: RawUTF8 read fPassWord;
    /// can be used to store the fForeignKeys[] data in an external BLOB
    // - since GetForeignKeys can be (somewhat) slow, could save a lot of time
    property ForeignKeysData: RawByteString
      read GetForeignKeysData write SetForeignKeysData;
    /// this event handler will be called during all process
    // - can be used e.g. to change the desktop cursor

    // - you can override this property directly in the TSQLDBConnection
    property OnProcess: TOnSQLDBProcess read fOnProcess write fOnProcess;



    /// you can define a callback method able to handle multiple INSERT
    // - may execute e.g. INSERT with multiple VALUES (like MySQL, MSSQL, NexusDB,
    // PostgreSQL or SQlite3), as defined by MultipleValuesInsert() callback
    property OnBatchInsert: TOnBatchInsert read fOnBatchInsert write fOnBatchInsert;
  published { to be logged as JSON - no UserID nor Password for security :) }
    /// return the database engine name, as computed from the class name
    // - 'TSQLDBConnectionProperties' will be trimmed left side of the class name






>







 







>
>
>
>
>
>
>
>







 







>







 







|
>


>
>
>







154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
...
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
....
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
....
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
    individual reading or writing speed by a factor of 4x
  - TSQLDBConnectionProperties.Create will set ForcedSchemaName := 'dbo'
    ("DataBase Owner") by default for dMSSQL kind of database engine
  - introducing TSQLDBConnectionProperties DefinitionTo/DefinitionToJSON/
    DefinitionToFile methods and CreateFrom*() class methods to persist the
    database connection properties, and the associated class, as JSON 
  - new TSQLDBConnectionProperties/TSQLDBConnection.OnProcess event handlers
  - new TSQLDBConnectionProperties.OnStatementInfo event handler
  - added TSQLDBConnectionProperties.StoreVoidStringAsNull, which will be
    set e.g. for MS SQL and Jet databases which do not allow by default to
    store '' values, but expect NULL instead
  - TSQLDBConnection.Connect will now trigger OnProcess(speReconnected) and
    update the new TSQLDBConnection.TotalConnectionCount property
  - TSQLDBConnection.Disconnect will now flush internal statement cache
  - TQuery.Execute() is now able to try to re-connect once in case of failure
................................................................................
    speStartTransaction, speCommit, speRollback);

  /// event handler called during all external DB process
  // - event handler is specified by TSQLDBConnectionProperties.OnProcess or
  // TSQLDBConnection.OnProperties properties
  TOnSQLDBProcess = procedure(Sender: TSQLDBConnection; Event: TOnSQLDBProcessEvent) of object;

  /// event handler called when the low-level driver send some warning information
  // - errors will trigger Exceptions, but sometimes the database driver returns
  // some non critical information, which is logged and may be intercepted using
  // the TSQLDBConnectionProperties.OnStatementInfo property
  // - may be used e.g. to track ORA-28001 or ORA-28002 about account expire
  // - is currently implemented by SynDBOracle, SynDBODBC and SynOleDB units
  TOnSQLDBInfo = procedure(Sender: TSQLDBStatement; const Msg: RawUTF8) of object;

  /// actions implemented by TSQLDBConnectionProperties.SharedTransaction()
  TSQLDBSharedTransactionAction = (transBegin, transCommit, transRollback);

  /// defines a callback signature able to handle multiple INSERT
  // - may execute e.g. for 2 fields and 3 data rows on a database engine
  // implementing INSERT with multiple VALUES (like MySQL, PostgreSQL, NexusDB,
  // MSSQL or SQlite3), as implemented by
................................................................................
    fForeignKeys: TSynNameValue;
    fSQLCreateField: TSQLDBFieldTypeDefinition;
    fSQLCreateFieldMax: cardinal;
    fSQLGetServerTimeStamp: RawUTF8;
    fEngineName: RawUTF8;
    fDBMS: TSQLDBDefinition;
    fOnProcess: TOnSQLDBProcess;
    fOnStatementInfo: TOnSQLDBInfo;
    fConnectionTimeOutTicks: Int64;
    fSharedTransactions: array of record
      SessionID: cardinal;
      RefCount: integer;
      Connection: TSQLDBConnection;
    end;
    procedure SetConnectionTimeOutMinutes(minutes: cardinal);
................................................................................
    // - not published, for security reasons (may be serialized otherwise)
    property PassWord: RawUTF8 read fPassWord;
    /// can be used to store the fForeignKeys[] data in an external BLOB
    // - since GetForeignKeys can be (somewhat) slow, could save a lot of time
    property ForeignKeysData: RawByteString
      read GetForeignKeysData write SetForeignKeysData;
    /// this event handler will be called during all process
    // - can be used e.g. to change the desktop cursor, or be notified
    // on connection/disconnection/reconnection
    // - you can override this property directly in the TSQLDBConnection
    property OnProcess: TOnSQLDBProcess read fOnProcess write fOnProcess;
    /// this event handler will be called when statements trigger some low-level
    // information
    property OnStatementInfo: TOnSQLDBInfo read fOnStatementInfo write fOnStatementInfo;
    /// you can define a callback method able to handle multiple INSERT
    // - may execute e.g. INSERT with multiple VALUES (like MySQL, MSSQL, NexusDB,
    // PostgreSQL or SQlite3), as defined by MultipleValuesInsert() callback
    property OnBatchInsert: TOnBatchInsert read fOnBatchInsert write fOnBatchInsert;
  published { to be logged as JSON - no UserID nor Password for security :) }
    /// return the database engine name, as computed from the class name
    // - 'TSQLDBConnectionProperties' will be trimmed left side of the class name

Changes to SynDBODBC.pas.

943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
....
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
....
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
....
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
....
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
....
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
....
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
....
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
....
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
....
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
....
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
....
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663

1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
....
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710

1711
1712
1713
1714
1715
1716
1717
1718
1719
....
1727
1728
1729
1730
1731
1732
1733
1734
1735







1736
1737
1738
1739
1740
1741
1742
....
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
....
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
....
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
....
1865
1866
1867
1868
1869
1870
1871

1872
1873
1874
1875
1876
1877
1878
1879
      var StringLength2Ptr: SqlSmallint; DriverCompletion: SqlUSmallint): SqlReturn;
      {$ifdef MSWINDOWS} stdcall {$else} cdecl {$endif};
  public
    /// load the ODBC library
    // - and retrieve all SQL*() addresses for ODBC_ENTRIES[] items
    constructor Create;
    /// raise an exception on error
    procedure Check(Stmt: TSQLDBStatement; Status: SqlReturn;
      HandleType: SqlSmallint; Handle: SqlHandle; InfoRaiseException: Boolean=false;
      LogLevelNoRaise: TSynLogInfo=sllNone);
      {$ifdef HASINLINE} inline; {$endif}
    /// generic process of error handle
    procedure HandleError(Stmt: TSQLDBStatement; Status: SqlReturn;
      HandleType: SqlSmallint; Handle: SqlHandle; InfoRaiseException: Boolean;
      LogLevelNoRaise: TSynLogInfo);
    /// wrapper around SQLGetDiagField() API call
    function GetDiagField(StatementHandle: SqlHStmt): RawUTF8;
    /// wrapper around GetInfo() API call
    procedure GetInfoString(ConnectionHandle: SqlHDbc; InfoType: SqlUSmallint;
      var Dest: RawUTF8);
  end;

................................................................................
  Disconnect; // force fDbc=nil
  if fEnv=nil then
    if (ODBC=nil) or (ODBC.AllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,fEnv)=SQL_ERROR) then
      raise EODBCException.CreateUTF8('%: Unable to allocate an environment handle',[self]);
  with ODBC do
  try
    // connect
    Check(nil,SetEnvAttr(fEnv,SQL_ATTR_ODBC_VERSION,SQL_OV_ODBC3,0),SQL_HANDLE_ENV,fEnv);
    Check(nil,AllocHandle(SQL_HANDLE_DBC,fEnv,fDbc),SQL_HANDLE_ENV,fEnv);
    with fODBCProperties do
      if fServerName<>'' then
        Check(nil,ConnectA(fDbc,pointer(fServerName),length(fServerName),
          pointer(fUserID),length(fUserID),pointer(fPassWord),length(fPassWord)),
          SQL_HANDLE_DBC,fDbc) else
      if fDatabaseName='' then
        raise EODBCException.Create(
          'Need ServerName=DataSourceName or DataBaseName=FullConnectString') else begin
        SetString(fSQLDriverFullString,nil,1024);
        fSQLDriverFullString[1] := #0;
        Len := 0;
        Check(nil,SQLDriverConnectA(fDbc,
          {$ifdef MSWINDOWS}GetDesktopWindow{$else}0{$endif},
          Pointer(fDatabaseName),length(fDatabaseName),pointer(fSQLDriverFullString),
          length(fSQLDriverFullString),Len,
          DRIVERCOMPLETION[fODBCProperties.fSQLDriverConnectPrompt]),SQL_HANDLE_DBC,fDbc);
        SetLength(fSQLDriverFullString,Len);
      end;
    // retrieve information of the just created connection
................................................................................
  result := TODBCStatement.Create(self);
end;

procedure TODBCConnection.Commit;
begin
  inherited Commit;
  with ODBC do begin
    Check(nil,EndTran(SQL_HANDLE_DBC,fDBc,SQL_COMMIT),SQL_HANDLE_DBC,fDBc);
    Check(nil,SetConnectAttrW(fDBc,SQL_AUTOCOMMIT,SQL_AUTOCOMMIT_ON,0),
      SQL_HANDLE_DBC,fDBc); // back to default AUTO COMMIT ON mode
  end;
end;

procedure TODBCConnection.Rollback;
begin
  inherited RollBack;
  with ODBC do begin
    Check(nil,EndTran(SQL_HANDLE_DBC,fDBc,SQL_ROLLBACK),SQL_HANDLE_DBC,fDBc);
    Check(nil,SetConnectAttrW(fDBc,SQL_AUTOCOMMIT,SQL_AUTOCOMMIT_ON,0),
      SQL_HANDLE_DBC,fDBc); // back to default AUTO COMMIT ON mode
  end;
end;

procedure TODBCConnection.StartTransaction;
begin
  if TransactionCount>0 then
    raise EODBCException.CreateUTF8('% do not support nested transactions',[self]);
  inherited StartTransaction;
  ODBC.Check(nil,ODBC.SetConnectAttrW(fDBc,SQL_AUTOCOMMIT,SQL_AUTOCOMMIT_OFF,0),
    SQL_HANDLE_DBC,fDBc);
end;


{ TODBCStatement }

procedure TODBCStatement.AllocStatement;
................................................................................
  if fStatement<>nil then
    raise EODBCException.CreateUTF8('%.AllocStatement called twice',[self]);
  fCurrentRow := 0;
  if not fConnection.Connected then
    fConnection.Connect;
  hDbc := (fConnection as TODBCConnection).fDbc;
  with ODBC do
    Check(self,AllocHandle(SQL_HANDLE_STMT,hDBC,fStatement),SQL_HANDLE_DBC,hDBC);
end;

function ODBCColumnToFieldType(DataType, ColumnPrecision, ColumnScale: integer): TSQLDBFieldType;
begin // ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency, ftDate, ftUTF8, ftBlob
  case DataType of
    SQL_DECIMAL, SQL_NUMERIC, SQL_FLOAT: begin
      result := ftDouble;
................................................................................
begin
  if (fColumnCount>0) or (fColData<>nil) then begin
    Finalize(fColData);
    fColumn.Clear;
    fColumn.ReHash;
  end;
  with ODBC do begin
    Check(self,NumResultCols(fStatement,nCols),SQL_HANDLE_STMT,fStatement);
    SetLength(fColData,nCols);
    for c := 1 to nCols do begin
      Check(self,DescribeColW(fStatement,c,Name,256,NameLength,DataType,ColumnSize,
        DecimalDigits,Nullable),SQL_HANDLE_STMT,fStatement);
      with PSQLDBColumnProperty(fColumn.AddAndMakeUniqueName(
         RawUnicodeToUtf8(Name,NameLength)))^ do begin
        ColumnValueInlined := true;
        ColumnValueDBType := DataType;
        if ColumnSize>65535 then
          ColumnSize := 0; // avoid out of memory error for BLOBs
................................................................................
    ExpectedDataType := ODBC_TYPE_TOC[ColumnType];
    Status := ODBC.GetData(fStatement,c+1,ExpectedDataType,
      pointer(fColData[c]),length(fColData[c]),@Indicator);
    if Status<>SQL_SUCCESS then
      if (Status=SQL_SUCCESS_WITH_INFO) and
         (ColumnType in FIXEDLENGTH_SQLDBFIELDTYPE) then
        Status := SQL_SUCCESS else // allow rounding problem
        ODBC.HandleError(self,Status,SQL_HANDLE_STMT,fStatement,false,sllNone);
    ColumnDataSize := Indicator;
    if Indicator>=0 then
      if Status=SQL_SUCCESS then
        ColumnDataState := colDataFilled else
        ColumnDataState := colDataTruncated else
    case Indicator of
    SQL_NULL_DATA:
................................................................................
  R := ODBC.MoreResults(fStatement);
  case R of
    SQL_NO_DATA:
      result := false; // no more results
    SQL_SUCCESS, SQL_SUCCESS_WITH_INFO:
      result := true; // got next
    else begin
      ODBC.Check(self, R, SQL_HANDLE_STMT, fStatement); // error
      result := false; // makes compiler happy
    end;
  end;
end;

function TODBCStatement.ColumnBlob(Col: integer): RawByteString;
var res: TSQLDBStatementGetCol;
................................................................................
  inherited Create(aConnection);
end;

destructor TODBCStatement.Destroy;
begin
  try
    if fStatement<>nil then
      ODBC.Check(self,ODBC.FreeHandle(SQL_HANDLE_STMT,fStatement),SQL_HANDLE_DBC,
        (fConnection as TODBCConnection).fDbc);
  finally
    inherited Destroy;
  end;
end;

const
................................................................................
        if (status=SQL_ERROR) and (not DriverDoesNotHandleUnicode) and
           (ODBC.GetDiagField(fStatement)='HY004') then begin
          TODBCConnection(fConnection).fODBCProperties.fDriverDoesNotHandleUnicode := true;
          DriverDoesNotHandleUnicode := true;
          VData := RawUnicodeToUtf8(pointer(VData),StrLenW(pointer(VData)));
          goto retry; // circumvent restriction of non-Unicode ODBC drivers
        end;
        ODBC.Check(self,status,SQL_HANDLE_STMT,fStatement);
      end;
    end;
    // 2. execute prepared statement
    status := ODBC.Execute(fStatement);
    if not (status in [SQL_SUCCESS,SQL_NO_DATA]) then
      ODBC.HandleError(self,status,SQL_HANDLE_STMT,fStatement,false,sllNone);
    if fExpectResults then
      BindColumns;
  finally
    // 3. release and/or retrieve OUT bound parameters
    for p := 0 to fParamCount-1 do
    with fParams[p] do
    case VType of
................................................................................
end;

procedure TODBCStatement.Reset;
begin
  if fStatement<>nil then
  with ODBC do begin
    if fColumnCount>0 then
      Check(self,CloseCursor(fStatement),SQL_HANDLE_STMT,fStatement);
    if fParamCount>0 then
      Check(self,FreeStmt(fStatement,SQL_RESET_PARAMS),SQL_HANDLE_STMT,fStatement);
  end;
  inherited Reset;
end;

function TODBCStatement.UpdateCount: integer;
var RowCount: SqlLen;
begin
  if (fStatement<>nil) and not fExpectResults then
    ODBC.Check(self,ODBC.RowCount(fStatement,RowCount),SQL_HANDLE_STMT,fStatement) else
    RowCount := 0;
  result := RowCount;
end;

procedure TODBCStatement.Prepare(const aSQL: RawUTF8; ExpectResults: Boolean);
var Log: ISynLog;
begin
................................................................................
    raise EODBCException.CreateUTF8('%.Prepare should be called only once',[self]);
  // 1. process SQL
  inherited Prepare(aSQL,ExpectResults); // set fSQL + Connect if necessary
  fSQLW := Utf8DecodeToRawUnicode(fSQL);
  // 2. prepare statement and bind result columns (if any)
  AllocStatement;
  try
    ODBC.Check(self,ODBC.PrepareW(fStatement,pointer(fSQLW),length(fSQLW) shr 1),
      SQL_HANDLE_STMT,fStatement);
  except
    on E: Exception do begin
      Log.Log(sllError,E);
      ODBC.FreeHandle(SQL_HANDLE_STMT,fStatement);
      fStatement := nil;
      raise;
................................................................................
    SQL_NO_DATA:
      exit;
    SQL_SUCCESS, SQL_SUCCESS_WITH_INFO: begin // ignore WITH_INFO messages
      fCurrentRow := sav+1;
      inc(fTotalRowsRetrieved);
      result := true; // mark data available for Column*() methods
    end;
    else HandleError(self,status,SQL_HANDLE_STMT,fStatement,false,sllNone);
    end;
  end;
end;


{ TODBCLib }


procedure TODBCLib.Check(Stmt: TSQLDBStatement; Status: SqlReturn; HandleType: SqlSmallint;
  Handle: SqlHandle; InfoRaiseException: Boolean=false; LogLevelNoRaise: TSynLogInfo=sllNone);
begin
  if Status<>SQL_SUCCESS then
    HandleError(Stmt,Status,HandleType,Handle,InfoRaiseException,LogLevelNoRaise);
end;

constructor TODBCLib.Create;
var P: PPointer;
    i: integer;
begin
  fHandle := SafeLoadLibrary('odbc32.dll');
................................................................................

procedure TODBCLib.GetInfoString(ConnectionHandle: SqlHDbc; InfoType: SqlUSmallint;
  var Dest: RawUTF8);
var Len: SqlSmallint;
    Info: array[byte] of WideChar;
begin
  Len := 0;
  Check(nil,GetInfoW(ConnectionHandle,InfoType,@Info,sizeof(Info)shr 1,@Len),
    SQL_HANDLE_DBC,ConnectionHandle);
  Dest := RawUnicodeToUtf8(Info,Len shr 1);
end;
  

procedure TODBCLib.HandleError(Stmt: TSQLDBStatement; Status: SqlReturn; HandleType: SqlSmallint;
  Handle: SqlHandle; InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
const FMT: PUTF8Char = '%[%] % (%)'#13#10;
var Sqlstate: array[0..6] of WideChar;
    MessageText: array[0..1023] of WideChar;
    RecNum, NativeError: SqlInteger;
    TextLength: SqlSmallint;
    msg: RawUTF8;
begin
................................................................................
        MessageText[textlength] := #0; // trim #13/#10 right of MessageText
      end;
      msg := FormatUTF8(FMT,[msg,Sqlstate,MessageText,NativeError]);
      inc(RecNum);
    end;
    if msg='' then
      msg := 'Unspecified error';
    if (Status=SQL_SUCCESS_WITH_INFO) and not InfoRaiseException then
      LogLevelNoRaise := sllInfo;







  end;
  if LogLevelNoRaise<>sllNone then
    SynDBLog.Add.Log(LogLevelNoRaise,msg) else
    if Stmt=nil then
      raise EODBCException.CreateUTF8('% error: %',[self,msg]) else
      raise EODBCException.CreateUTF8('% - % error: %',[Stmt,self,msg]);
end;
................................................................................
    with TODBCStatement.Create(MainConnection) do
    try
      AllocStatement;
      status := ODBC.ColumnsA(fStatement,nil,0,pointer(Schema),SQL_NTS,
        pointer(Table),SQL_NTS,nil,0);
      if status<>SQL_SUCCESS then // e.g. driver does not support schema
        status := ODBC.ColumnsA(fStatement,nil,0,nil,0,pointer(Table),SQL_NTS,nil,0);
      ODBC.Check(nil,status,SQL_HANDLE_STMT,fStatement);
      BindColumns;
      FA.Init(TypeInfo(TSQLDBColumnDefineDynArray),Fields,@n);
      FA.Compare := SortDynArrayAnsiStringI; // FA.Find() case insensitive
      fillchar(F,sizeof(F),0);
      while Step do begin
        F.ColumnName := Trim(ColumnUTF8(3));
        DataType := ColumnInt(4);
................................................................................
      try
        AllocStatement;
        status := ODBC.StatisticsA(fStatement,nil,0,pointer(Schema),SQL_NTS,
          pointer(Table),SQL_NTS,SQL_INDEX_ALL,SQL_QUICK);
        if status<>SQL_SUCCESS then // e.g. driver does not support schema
          status := ODBC.StatisticsA(fStatement,nil,0,nil,0,pointer(Table),
            SQL_NTS,SQL_INDEX_ALL,SQL_QUICK);
        ODBC.Check(nil,status,SQL_HANDLE_STMT,fStatement);
        BindColumns;
        while Step do begin
          F.ColumnName := Trim(ColumnUTF8(8));
          i := FA.Find(F);
          if i>=0 then
            Fields[i].ColumnIndexed := true;
        end;
................................................................................
  inherited; // first try from SQL, if any (faster)
  if Tables<>nil then
    exit; // already retrieved directly from engine
  try
    with TODBCStatement.Create(MainConnection) do
    try
      AllocStatement;
      ODBC.Check(nil,ODBC.TablesA(fStatement,nil,0,nil,0,nil,0,'TABLE',SQL_NTS),SQL_HANDLE_STMT,fStatement);
      BindColumns;
      n := 0;
      while Step do begin
        schema := Trim(ColumnUTF8(1));
        tablename := Trim(ColumnUTF8(2));
        if schema<>'' then
          tablename := schema+'.'+tablename;
................................................................................

procedure TODBCConnectionProperties.GetForeignKeys;
begin
  try
    with TODBCStatement.Create(MainConnection) do
    try
      AllocStatement;

      ODBC.Check(nil,ODBC.ForeignKeysA(fStatement,nil,0,nil,0,nil,0,nil,0,nil,0,'%',SQL_NTS),
        SQL_HANDLE_STMT,fStatement);
      BindColumns;
      while Step do 
        fForeignKeys.Add(
          Trim(ColumnUTF8(5))+'.'+Trim(ColumnUTF8(6))+'.'+Trim(ColumnUTF8(7)),
          Trim(ColumnUTF8(1))+'.'+Trim(ColumnUTF8(2))+'.'+Trim(ColumnUTF8(3)));
    finally






|




|
|
|







 







|
|


|








|







 







|
|








|
|









|







 







|







 







|


|







 







|







 







|







 







|







 







|





|







 







|

|








|







 







|







 







|







>
|
|


|







 







|




>
|
|







 







|

>
>
>
>
>
>
>







 







|







 







|







 







|







 







>
|







943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
....
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
....
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
....
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
....
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
....
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
....
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
....
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
....
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
....
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
....
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
....
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
....
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
....
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
....
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
....
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
....
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
....
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
      var StringLength2Ptr: SqlSmallint; DriverCompletion: SqlUSmallint): SqlReturn;
      {$ifdef MSWINDOWS} stdcall {$else} cdecl {$endif};
  public
    /// load the ODBC library
    // - and retrieve all SQL*() addresses for ODBC_ENTRIES[] items
    constructor Create;
    /// raise an exception on error
    procedure Check(Conn: TSQLDBConnection; Stmt: TSQLDBStatement; Status: SqlReturn;
      HandleType: SqlSmallint; Handle: SqlHandle; InfoRaiseException: Boolean=false;
      LogLevelNoRaise: TSynLogInfo=sllNone);
      {$ifdef HASINLINE} inline; {$endif}
    /// generic process of error handle
    procedure HandleError(Conn: TSQLDBConnection; Stmt: TSQLDBStatement;
      Status: SqlReturn; HandleType: SqlSmallint; Handle: SqlHandle;
      InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
    /// wrapper around SQLGetDiagField() API call
    function GetDiagField(StatementHandle: SqlHStmt): RawUTF8;
    /// wrapper around GetInfo() API call
    procedure GetInfoString(ConnectionHandle: SqlHDbc; InfoType: SqlUSmallint;
      var Dest: RawUTF8);
  end;

................................................................................
  Disconnect; // force fDbc=nil
  if fEnv=nil then
    if (ODBC=nil) or (ODBC.AllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,fEnv)=SQL_ERROR) then
      raise EODBCException.CreateUTF8('%: Unable to allocate an environment handle',[self]);
  with ODBC do
  try
    // connect
    Check(self,nil,SetEnvAttr(fEnv,SQL_ATTR_ODBC_VERSION,SQL_OV_ODBC3,0),SQL_HANDLE_ENV,fEnv);
    Check(self,nil,AllocHandle(SQL_HANDLE_DBC,fEnv,fDbc),SQL_HANDLE_ENV,fEnv);
    with fODBCProperties do
      if fServerName<>'' then
        Check(self,nil,ConnectA(fDbc,pointer(fServerName),length(fServerName),
          pointer(fUserID),length(fUserID),pointer(fPassWord),length(fPassWord)),
          SQL_HANDLE_DBC,fDbc) else
      if fDatabaseName='' then
        raise EODBCException.Create(
          'Need ServerName=DataSourceName or DataBaseName=FullConnectString') else begin
        SetString(fSQLDriverFullString,nil,1024);
        fSQLDriverFullString[1] := #0;
        Len := 0;
        Check(self,nil,SQLDriverConnectA(fDbc,
          {$ifdef MSWINDOWS}GetDesktopWindow{$else}0{$endif},
          Pointer(fDatabaseName),length(fDatabaseName),pointer(fSQLDriverFullString),
          length(fSQLDriverFullString),Len,
          DRIVERCOMPLETION[fODBCProperties.fSQLDriverConnectPrompt]),SQL_HANDLE_DBC,fDbc);
        SetLength(fSQLDriverFullString,Len);
      end;
    // retrieve information of the just created connection
................................................................................
  result := TODBCStatement.Create(self);
end;

procedure TODBCConnection.Commit;
begin
  inherited Commit;
  with ODBC do begin
    Check(self,nil,EndTran(SQL_HANDLE_DBC,fDBc,SQL_COMMIT),SQL_HANDLE_DBC,fDBc);
    Check(self,nil,SetConnectAttrW(fDBc,SQL_AUTOCOMMIT,SQL_AUTOCOMMIT_ON,0),
      SQL_HANDLE_DBC,fDBc); // back to default AUTO COMMIT ON mode
  end;
end;

procedure TODBCConnection.Rollback;
begin
  inherited RollBack;
  with ODBC do begin
    Check(self,nil,EndTran(SQL_HANDLE_DBC,fDBc,SQL_ROLLBACK),SQL_HANDLE_DBC,fDBc);
    Check(self,nil,SetConnectAttrW(fDBc,SQL_AUTOCOMMIT,SQL_AUTOCOMMIT_ON,0),
      SQL_HANDLE_DBC,fDBc); // back to default AUTO COMMIT ON mode
  end;
end;

procedure TODBCConnection.StartTransaction;
begin
  if TransactionCount>0 then
    raise EODBCException.CreateUTF8('% do not support nested transactions',[self]);
  inherited StartTransaction;
  ODBC.Check(self,nil,ODBC.SetConnectAttrW(fDBc,SQL_AUTOCOMMIT,SQL_AUTOCOMMIT_OFF,0),
    SQL_HANDLE_DBC,fDBc);
end;


{ TODBCStatement }

procedure TODBCStatement.AllocStatement;
................................................................................
  if fStatement<>nil then
    raise EODBCException.CreateUTF8('%.AllocStatement called twice',[self]);
  fCurrentRow := 0;
  if not fConnection.Connected then
    fConnection.Connect;
  hDbc := (fConnection as TODBCConnection).fDbc;
  with ODBC do
    Check(nil,self,AllocHandle(SQL_HANDLE_STMT,hDBC,fStatement),SQL_HANDLE_DBC,hDBC);
end;

function ODBCColumnToFieldType(DataType, ColumnPrecision, ColumnScale: integer): TSQLDBFieldType;
begin // ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency, ftDate, ftUTF8, ftBlob
  case DataType of
    SQL_DECIMAL, SQL_NUMERIC, SQL_FLOAT: begin
      result := ftDouble;
................................................................................
begin
  if (fColumnCount>0) or (fColData<>nil) then begin
    Finalize(fColData);
    fColumn.Clear;
    fColumn.ReHash;
  end;
  with ODBC do begin
    Check(nil,self,NumResultCols(fStatement,nCols),SQL_HANDLE_STMT,fStatement);
    SetLength(fColData,nCols);
    for c := 1 to nCols do begin
      Check(nil,self,DescribeColW(fStatement,c,Name,256,NameLength,DataType,ColumnSize,
        DecimalDigits,Nullable),SQL_HANDLE_STMT,fStatement);
      with PSQLDBColumnProperty(fColumn.AddAndMakeUniqueName(
         RawUnicodeToUtf8(Name,NameLength)))^ do begin
        ColumnValueInlined := true;
        ColumnValueDBType := DataType;
        if ColumnSize>65535 then
          ColumnSize := 0; // avoid out of memory error for BLOBs
................................................................................
    ExpectedDataType := ODBC_TYPE_TOC[ColumnType];
    Status := ODBC.GetData(fStatement,c+1,ExpectedDataType,
      pointer(fColData[c]),length(fColData[c]),@Indicator);
    if Status<>SQL_SUCCESS then
      if (Status=SQL_SUCCESS_WITH_INFO) and
         (ColumnType in FIXEDLENGTH_SQLDBFIELDTYPE) then
        Status := SQL_SUCCESS else // allow rounding problem
        ODBC.HandleError(nil,self,Status,SQL_HANDLE_STMT,fStatement,false,sllNone);
    ColumnDataSize := Indicator;
    if Indicator>=0 then
      if Status=SQL_SUCCESS then
        ColumnDataState := colDataFilled else
        ColumnDataState := colDataTruncated else
    case Indicator of
    SQL_NULL_DATA:
................................................................................
  R := ODBC.MoreResults(fStatement);
  case R of
    SQL_NO_DATA:
      result := false; // no more results
    SQL_SUCCESS, SQL_SUCCESS_WITH_INFO:
      result := true; // got next
    else begin
      ODBC.Check(nil,self, R, SQL_HANDLE_STMT, fStatement); // error
      result := false; // makes compiler happy
    end;
  end;
end;

function TODBCStatement.ColumnBlob(Col: integer): RawByteString;
var res: TSQLDBStatementGetCol;
................................................................................
  inherited Create(aConnection);
end;

destructor TODBCStatement.Destroy;
begin
  try
    if fStatement<>nil then
      ODBC.Check(nil,self,ODBC.FreeHandle(SQL_HANDLE_STMT,fStatement),SQL_HANDLE_DBC,
        (fConnection as TODBCConnection).fDbc);
  finally
    inherited Destroy;
  end;
end;

const
................................................................................
        if (status=SQL_ERROR) and (not DriverDoesNotHandleUnicode) and
           (ODBC.GetDiagField(fStatement)='HY004') then begin
          TODBCConnection(fConnection).fODBCProperties.fDriverDoesNotHandleUnicode := true;
          DriverDoesNotHandleUnicode := true;
          VData := RawUnicodeToUtf8(pointer(VData),StrLenW(pointer(VData)));
          goto retry; // circumvent restriction of non-Unicode ODBC drivers
        end;
        ODBC.Check(nil,self,status,SQL_HANDLE_STMT,fStatement);
      end;
    end;
    // 2. execute prepared statement
    status := ODBC.Execute(fStatement);
    if not (status in [SQL_SUCCESS,SQL_NO_DATA]) then
      ODBC.HandleError(nil,self,status,SQL_HANDLE_STMT,fStatement,false,sllNone);
    if fExpectResults then
      BindColumns;
  finally
    // 3. release and/or retrieve OUT bound parameters
    for p := 0 to fParamCount-1 do
    with fParams[p] do
    case VType of
................................................................................
end;

procedure TODBCStatement.Reset;
begin
  if fStatement<>nil then
  with ODBC do begin
    if fColumnCount>0 then
      Check(nil,self,CloseCursor(fStatement),SQL_HANDLE_STMT,fStatement);
    if fParamCount>0 then
      Check(nil,self,FreeStmt(fStatement,SQL_RESET_PARAMS),SQL_HANDLE_STMT,fStatement);
  end;
  inherited Reset;
end;

function TODBCStatement.UpdateCount: integer;
var RowCount: SqlLen;
begin
  if (fStatement<>nil) and not fExpectResults then
    ODBC.Check(nil,self,ODBC.RowCount(fStatement,RowCount),SQL_HANDLE_STMT,fStatement) else
    RowCount := 0;
  result := RowCount;
end;

procedure TODBCStatement.Prepare(const aSQL: RawUTF8; ExpectResults: Boolean);
var Log: ISynLog;
begin
................................................................................
    raise EODBCException.CreateUTF8('%.Prepare should be called only once',[self]);
  // 1. process SQL
  inherited Prepare(aSQL,ExpectResults); // set fSQL + Connect if necessary
  fSQLW := Utf8DecodeToRawUnicode(fSQL);
  // 2. prepare statement and bind result columns (if any)
  AllocStatement;
  try
    ODBC.Check(nil,self,ODBC.PrepareW(fStatement,pointer(fSQLW),length(fSQLW) shr 1),
      SQL_HANDLE_STMT,fStatement);
  except
    on E: Exception do begin
      Log.Log(sllError,E);
      ODBC.FreeHandle(SQL_HANDLE_STMT,fStatement);
      fStatement := nil;
      raise;
................................................................................
    SQL_NO_DATA:
      exit;
    SQL_SUCCESS, SQL_SUCCESS_WITH_INFO: begin // ignore WITH_INFO messages
      fCurrentRow := sav+1;
      inc(fTotalRowsRetrieved);
      result := true; // mark data available for Column*() methods
    end;
    else HandleError(nil,self,status,SQL_HANDLE_STMT,fStatement,false,sllNone);
    end;
  end;
end;


{ TODBCLib }

procedure TODBCLib.Check(Conn: TSQLDBConnection; Stmt: TSQLDBStatement;
  Status: SqlReturn; HandleType: SqlSmallint; Handle: SqlHandle;
  InfoRaiseException: Boolean=false; LogLevelNoRaise: TSynLogInfo=sllNone);
begin
  if Status<>SQL_SUCCESS then
    HandleError(Conn,Stmt,Status,HandleType,Handle,InfoRaiseException,LogLevelNoRaise);
end;

constructor TODBCLib.Create;
var P: PPointer;
    i: integer;
begin
  fHandle := SafeLoadLibrary('odbc32.dll');
................................................................................

procedure TODBCLib.GetInfoString(ConnectionHandle: SqlHDbc; InfoType: SqlUSmallint;
  var Dest: RawUTF8);
var Len: SqlSmallint;
    Info: array[byte] of WideChar;
begin
  Len := 0;
  Check(nil,nil,GetInfoW(ConnectionHandle,InfoType,@Info,sizeof(Info)shr 1,@Len),
    SQL_HANDLE_DBC,ConnectionHandle);
  Dest := RawUnicodeToUtf8(Info,Len shr 1);
end;
  
procedure TODBCLib.HandleError(Conn: TSQLDBConnection; Stmt: TSQLDBStatement;
  Status: SqlReturn; HandleType: SqlSmallint; Handle: SqlHandle;
  InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
const FMT: PUTF8Char = '%[%] % (%)'#13#10;
var Sqlstate: array[0..6] of WideChar;
    MessageText: array[0..1023] of WideChar;
    RecNum, NativeError: SqlInteger;
    TextLength: SqlSmallint;
    msg: RawUTF8;
begin
................................................................................
        MessageText[textlength] := #0; // trim #13/#10 right of MessageText
      end;
      msg := FormatUTF8(FMT,[msg,Sqlstate,MessageText,NativeError]);
      inc(RecNum);
    end;
    if msg='' then
      msg := 'Unspecified error';
    if (Status=SQL_SUCCESS_WITH_INFO) and not InfoRaiseException then begin
      LogLevelNoRaise := sllInfo;
      if (Conn=nil) and (Stmt<>nil) then
        Conn := Stmt.Connection;
      if Conn<>nil then
        with Conn.Properties do
          if Assigned(OnStatementInfo) then
            OnStatementInfo(Stmt,msg);
    end;
  end;
  if LogLevelNoRaise<>sllNone then
    SynDBLog.Add.Log(LogLevelNoRaise,msg) else
    if Stmt=nil then
      raise EODBCException.CreateUTF8('% error: %',[self,msg]) else
      raise EODBCException.CreateUTF8('% - % error: %',[Stmt,self,msg]);
end;
................................................................................
    with TODBCStatement.Create(MainConnection) do
    try
      AllocStatement;
      status := ODBC.ColumnsA(fStatement,nil,0,pointer(Schema),SQL_NTS,
        pointer(Table),SQL_NTS,nil,0);
      if status<>SQL_SUCCESS then // e.g. driver does not support schema
        status := ODBC.ColumnsA(fStatement,nil,0,nil,0,pointer(Table),SQL_NTS,nil,0);
      ODBC.Check(Connection,nil,status,SQL_HANDLE_STMT,fStatement);
      BindColumns;
      FA.Init(TypeInfo(TSQLDBColumnDefineDynArray),Fields,@n);
      FA.Compare := SortDynArrayAnsiStringI; // FA.Find() case insensitive
      fillchar(F,sizeof(F),0);
      while Step do begin
        F.ColumnName := Trim(ColumnUTF8(3));
        DataType := ColumnInt(4);
................................................................................
      try
        AllocStatement;
        status := ODBC.StatisticsA(fStatement,nil,0,pointer(Schema),SQL_NTS,
          pointer(Table),SQL_NTS,SQL_INDEX_ALL,SQL_QUICK);
        if status<>SQL_SUCCESS then // e.g. driver does not support schema
          status := ODBC.StatisticsA(fStatement,nil,0,nil,0,pointer(Table),
            SQL_NTS,SQL_INDEX_ALL,SQL_QUICK);
        ODBC.Check(Connection,nil,status,SQL_HANDLE_STMT,fStatement);
        BindColumns;
        while Step do begin
          F.ColumnName := Trim(ColumnUTF8(8));
          i := FA.Find(F);
          if i>=0 then
            Fields[i].ColumnIndexed := true;
        end;
................................................................................
  inherited; // first try from SQL, if any (faster)
  if Tables<>nil then
    exit; // already retrieved directly from engine
  try
    with TODBCStatement.Create(MainConnection) do
    try
      AllocStatement;
      ODBC.Check(Connection,nil,ODBC.TablesA(fStatement,nil,0,nil,0,nil,0,'TABLE',SQL_NTS),SQL_HANDLE_STMT,fStatement);
      BindColumns;
      n := 0;
      while Step do begin
        schema := Trim(ColumnUTF8(1));
        tablename := Trim(ColumnUTF8(2));
        if schema<>'' then
          tablename := schema+'.'+tablename;
................................................................................

procedure TODBCConnectionProperties.GetForeignKeys;
begin
  try
    with TODBCStatement.Create(MainConnection) do
    try
      AllocStatement;
      ODBC.Check(Connection,nil,
        ODBC.ForeignKeysA(fStatement,nil,0,nil,0,nil,0,nil,0,nil,0,'%',SQL_NTS),
        SQL_HANDLE_STMT,fStatement);
      BindColumns;
      while Step do 
        fForeignKeys.Add(
          Trim(ColumnUTF8(5))+'.'+Trim(ColumnUTF8(6))+'.'+Trim(ColumnUTF8(7)),
          Trim(ColumnUTF8(1))+'.'+Trim(ColumnUTF8(2))+'.'+Trim(ColumnUTF8(3)));
    finally

Changes to SynDBOracle.pas.

1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
....
1400
1401
1402
1403
1404
1405
1406

1407
1408
1409
1410
1411
1412
1413
1414
....
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
1481
1482
1483

1484
1485
1486
1487
1488
1489
1490
1491
1492
1493

1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507

1508
1509
1510
1511







1512
1513
1514
1515
1516
1517
1518
....
1524
1525
1526
1527
1528
1529
1530
1531

1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
....
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
....
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
....
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
....
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
....
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
....
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
....
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
....
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
....
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
....
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
....
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
....
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
....
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
....
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
....
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
....
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
....
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
....
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
....
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
    'OCINumberFromInt','OCIStringAssignText', 'OCICollAppend', 'OCIBindObject');

type
  /// direct access to the native Oracle Client Interface (OCI)
  TSQLDBOracleLib = class(TSQLDBLib)
  protected
    fLibraryPath: TFileName;
    procedure HandleError(Stmt: TSQLDBStatement; Status: Integer;
      ErrorHandle: POCIError; InfoRaiseException: Boolean=false;
      LogLevelNoRaise: TSynLogInfo=sllNone);
    procedure RetrieveVersion;
  public
    ClientVersion: function(var major_version, minor_version,
      update_num, patch_num, port_update_num: sword): sword; cdecl;
    EnvNlsCreate: function(var envhpp: pointer; mode: ub4; ctxp: Pointer;
      malocfp: Pointer; ralocfp: Pointer; mfreefp: Pointer; xtramemsz: size_T;
................................................................................
    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: cardinal): cardinal;
    /// raise an exception on error

    procedure Check(Stmt: TSQLDBStatement; Status: Integer; ErrorHandle: POCIError;
      InfoRaiseException: Boolean=false; LogLevelNoRaise: TSynLogInfo=sllNone);
      {$ifdef HASINLINE} inline; {$endif}
    /// retrieve some BLOB content
    procedure BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
      errhp: POCIError; locp: POCIDescriptor; out result: RawByteString); overload;
    /// retrieve some BLOB content
    procedure BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
................................................................................
  end;
end;

procedure TSQLDBOracleLib.BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
  errhp: POCIError; locp: POCIDescriptor; out result: RawByteString);
var Len, Read: ub4;
begin
  Check(Stmt,LobOpen(svchp,errhp,locp,OCI_LOB_READONLY),errhp);
  try
    Len := 0;
    Check(Stmt,LobGetLength(svchp,errhp,locp,Len),errhp);
    SetLength(result,Len);
    if Len>0 then begin
      Read := Len;

      Check(Stmt,LobRead(svchp,errhp,locp,Read,1,pointer(result),Read),errhp);
      if Read<>Len then
        raise ESQLDBOracle.Create('LOB read error');
    end;
  finally
    Check(Stmt,LobClose(svchp,errhp,locp),errhp);
  end;
end;

procedure TSQLDBOracleLib.BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
  errhp: POCIError; locp: POCIDescriptor; out result: TBytes);
var Len, Read: ub4;
begin
  Check(Stmt,LobOpen(svchp,errhp,locp,OCI_LOB_READONLY),errhp);
  try
    Len := 0;
    Check(Stmt,LobGetLength(svchp,errhp,locp,Len),errhp);
    SetLength(result,Len);
    if Len>0 then begin
      Read := Len;

      Check(Stmt,LobRead(svchp,errhp,locp,Read,1,pointer(result),Read),errhp);
      if Read<>Len then
        raise ESQLDBOracle.Create('LOB read error');
    end;
  finally
    Check(Stmt,LobClose(svchp,errhp,locp),errhp);
  end;
end;

procedure TSQLDBOracleLib.ClobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
  errhp: POCIError; locp: POCIDescriptor; ColumnDBForm: integer;
  out result: RawUTF8);
var Len,Read: ub4;
begin
  Check(Stmt,LobOpen(svchp,errhp,locp,OCI_LOB_READONLY),errhp);
  try
    Len := 0;
    Check(Stmt,LobGetLength(svchp,errhp,locp,Len),errhp);
    if Len>0 then begin
      Len := Len*3; // max UTF-8 size according to number of characters
      SetLength(result,Len);
      Read := Len;

      Check(Stmt,LobRead(svchp,errhp,locp,Read,1,pointer(result),Read,nil,nil,
        OCI_UTF8,ColumnDBForm),errhp);
      SetLength(result,Read);
    end;
  finally
    Check(Stmt,LobClose(svchp,errhp,locp),errhp);
  end;
end;

procedure TSQLDBOracleLib.HandleError(Stmt: TSQLDBStatement; Status: Integer;

  ErrorHandle: POCIError; InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
var msg: RawUTF8;
    tmp: array[0..3071] of AnsiChar;
    L, ErrNum: integer;
begin
  case Status of
    OCI_ERROR, OCI_SUCCESS_WITH_INFO: begin
      tmp[0] := #0;
      ErrorGet(ErrorHandle,1,nil,ErrNum,tmp,sizeof(tmp),OCI_HTYPE_ERROR);
      L := SynCommons.StrLen(@tmp);
      while (L>0) and (tmp[L-1]<' ') do begin
        tmp[L-1] := #0; // trim right #10
        dec(L);
      end;

      if (Status=OCI_SUCCESS_WITH_INFO) and not InfoRaiseException then
        if LogLevelNoRaise=sllNone then // may be e.g. sllWarning 
          LogLevelNoRaise := sllInfo;
      msg := CurrentAnsiConvert.AnsiBufferToRawUTF8(tmp,L);







    end;
    OCI_NEED_DATA:
      msg := 'OCI_NEED_DATA';
    OCI_NO_DATA:
      msg := 'OCI_NO_DATA';
    OCI_INVALID_HANDLE:
      msg := 'OCI_INVALID_HANDLE';
................................................................................
  if LogLevelNoRaise<>sllNone then
    SynDBLog.Add.Log(LogLevelNoRaise,msg) else
    if Stmt=nil then
      raise ESQLDBOracle.CreateUTF8('% error: %',[self,msg]) else
      raise ESQLDBOracle.CreateUTF8('% error: %',[Stmt,msg]);
end;

procedure TSQLDBOracleLib.Check(Stmt: TSQLDBStatement; Status: Integer;

  ErrorHandle: POCIError; InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
begin
  if Status<>OCI_SUCCESS then
    HandleError(Stmt,Status,ErrorHandle,InfoRaiseException,LogLevelNoRaise);
end;

function TSQLDBOracleLib.ClientRevision: RawUTF8;
const EXE_FMT: PUTF8Char = '% rev. %.%.%.%';
begin
  if self=nil then
    result := '' else begin
................................................................................
{ TSQLDBOracleConnection }

procedure TSQLDBOracleConnection.Commit;
begin
  inherited;
  if fTrans=nil then
    raise ESQLDBOracle.CreateUTF8('Invalid %.Commit call',[self]);
  OCI.Check(nil,OCI.TransCommit(fContext,fError,OCI_DEFAULT),fError);
end;

procedure TSQLDBOracleConnection.Connect;
var Log: ISynLog;
    Props: TSQLDBOracleConnectionProperties;
    mode: ub4;
const
................................................................................
      // 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(nil,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);
    HandleAlloc(fEnv,fSession,OCI_HTYPE_SESSION);
    AttrSet(fSession,OCI_HTYPE_SESSION,pointer(Props.UserID),
      length(Props.UserID),OCI_ATTR_USERNAME,fError);
    AttrSet(fSession,OCI_HTYPE_SESSION,pointer(Props.Password),
................................................................................
    AttrSet(fContext,OCI_HTYPE_SVCCTX,fTrans,0,OCI_ATTR_TRANS,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(nil,SessionBegin(fContext,fError,fSession,OCI_CRED_RDBMS,mode),fError);
    Check(nil,TypeByName(fEnv,fError,fContext,Pointer(type_owner_name),length(type_owner_name),
      Pointer(type_NymberListName),length(type_NymberListName),nil,0,OCI_DURATION_SESSION,OCI_TYPEGET_HEADER,
      fType_numList),fError);
    Check(nil,TypeByName(fEnv,fError,fContext,Pointer(type_owner_name),length(type_owner_name),
      Pointer(type_Varchar2ListName),length(type_Varchar2ListName),nil,0,OCI_DURATION_SESSION,OCI_TYPEGET_HEADER,
      fType_strList),fError);
    if fOCICharSet=0 then begin
      // retrieve the charset to be used for inlined CHAR / VARCHAR2 fields
      with NewStatement do
      try
        try
................................................................................
    if (fError<>nil) and (OCI<>nil) then
    with OCI do begin
      SynDBLog.Enter(self);
      if fTrans<>nil then begin
        // close any opened session
        HandleFree(fTrans,OCI_HTYPE_TRANS);
        fTrans := nil;
        Check(nil,SessionEnd(fContext,fError,fSession,OCI_DEFAULT),fError,false,sllError);
        Check(nil,ServerDetach(fServer,fError,OCI_DEFAULT),fError,false,sllError);
      end;
      HandleFree(fSession,OCI_HTYPE_SESSION);
      HandleFree(fContext,OCI_HTYPE_SVCCTX);
      HandleFree(fServer,OCI_HTYPE_SERVER);
      HandleFree(fError,OCI_HTYPE_ERROR);
      fSession := nil;
      fContext := nil;
................................................................................
end;

procedure TSQLDBOracleConnection.Rollback;
begin
  inherited;
  if fTrans=nil then
    raise ESQLDBOracle.CreateUTF8('Invalid %.RollBack call',[self]);
  OCI.Check(nil,OCI.TransRollback(fContext,fError,OCI_DEFAULT),fError);
end;

procedure TSQLDBOracleConnection.StartTransaction;
begin
  if TransactionCount>0 then
    raise ESQLDBOracle.CreateUTF8('Invalid %.StartTransaction: nested '+
      'transactions are supported by the Oracle driver',[self]);
  inherited StartTransaction;               
  if fTrans=nil then
    raise ESQLDBOracle.CreateUTF8('Invalid %.StartTransaction call',[self]);
  // Oracle creates implicit transactions, and we'll handle AutoCommit in
  // TSQLDBOracleStatement.ExecutePrepared if TransactionCount=0
  OCI.Check(nil,OCI.TransStart(fContext,fError,0,OCI_DEFAULT),fError);
end;

procedure TSQLDBOracleConnection.STRToUTF8(P: PAnsiChar; var result: RawUTF8;
  ColumnDBCharSet, ColumnDBForm: cardinal);
var L: integer;
begin
  L := StrLen(PUTF8Char(P));
................................................................................
  OCI_SUCCESS:
    fRowFetched := fRowCount; // all rows successfully retrieved
  OCI_NO_DATA: begin
    OCI.AttrGet(fStatement,OCI_HTYPE_STMT,@fRowFetched,nil,OCI_ATTR_ROWS_FETCHED,fError);
    fRowFetchedEnded := true;
  end;
  else
    OCI.Check(self,Status,fError); // will raise error
  end;
  fRowFetchedCurrent := 0;
end;

type
  TSQLT_VNU = array[0..21] of byte;
  PSQLT_VNU = ^TSQLT_VNU;
................................................................................
            move(Pointer(PtrInt(VArray[j])-sizeof(Integer))^,oDataSTR^,
              length(VArray[j])+sizeof(integer));
            inc(oDataSTR,oLength);
          end;
        end;
        end;
        oBind := nil;
        OCI.Check(self,OCI.BindByPos(fStatement,oBind,fError,i+1,oData,oLength,VDBType,
          pointer(aIndicator[i]),nil,nil,0,nil,OCI_DEFAULT),fError);
      end;
      fRowCount := fParamsArrayCount; // set iters count for OCI.StmtExecute()
    end else begin
      // 1.2. One row DML optimized binding
      fillchar(Int32,sizeof(Int32),0);
      SetLength(oIndicator,fParamCount);
................................................................................
        else
          Type_List := nil;
        end;
        if Type_List=nil then
         raise ESQLDBOracle.CreateUTF8(
            '%.ExecutePrepared: Unsupported array parameter type #%',[self,i+1]);
        ociArrays[ociArraysCount] := nil;
        OCI.Check(self,OCI.ObjectNew(Env, fError, Context, OCI_TYPECODE_VARRAY, Type_List, nil,
          OCI_DURATION_SESSION, True, ociArrays[ociArraysCount]), fError);
        inc(ociArraysCount);
        SetString(fParams[i].VData,nil,Length(fParams[i].VArray)*sizeof(Int64));
        oData := pointer(fParams[i].VData);
        for j := 0 to Length(fParams[i].VArray)-1 do
          case fParams[i].VType of
          ftInt64: begin
            SetInt64(pointer(fParams[i].Varray[j]),oDataINT^[j]);
            OCI.Check(self,OCI.NumberFromInt(fError, @oDataINT[j], sizeof(Int64), OCI_NUMBER_SIGNED, num_val), fError);
            OCI.Check(self,OCI.CollAppend(Env, fError, @num_val, nil, ociArrays[ociArraysCount-1]),fError);
          end;
          ftUTF8: begin
            str_val := nil;
            SynCommons.UnQuoteSQLStringVar(pointer(fParams[i].VArray[j]),tmp);
            OCI.Check(self,OCI.StringAssignText(Env, fError, pointer(tmp), length(tmp), str_val), fError);
            OCI.Check(self,OCI.CollAppend(Env, fError, str_val, nil, ociArrays[ociArraysCount-1]),fError);
          end;
          end;
        oBind := nil;
        OCI.Check(self,OCI.BindByPos(fStatement,oBind,fError,i+1,nil,0,SQLT_NTY,
          nil,nil,nil,0,nil,OCI_DEFAULT),fError);
        OCI.BindObject(oBind,fError,Type_List, ociArrays[ociArraysCount-1], nil, nil, nil);
      end else
      // 1.2.2. Bind one simple parameter value
      with fParams[i] do begin
        if VType=ftNull then begin
          oIndicator[i] := -1; // assign a NULL to the column, ignoring input value
................................................................................
          case VType of
          ftUnknown: begin
            if VInOut=paramIn then
              raise ESQLDBOracle.CreateUTF8(
                '%.ExecutePrepared: Unexpected IN cursor parameter #%',[self,i+1]); 
            VDBType := SQLT_RSET;
            with OCI do
              Check(self,HandleAlloc(Env,PPointer(oData)^,OCI_HTYPE_STMT,0,nil),fError);
            oLength := sizeof(pointer);
          end;
          ftInt64:
            if OCI.SupportsInt64Params then
              // starting with 11.2, OCI supports NUMBER conversion to/from Int64
              VDBType := SQLT_INT else
              // before 11.2, we will use either SQLT_INT, SQLT_STR or SQLT_FLT
................................................................................
          end;
          else
            raise ESQLDBOracle.CreateUTF8(
              '%.ExecutePrepared: Invalid parameter #% type=%',[self,i+1,ord(VType)]);
          end;
        end;
        oBind := nil;
        OCI.Check(self,OCI.BindByPos(fStatement,oBind,fError,i+1,oData,oLength,
          VDBType,@oIndicator[i],nil,nil,0,nil,OCI_DEFAULT),fError);
      end;
    end;
    // 2. execute prepared statement
    if (fColumnCount=0) and (Connection.TransactionCount=0) then
      // for INSERT/UPDATE/DELETE without a transaction: AutoCommit after execution
      mode := OCI_COMMIT_ON_SUCCESS else
................................................................................
      mode := OCI_DEFAULT;
    Status := OCI.StmtExecute((Connection as TSQLDBOracleConnection).fContext,
      fStatement,fError,fRowCount,0,nil,nil,mode);
    FetchTest(Status); // error + set fRowCount+fCurrentRow+fRowFetchedCurrent
    Status := OCI_SUCCESS; // mark OK for fBoundCursor[] below
  finally
    for i := 0 to ociArraysCount-1 do
      OCI.Check(self,OCI.ObjectFree(Env, fError, ociArrays[i], OCI_OBJECTFREE_FORCE), fError);
    // 3. release and/or retrieve OUT bound parameters
    if fParamsArrayCount>0 then
    for i := 0 to fParamCount-1 do
      fParams[i].VData := '' else
    for i := 0 to fParamCount-1 do
    with fParams[i] do
    case VType of
................................................................................
begin
  fRowFetched := 0;
  case Status of
    OCI_SUCCESS, OCI_SUCCESS_WITH_INFO: begin
      if fColumnCount<>0 then
        fRowFetched := fRowCount;
      if Status = OCI_SUCCESS_WITH_INFO then
        OCI.Check(self,Status,fError,false,sllWarning);
    end;
    OCI_NO_DATA: begin
      assert(fColumnCount<>0);
      OCI.AttrGet(fStatement,OCI_HTYPE_STMT,@fRowFetched,nil,OCI_ATTR_ROWS_FETCHED,fError);
      fRowFetchedEnded := true;
    end;
    else OCI.Check(self,Status,fError); // will raise error
  end;
  if fRowFetched=0 then begin
    fRowCount := 0;
    fCurrentRow := -1; // no data
  end else begin
    fCurrentRow := 0; // mark cursor on the first row
    fRowFetchedCurrent := 0;
................................................................................
end;

function TSQLDBOracleStatement.DateTimeToDescriptor(aDateTime: TDateTime): pointer;
var HH,MM,SS,MS,Y,M,D: word;
    env: pointer;
begin
  env := (Connection as TSQLDBOracleConnection).fEnv;
  OCI.Check(self,OCI.DescriptorAlloc(env,result,OCI_DTYPE_TIMESTAMP,0,nil),fError);
  DecodeDate(aDateTime,Y,M,D);
  if Frac(aDateTime)=0 then begin
    HH := 0; MM := 0; SS := 0;
  end else
    DecodeTime(aDateTime,HH,MM,SS,MS);
  OCI.Check(nil,OCI.DateTimeConstruct(env,fError,result,Y,M,D,HH,MM,SS,0,nil,0),fError);
end;

procedure TSQLDBOracleStatement.FreeHandles(AfterError: boolean);
const // see http://gcov.php.net/PHP_5_3/lcov_html/ext/oci8/oci8_statement.c.gcov.php
  RELEASE_MODE: array[boolean] of integer = (OCI_DEFAULT,OCI_STMTCACHE_DELETE);
var i,j: integer;
    P: PPointer;
................................................................................
    for i := 0 to high(fBoundCursor) do
      if fBoundCursor[i]<>nil then
        OCI.HandleFree(fBoundCursor[i],OCI_HTYPE_STMT);
    fBoundCursor := nil;
  end;
  if fStatement<>nil then begin
    if fUseServerSideStatementCache then
      OCI.Check(self,OCI.StmtRelease(fStatement,fError,nil,0,RELEASE_MODE[AfterError]),fError) else
      OCI.HandleFree(fStatement,OCI_HTYPE_STMT);
    fStatement := nil;
  end;
  if fError<>nil then begin
    OCI.HandleFree(fError,OCI_HTYPE_ERROR);
    fError := nil;
  end;
................................................................................
    raise ESQLDBOracle.CreateUTF8('%: prepare StatementType=% with ExpectResults=%',
        [self,StatementType,ord(fExpectResults)]);
    if not fExpectResults then begin
      fRowCount := 1; // iters=1 by default
      exit; // no row data expected -> leave fColumnCount=0
    end;
    // 2. retrieve rows column types
    Check(self,StmtExecute(TSQLDBOracleConnection(Connection).fContext,fStatement,fError,
      1,0,nil,nil,OCI_DESCRIBE_ONLY),fError);
    ColCount := 0;
    AttrGet(fStatement,OCI_HTYPE_STMT,@ColCount,nil,OCI_ATTR_PARAM_COUNT,fError);
    RowSize := ColCount*sizeof(sb2); // space for indicators
    ColumnLongTypes := [];
    for i := 1 to ColCount do begin
      oHandle := nil;
................................................................................
          include(ColumnLongTypes,hasCURS);
        end;
        else raise ESQLDBOracle.CreateUTF8('% - Column "%": unknown type %',
          [self,ColumnName,oType]);
        end;
        inc(RowSize,ColumnValueDBSize);
        if ColumnType=ftUTF8 then begin
          Check(self,AttrGet(oHandle,OCI_DTYPE_PARAM,@ColumnValueDBForm,nil,
            OCI_ATTR_CHARSET_FORM,fError),fError);
          Check(self,AttrGet(oHandle,OCI_DTYPE_PARAM,@ColumnValueDBCharSet,nil,
             OCI_ATTR_CHARSET_ID,fError),fError);
          case ColumnValueDBForm of
          SQLCS_IMPLICIT: begin
            oCharSet := TSQLDBOracleConnection(Connection).fOCICharSet;
            if ColumnValueDBCharSet=SQLCS_IMPLICIT then
              ColumnValueDBCharSet := oCharSet else
              if (ColumnValueDBCharSet<>oCharSet) and
................................................................................
    if fRowCount=0 then begin // reserve space for at least one row of data
      fInternalBufferSize := RowSize+ColCount shl 4;
      fRowCount := 1;
    end else
    if (TSQLDBOracleConnectionProperties(Connection.Properties).RowsPrefetchSize>1024)
       and (ColumnLongTypes=[]) then begin // prefetching if no LOB nor LONG column(s)
      Prefetch := 0; // set prefetch by Memory, not by row count
      Check(self,AttrSet(fStatement,OCI_HTYPE_STMT,@Prefetch,0,OCI_ATTR_PREFETCH_ROWS,fError),fError);
      Prefetch := TSQLDBOracleConnectionProperties(Connection.Properties).RowsPrefetchSize;
      Check(self,AttrSet(fStatement,OCI_HTYPE_STMT,@Prefetch,0,OCI_ATTR_PREFETCH_MEMORY,fError),fError);
    end;
    Setlength(fRowBuffer,fInternalBufferSize);
    assert(fRowCount>0);
    if ((hasLOB in ColumnLongTypes) or (hasCURS in ColumnLongTypes)) and
       (fRowCount>100) then
      fRowCount := 100; // do not create too much POCILobLocator items
    fRowBufferCount := fRowCount; // fRowCount may be set to 0: avoid leaking
................................................................................
      RowSize := ((RowSize-1) shr 3+1)shl 3; // 8 bytes Col*[] alignment
      ColumnAttr := RowSize;
      if not ColumnValueInlined then begin
        PP := @fRowBuffer[RowSize]; // first POCILobLocator item
        for j := 1 to fRowBufferCount do begin
          case ColumnValueDBType of
          SQLT_CLOB, SQLT_BLOB:
            Check(self,DescriptorAlloc(Env,PP^,OCI_DTYPE_LOB,0,nil),fError);
          SQLT_RSET:
            Check(self,HandleAlloc(Env,PP^,OCI_HTYPE_STMT,0,nil),fError);
          else raise ESQLDBOracle.CreateUTF8('%: Wrong % type for %',
            [self,ColumnValueDBType,ColumnName]);
          end;
          inc(PP);
        end;
      end;
      oDefine := nil;
      Check(self,DefineByPos(fStatement,oDefine,fError,i+1,@fRowBuffer[RowSize],
        ColumnValueDBSize,ColumnValueDBType,Indicators,nil,nil,OCI_DEFAULT),fError);
      case ColumnType of
      ftCurrency: // currency content is returned as SQLT_STR
        Check(self,AttrSet(oDefine,OCI_HTYPE_DEFINE,@CHARSET_WIN1252,0,OCI_ATTR_CHARSET_ID,fError),fError);
      ftUTF8:
        case ColumnValueDBForm of
        SQLCS_IMPLICIT: // force CHAR + VARCHAR2 inlined fields charset
          // -> a conversion into UTF-8 will probably truncate the inlined result
          Check(self,AttrSet(oDefine,OCI_HTYPE_DEFINE,@ColumnValueDBCharSet,0,OCI_ATTR_CHARSET_ID,fError),fError);
        SQLCS_NCHAR: // NVARCHAR2 + NCLOB will be retrieved directly as UTF-8 content
          Check(self,AttrSet(oDefine,OCI_HTYPE_DEFINE,@CHARSET_UTF8,0,OCI_ATTR_CHARSET_ID,fError),fError);
        end;
      end;
      inc(RowSize,fRowBufferCount*ColumnValueDBSize);
      inc(Indicators,fRowBufferCount*sizeof(sb2));
    end;
    assert(PtrUInt(Indicators-pointer(fRowBuffer))=fRowBufferCount*ColCount*sizeof(sb2));
    assert(RowSize<=fInternalBufferSize);
................................................................................
    inherited Prepare(aSQL,ExpectResults); // set fSQL + Connect if necessary
    fPreparedParamsCount := ReplaceParamsByNames(aSQL,oSQL);
    // 2. prepare statement
    Env := (Connection as TSQLDBOracleConnection).fEnv;
    with OCI do begin
      HandleAlloc(Env,fError,OCI_HTYPE_ERROR);
      if fUseServerSideStatementCache then
        Check(self,StmtPrepare2(TSQLDBOracleConnection(Connection).fContext,fStatement,
          fError,pointer(oSQL),length(oSQL),nil,0,OCI_NTV_SYNTAX,OCI_DEFAULT),fError) else begin
        HandleAlloc(Env,fStatement,OCI_HTYPE_STMT);
        Check(self,StmtPrepare(fStatement,fError,pointer(oSQL),length(oSQL),
          OCI_NTV_SYNTAX,OCI_DEFAULT),fError);
      end;
    end;
    // 3. retrieve column information and dispatch data in row buffer
    SetColumnsForPreparedStatement;
  except
    on E: Exception do begin






|
|







 







>
|







 







|


|



>
|




|







|


|



>
|




|








|


|




>
|
|



|



|
>
|













>
|
|

<
>
>
>
>
>
>
>







 







|
>
|


|







 







|







 







|







 







|
|


|







 







|
|







 







|












|







 







|







 







|







 







|








|
|




|
|



|







 







|







 







|







 







|







 







|






|







 







|





|







 







|







 







|







 







|

|







 







|

|







 







|

|







|



|




|

|







 







|


|







1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
....
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
....
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
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
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
....
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
....
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
....
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
....
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
....
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
....
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
....
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
....
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
....
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
....
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
....
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
....
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
....
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
....
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
....
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
....
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
....
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
....
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
....
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
....
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
    'OCINumberFromInt','OCIStringAssignText', 'OCICollAppend', 'OCIBindObject');

type
  /// direct access to the native Oracle Client Interface (OCI)
  TSQLDBOracleLib = class(TSQLDBLib)
  protected
    fLibraryPath: TFileName;
    procedure HandleError(Conn: TSQLDBConnection; Stmt: TSQLDBStatement;
      Status: Integer; ErrorHandle: POCIError; InfoRaiseException: Boolean=false;
      LogLevelNoRaise: TSynLogInfo=sllNone);
    procedure RetrieveVersion;
  public
    ClientVersion: function(var major_version, minor_version,
      update_num, patch_num, port_update_num: sword): sword; cdecl;
    EnvNlsCreate: function(var envhpp: pointer; mode: ub4; ctxp: Pointer;
      malocfp: Pointer; ralocfp: Pointer; mfreefp: Pointer; xtramemsz: size_T;
................................................................................
    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: cardinal): cardinal;
    /// raise an exception on error
    procedure Check(Conn: TSQLDBConnection; Stmt: TSQLDBStatement;
      Status: Integer; ErrorHandle: POCIError;
      InfoRaiseException: Boolean=false; LogLevelNoRaise: TSynLogInfo=sllNone);
      {$ifdef HASINLINE} inline; {$endif}
    /// retrieve some BLOB content
    procedure BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
      errhp: POCIError; locp: POCIDescriptor; out result: RawByteString); overload;
    /// retrieve some BLOB content
    procedure BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
................................................................................
  end;
end;

procedure TSQLDBOracleLib.BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
  errhp: POCIError; locp: POCIDescriptor; out result: RawByteString);
var Len, Read: ub4;
begin
  Check(nil,Stmt,LobOpen(svchp,errhp,locp,OCI_LOB_READONLY),errhp);
  try
    Len := 0;
    Check(nil,Stmt,LobGetLength(svchp,errhp,locp,Len),errhp);
    SetLength(result,Len);
    if Len>0 then begin
      Read := Len;
      Check(nil,Stmt,
        LobRead(svchp,errhp,locp,Read,1,pointer(result),Read),errhp);
      if Read<>Len then
        raise ESQLDBOracle.Create('LOB read error');
    end;
  finally
    Check(nil,Stmt,LobClose(svchp,errhp,locp),errhp);
  end;
end;

procedure TSQLDBOracleLib.BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
  errhp: POCIError; locp: POCIDescriptor; out result: TBytes);
var Len, Read: ub4;
begin
  Check(nil,Stmt,LobOpen(svchp,errhp,locp,OCI_LOB_READONLY),errhp);
  try
    Len := 0;
    Check(nil,Stmt,LobGetLength(svchp,errhp,locp,Len),errhp);
    SetLength(result,Len);
    if Len>0 then begin
      Read := Len;
      Check(nil,Stmt,
        LobRead(svchp,errhp,locp,Read,1,pointer(result),Read),errhp);
      if Read<>Len then
        raise ESQLDBOracle.Create('LOB read error');
    end;
  finally
    Check(nil,Stmt,LobClose(svchp,errhp,locp),errhp);
  end;
end;

procedure TSQLDBOracleLib.ClobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
  errhp: POCIError; locp: POCIDescriptor; ColumnDBForm: integer;
  out result: RawUTF8);
var Len,Read: ub4;
begin
  Check(nil,Stmt,LobOpen(svchp,errhp,locp,OCI_LOB_READONLY),errhp);
  try
    Len := 0;
    Check(nil,Stmt,LobGetLength(svchp,errhp,locp,Len),errhp);
    if Len>0 then begin
      Len := Len*3; // max UTF-8 size according to number of characters
      SetLength(result,Len);
      Read := Len;
      Check(nil,Stmt,
        LobRead(svchp,errhp,locp,Read,1,pointer(result),Read,nil,nil,
          OCI_UTF8,ColumnDBForm),errhp);
      SetLength(result,Read);
    end;
  finally
    Check(nil,Stmt,LobClose(svchp,errhp,locp),errhp);
  end;
end;

procedure TSQLDBOracleLib.HandleError(Conn: TSQLDBConnection;
  Stmt: TSQLDBStatement; Status: Integer; ErrorHandle: POCIError;
  InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
var msg: RawUTF8;
    tmp: array[0..3071] of AnsiChar;
    L, ErrNum: integer;
begin
  case Status of
    OCI_ERROR, OCI_SUCCESS_WITH_INFO: begin
      tmp[0] := #0;
      ErrorGet(ErrorHandle,1,nil,ErrNum,tmp,sizeof(tmp),OCI_HTYPE_ERROR);
      L := SynCommons.StrLen(@tmp);
      while (L>0) and (tmp[L-1]<' ') do begin
        tmp[L-1] := #0; // trim right #10
        dec(L);
      end;
      msg := CurrentAnsiConvert.AnsiBufferToRawUTF8(tmp,L);
      if (Status=OCI_SUCCESS_WITH_INFO) and not InfoRaiseException then begin
        if LogLevelNoRaise=sllNone then // may be e.g. sllWarning
          LogLevelNoRaise := sllInfo;

        if (Conn=nil) and (Stmt<>nil) then
          Conn := Stmt.Connection;
        if Conn<>nil then
          with Conn.Properties do
            if Assigned(OnStatementInfo) then
              OnStatementInfo(Stmt,msg);
      end;
    end;
    OCI_NEED_DATA:
      msg := 'OCI_NEED_DATA';
    OCI_NO_DATA:
      msg := 'OCI_NO_DATA';
    OCI_INVALID_HANDLE:
      msg := 'OCI_INVALID_HANDLE';
................................................................................
  if LogLevelNoRaise<>sllNone then
    SynDBLog.Add.Log(LogLevelNoRaise,msg) else
    if Stmt=nil then
      raise ESQLDBOracle.CreateUTF8('% error: %',[self,msg]) else
      raise ESQLDBOracle.CreateUTF8('% error: %',[Stmt,msg]);
end;

procedure TSQLDBOracleLib.Check(Conn: TSQLDBConnection; Stmt: TSQLDBStatement;
  Status: Integer; ErrorHandle: POCIError;
  InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
begin
  if Status<>OCI_SUCCESS then
    HandleError(Conn,Stmt,Status,ErrorHandle,InfoRaiseException,LogLevelNoRaise);
end;

function TSQLDBOracleLib.ClientRevision: RawUTF8;
const EXE_FMT: PUTF8Char = '% rev. %.%.%.%';
begin
  if self=nil then
    result := '' else begin
................................................................................
{ TSQLDBOracleConnection }

procedure TSQLDBOracleConnection.Commit;
begin
  inherited;
  if fTrans=nil then
    raise ESQLDBOracle.CreateUTF8('Invalid %.Commit call',[self]);
  OCI.Check(self,nil,OCI.TransCommit(fContext,fError,OCI_DEFAULT),fError);
end;

procedure TSQLDBOracleConnection.Connect;
var Log: ISynLog;
    Props: TSQLDBOracleConnectionProperties;
    mode: ub4;
const
................................................................................
      // 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(self,nil,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);
    HandleAlloc(fEnv,fSession,OCI_HTYPE_SESSION);
    AttrSet(fSession,OCI_HTYPE_SESSION,pointer(Props.UserID),
      length(Props.UserID),OCI_ATTR_USERNAME,fError);
    AttrSet(fSession,OCI_HTYPE_SESSION,pointer(Props.Password),
................................................................................
    AttrSet(fContext,OCI_HTYPE_SVCCTX,fTrans,0,OCI_ATTR_TRANS,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(self,nil,SessionBegin(fContext,fError,fSession,OCI_CRED_RDBMS,mode),fError);
    Check(self,nil,TypeByName(fEnv,fError,fContext,Pointer(type_owner_name),length(type_owner_name),
      Pointer(type_NymberListName),length(type_NymberListName),nil,0,OCI_DURATION_SESSION,OCI_TYPEGET_HEADER,
      fType_numList),fError);
    Check(self,nil,TypeByName(fEnv,fError,fContext,Pointer(type_owner_name),length(type_owner_name),
      Pointer(type_Varchar2ListName),length(type_Varchar2ListName),nil,0,OCI_DURATION_SESSION,OCI_TYPEGET_HEADER,
      fType_strList),fError);
    if fOCICharSet=0 then begin
      // retrieve the charset to be used for inlined CHAR / VARCHAR2 fields
      with NewStatement do
      try
        try
................................................................................
    if (fError<>nil) and (OCI<>nil) then
    with OCI do begin
      SynDBLog.Enter(self);
      if fTrans<>nil then begin
        // close any opened session
        HandleFree(fTrans,OCI_HTYPE_TRANS);
        fTrans := nil;
        Check(self,nil,SessionEnd(fContext,fError,fSession,OCI_DEFAULT),fError,false,sllError);
        Check(self,nil,ServerDetach(fServer,fError,OCI_DEFAULT),fError,false,sllError);
      end;
      HandleFree(fSession,OCI_HTYPE_SESSION);
      HandleFree(fContext,OCI_HTYPE_SVCCTX);
      HandleFree(fServer,OCI_HTYPE_SERVER);
      HandleFree(fError,OCI_HTYPE_ERROR);
      fSession := nil;
      fContext := nil;
................................................................................
end;

procedure TSQLDBOracleConnection.Rollback;
begin
  inherited;
  if fTrans=nil then
    raise ESQLDBOracle.CreateUTF8('Invalid %.RollBack call',[self]);
  OCI.Check(self,nil,OCI.TransRollback(fContext,fError,OCI_DEFAULT),fError);
end;

procedure TSQLDBOracleConnection.StartTransaction;
begin
  if TransactionCount>0 then
    raise ESQLDBOracle.CreateUTF8('Invalid %.StartTransaction: nested '+
      'transactions are supported by the Oracle driver',[self]);
  inherited StartTransaction;               
  if fTrans=nil then
    raise ESQLDBOracle.CreateUTF8('Invalid %.StartTransaction call',[self]);
  // Oracle creates implicit transactions, and we'll handle AutoCommit in
  // TSQLDBOracleStatement.ExecutePrepared if TransactionCount=0
  OCI.Check(self,nil,OCI.TransStart(fContext,fError,0,OCI_DEFAULT),fError);
end;

procedure TSQLDBOracleConnection.STRToUTF8(P: PAnsiChar; var result: RawUTF8;
  ColumnDBCharSet, ColumnDBForm: cardinal);
var L: integer;
begin
  L := StrLen(PUTF8Char(P));
................................................................................
  OCI_SUCCESS:
    fRowFetched := fRowCount; // all rows successfully retrieved
  OCI_NO_DATA: begin
    OCI.AttrGet(fStatement,OCI_HTYPE_STMT,@fRowFetched,nil,OCI_ATTR_ROWS_FETCHED,fError);
    fRowFetchedEnded := true;
  end;
  else
    OCI.Check(nil,self,Status,fError); // will raise error
  end;
  fRowFetchedCurrent := 0;
end;

type
  TSQLT_VNU = array[0..21] of byte;
  PSQLT_VNU = ^TSQLT_VNU;
................................................................................
            move(Pointer(PtrInt(VArray[j])-sizeof(Integer))^,oDataSTR^,
              length(VArray[j])+sizeof(integer));
            inc(oDataSTR,oLength);
          end;
        end;
        end;
        oBind := nil;
        OCI.Check(nil,self,OCI.BindByPos(fStatement,oBind,fError,i+1,oData,oLength,VDBType,
          pointer(aIndicator[i]),nil,nil,0,nil,OCI_DEFAULT),fError);
      end;
      fRowCount := fParamsArrayCount; // set iters count for OCI.StmtExecute()
    end else begin
      // 1.2. One row DML optimized binding
      fillchar(Int32,sizeof(Int32),0);
      SetLength(oIndicator,fParamCount);
................................................................................
        else
          Type_List := nil;
        end;
        if Type_List=nil then
         raise ESQLDBOracle.CreateUTF8(
            '%.ExecutePrepared: Unsupported array parameter type #%',[self,i+1]);
        ociArrays[ociArraysCount] := nil;
        OCI.Check(nil,self,OCI.ObjectNew(Env, fError, Context, OCI_TYPECODE_VARRAY, Type_List, nil,
          OCI_DURATION_SESSION, True, ociArrays[ociArraysCount]), fError);
        inc(ociArraysCount);
        SetString(fParams[i].VData,nil,Length(fParams[i].VArray)*sizeof(Int64));
        oData := pointer(fParams[i].VData);
        for j := 0 to Length(fParams[i].VArray)-1 do
          case fParams[i].VType of
          ftInt64: begin
            SetInt64(pointer(fParams[i].Varray[j]),oDataINT^[j]);
            OCI.Check(nil,self,OCI.NumberFromInt(fError, @oDataINT[j], sizeof(Int64), OCI_NUMBER_SIGNED, num_val), fError);
            OCI.Check(nil,self,OCI.CollAppend(Env, fError, @num_val, nil, ociArrays[ociArraysCount-1]),fError);
          end;
          ftUTF8: begin
            str_val := nil;
            SynCommons.UnQuoteSQLStringVar(pointer(fParams[i].VArray[j]),tmp);
            OCI.Check(nil,self,OCI.StringAssignText(Env, fError, pointer(tmp), length(tmp), str_val), fError);
            OCI.Check(nil,self,OCI.CollAppend(Env, fError, str_val, nil, ociArrays[ociArraysCount-1]),fError);
          end;
          end;
        oBind := nil;
        OCI.Check(nil,self,OCI.BindByPos(fStatement,oBind,fError,i+1,nil,0,SQLT_NTY,
          nil,nil,nil,0,nil,OCI_DEFAULT),fError);
        OCI.BindObject(oBind,fError,Type_List, ociArrays[ociArraysCount-1], nil, nil, nil);
      end else
      // 1.2.2. Bind one simple parameter value
      with fParams[i] do begin
        if VType=ftNull then begin
          oIndicator[i] := -1; // assign a NULL to the column, ignoring input value
................................................................................
          case VType of
          ftUnknown: begin
            if VInOut=paramIn then
              raise ESQLDBOracle.CreateUTF8(
                '%.ExecutePrepared: Unexpected IN cursor parameter #%',[self,i+1]); 
            VDBType := SQLT_RSET;
            with OCI do
              Check(nil,self,HandleAlloc(Env,PPointer(oData)^,OCI_HTYPE_STMT,0,nil),fError);
            oLength := sizeof(pointer);
          end;
          ftInt64:
            if OCI.SupportsInt64Params then
              // starting with 11.2, OCI supports NUMBER conversion to/from Int64
              VDBType := SQLT_INT else
              // before 11.2, we will use either SQLT_INT, SQLT_STR or SQLT_FLT
................................................................................
          end;
          else
            raise ESQLDBOracle.CreateUTF8(
              '%.ExecutePrepared: Invalid parameter #% type=%',[self,i+1,ord(VType)]);
          end;
        end;
        oBind := nil;
        OCI.Check(nil,self,OCI.BindByPos(fStatement,oBind,fError,i+1,oData,oLength,
          VDBType,@oIndicator[i],nil,nil,0,nil,OCI_DEFAULT),fError);
      end;
    end;
    // 2. execute prepared statement
    if (fColumnCount=0) and (Connection.TransactionCount=0) then
      // for INSERT/UPDATE/DELETE without a transaction: AutoCommit after execution
      mode := OCI_COMMIT_ON_SUCCESS else
................................................................................
      mode := OCI_DEFAULT;
    Status := OCI.StmtExecute((Connection as TSQLDBOracleConnection).fContext,
      fStatement,fError,fRowCount,0,nil,nil,mode);
    FetchTest(Status); // error + set fRowCount+fCurrentRow+fRowFetchedCurrent
    Status := OCI_SUCCESS; // mark OK for fBoundCursor[] below
  finally
    for i := 0 to ociArraysCount-1 do
      OCI.Check(nil,self,OCI.ObjectFree(Env, fError, ociArrays[i], OCI_OBJECTFREE_FORCE), fError);
    // 3. release and/or retrieve OUT bound parameters
    if fParamsArrayCount>0 then
    for i := 0 to fParamCount-1 do
      fParams[i].VData := '' else
    for i := 0 to fParamCount-1 do
    with fParams[i] do
    case VType of
................................................................................
begin
  fRowFetched := 0;
  case Status of
    OCI_SUCCESS, OCI_SUCCESS_WITH_INFO: begin
      if fColumnCount<>0 then
        fRowFetched := fRowCount;
      if Status = OCI_SUCCESS_WITH_INFO then
        OCI.Check(nil,self,Status,fError,false,sllWarning);
    end;
    OCI_NO_DATA: begin
      assert(fColumnCount<>0);
      OCI.AttrGet(fStatement,OCI_HTYPE_STMT,@fRowFetched,nil,OCI_ATTR_ROWS_FETCHED,fError);
      fRowFetchedEnded := true;
    end;
    else OCI.Check(nil,self,Status,fError); // will raise error
  end;
  if fRowFetched=0 then begin
    fRowCount := 0;
    fCurrentRow := -1; // no data
  end else begin
    fCurrentRow := 0; // mark cursor on the first row
    fRowFetchedCurrent := 0;
................................................................................
end;

function TSQLDBOracleStatement.DateTimeToDescriptor(aDateTime: TDateTime): pointer;
var HH,MM,SS,MS,Y,M,D: word;
    env: pointer;
begin
  env := (Connection as TSQLDBOracleConnection).fEnv;
  OCI.Check(nil,self,OCI.DescriptorAlloc(env,result,OCI_DTYPE_TIMESTAMP,0,nil),fError);
  DecodeDate(aDateTime,Y,M,D);
  if Frac(aDateTime)=0 then begin
    HH := 0; MM := 0; SS := 0;
  end else
    DecodeTime(aDateTime,HH,MM,SS,MS);
  OCI.Check(nil,nil,OCI.DateTimeConstruct(env,fError,result,Y,M,D,HH,MM,SS,0,nil,0),fError);
end;

procedure TSQLDBOracleStatement.FreeHandles(AfterError: boolean);
const // see http://gcov.php.net/PHP_5_3/lcov_html/ext/oci8/oci8_statement.c.gcov.php
  RELEASE_MODE: array[boolean] of integer = (OCI_DEFAULT,OCI_STMTCACHE_DELETE);
var i,j: integer;
    P: PPointer;
................................................................................
    for i := 0 to high(fBoundCursor) do
      if fBoundCursor[i]<>nil then
        OCI.HandleFree(fBoundCursor[i],OCI_HTYPE_STMT);
    fBoundCursor := nil;
  end;
  if fStatement<>nil then begin
    if fUseServerSideStatementCache then
      OCI.Check(nil,self,OCI.StmtRelease(fStatement,fError,nil,0,RELEASE_MODE[AfterError]),fError) else
      OCI.HandleFree(fStatement,OCI_HTYPE_STMT);
    fStatement := nil;
  end;
  if fError<>nil then begin
    OCI.HandleFree(fError,OCI_HTYPE_ERROR);
    fError := nil;
  end;
................................................................................
    raise ESQLDBOracle.CreateUTF8('%: prepare StatementType=% with ExpectResults=%',
        [self,StatementType,ord(fExpectResults)]);
    if not fExpectResults then begin
      fRowCount := 1; // iters=1 by default
      exit; // no row data expected -> leave fColumnCount=0
    end;
    // 2. retrieve rows column types
    Check(nil,self,StmtExecute(TSQLDBOracleConnection(Connection).fContext,fStatement,fError,
      1,0,nil,nil,OCI_DESCRIBE_ONLY),fError);
    ColCount := 0;
    AttrGet(fStatement,OCI_HTYPE_STMT,@ColCount,nil,OCI_ATTR_PARAM_COUNT,fError);
    RowSize := ColCount*sizeof(sb2); // space for indicators
    ColumnLongTypes := [];
    for i := 1 to ColCount do begin
      oHandle := nil;
................................................................................
          include(ColumnLongTypes,hasCURS);
        end;
        else raise ESQLDBOracle.CreateUTF8('% - Column "%": unknown type %',
          [self,ColumnName,oType]);
        end;
        inc(RowSize,ColumnValueDBSize);
        if ColumnType=ftUTF8 then begin
          Check(nil,self,AttrGet(oHandle,OCI_DTYPE_PARAM,@ColumnValueDBForm,nil,
            OCI_ATTR_CHARSET_FORM,fError),fError);
          Check(nil,self,AttrGet(oHandle,OCI_DTYPE_PARAM,@ColumnValueDBCharSet,nil,
             OCI_ATTR_CHARSET_ID,fError),fError);
          case ColumnValueDBForm of
          SQLCS_IMPLICIT: begin
            oCharSet := TSQLDBOracleConnection(Connection).fOCICharSet;
            if ColumnValueDBCharSet=SQLCS_IMPLICIT then
              ColumnValueDBCharSet := oCharSet else
              if (ColumnValueDBCharSet<>oCharSet) and
................................................................................
    if fRowCount=0 then begin // reserve space for at least one row of data
      fInternalBufferSize := RowSize+ColCount shl 4;
      fRowCount := 1;
    end else
    if (TSQLDBOracleConnectionProperties(Connection.Properties).RowsPrefetchSize>1024)
       and (ColumnLongTypes=[]) then begin // prefetching if no LOB nor LONG column(s)
      Prefetch := 0; // set prefetch by Memory, not by row count
      Check(nil,self,AttrSet(fStatement,OCI_HTYPE_STMT,@Prefetch,0,OCI_ATTR_PREFETCH_ROWS,fError),fError);
      Prefetch := TSQLDBOracleConnectionProperties(Connection.Properties).RowsPrefetchSize;
      Check(nil,self,AttrSet(fStatement,OCI_HTYPE_STMT,@Prefetch,0,OCI_ATTR_PREFETCH_MEMORY,fError),fError);
    end;
    Setlength(fRowBuffer,fInternalBufferSize);
    assert(fRowCount>0);
    if ((hasLOB in ColumnLongTypes) or (hasCURS in ColumnLongTypes)) and
       (fRowCount>100) then
      fRowCount := 100; // do not create too much POCILobLocator items
    fRowBufferCount := fRowCount; // fRowCount may be set to 0: avoid leaking
................................................................................
      RowSize := ((RowSize-1) shr 3+1)shl 3; // 8 bytes Col*[] alignment
      ColumnAttr := RowSize;
      if not ColumnValueInlined then begin
        PP := @fRowBuffer[RowSize]; // first POCILobLocator item
        for j := 1 to fRowBufferCount do begin
          case ColumnValueDBType of
          SQLT_CLOB, SQLT_BLOB:
            Check(nil,self,DescriptorAlloc(Env,PP^,OCI_DTYPE_LOB,0,nil),fError);
          SQLT_RSET:
            Check(nil,self,HandleAlloc(Env,PP^,OCI_HTYPE_STMT,0,nil),fError);
          else raise ESQLDBOracle.CreateUTF8('%: Wrong % type for %',
            [self,ColumnValueDBType,ColumnName]);
          end;
          inc(PP);
        end;
      end;
      oDefine := nil;
      Check(nil,self,DefineByPos(fStatement,oDefine,fError,i+1,@fRowBuffer[RowSize],
        ColumnValueDBSize,ColumnValueDBType,Indicators,nil,nil,OCI_DEFAULT),fError);
      case ColumnType of
      ftCurrency: // currency content is returned as SQLT_STR
        Check(nil,self,AttrSet(oDefine,OCI_HTYPE_DEFINE,@CHARSET_WIN1252,0,OCI_ATTR_CHARSET_ID,fError),fError);
      ftUTF8:
        case ColumnValueDBForm of
        SQLCS_IMPLICIT: // force CHAR + VARCHAR2 inlined fields charset
          // -> a conversion into UTF-8 will probably truncate the inlined result
          Check(nil,self,AttrSet(oDefine,OCI_HTYPE_DEFINE,@ColumnValueDBCharSet,0,OCI_ATTR_CHARSET_ID,fError),fError);
        SQLCS_NCHAR: // NVARCHAR2 + NCLOB will be retrieved directly as UTF-8 content
          Check(nil,self,AttrSet(oDefine,OCI_HTYPE_DEFINE,@CHARSET_UTF8,0,OCI_ATTR_CHARSET_ID,fError),fError);
        end;
      end;
      inc(RowSize,fRowBufferCount*ColumnValueDBSize);
      inc(Indicators,fRowBufferCount*sizeof(sb2));
    end;
    assert(PtrUInt(Indicators-pointer(fRowBuffer))=fRowBufferCount*ColCount*sizeof(sb2));
    assert(RowSize<=fInternalBufferSize);
................................................................................
    inherited Prepare(aSQL,ExpectResults); // set fSQL + Connect if necessary
    fPreparedParamsCount := ReplaceParamsByNames(aSQL,oSQL);
    // 2. prepare statement
    Env := (Connection as TSQLDBOracleConnection).fEnv;
    with OCI do begin
      HandleAlloc(Env,fError,OCI_HTYPE_ERROR);
      if fUseServerSideStatementCache then
        Check(nil,self,StmtPrepare2(TSQLDBOracleConnection(Connection).fContext,fStatement,
          fError,pointer(oSQL),length(oSQL),nil,0,OCI_NTV_SYNTAX,OCI_DEFAULT),fError) else begin
        HandleAlloc(Env,fStatement,OCI_HTYPE_STMT);
        Check(nil,self,StmtPrepare(fStatement,fError,pointer(oSQL),length(oSQL),
          OCI_NTV_SYNTAX,OCI_DEFAULT),fError);
      end;
    end;
    // 3. retrieve column information and dispatch data in row buffer
    SetColumnsForPreparedStatement;
  except
    on E: Exception do begin

Changes to SynOleDB.pas.

2480
2481
2482
2483
2484
2485
2486

2487
2488
2489
2490
2491
2492
2493
....
2494
2495
2496
2497
2498
2499
2500
2501
2502





2503
2504
2505
2506
2507
2508
2509
function TOleDBMSSQLConnectionProperties.MSOnCustomError(Connection: TOleDBConnection;
  ErrorRecords: IErrorRecords; RecordNum: UINT): boolean;
var SQLServerErrorInfo: ISQLServerErrorInfo;
    SSErrorInfo: PSSERRORINFO;
    SSErrorMsg: PWideChar;
    msg, tmp: string;

begin
  result := False;
  if (self=nil) or (Connection=nil) then
    exit;
  ErrorRecords.GetCustomErrorObject(RecordNum,IID_ISQLServerErrorInfo,
    IUnknown(SQLServerErrorInfo));
  if Assigned(SQLServerErrorInfo) then
................................................................................
  try
    if (SQLServerErrorInfo.GetErrorInfo(SSErrorInfo,SSErrorMsg)=S_OK) and
       (SSErrorInfo<>nil) then
    with SSErrorInfo^ do
    try
      msg := UnicodeBufferToString(pwszMessage)+#13#10;
      if bClass<=10 then begin
        SynDBLog.Add.Log(sllDB,StringToUTF8(msg),self);
        Connection.fOleDBInfoMessage := Connection.fOleDBInfoMessage+msg;





      end else begin
        if pwszProcedure<>nil then
          tmp := UnicodeBufferToString(pwszProcedure) else
          tmp := 'Error '+IntToStr(lNative);
        Connection.fOleDBErrorMessage := Format('%s %s (line %d): %s',
          [Connection.fOleDBErrorMessage,tmp,wLineNumber,msg]);
      end;






>







 







<

>
>
>
>
>







2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
....
2495
2496
2497
2498
2499
2500
2501

2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
function TOleDBMSSQLConnectionProperties.MSOnCustomError(Connection: TOleDBConnection;
  ErrorRecords: IErrorRecords; RecordNum: UINT): boolean;
var SQLServerErrorInfo: ISQLServerErrorInfo;
    SSErrorInfo: PSSERRORINFO;
    SSErrorMsg: PWideChar;
    msg, tmp: string;
    utf8: RawUTF8;
begin
  result := False;
  if (self=nil) or (Connection=nil) then
    exit;
  ErrorRecords.GetCustomErrorObject(RecordNum,IID_ISQLServerErrorInfo,
    IUnknown(SQLServerErrorInfo));
  if Assigned(SQLServerErrorInfo) then
................................................................................
  try
    if (SQLServerErrorInfo.GetErrorInfo(SSErrorInfo,SSErrorMsg)=S_OK) and
       (SSErrorInfo<>nil) then
    with SSErrorInfo^ do
    try
      msg := UnicodeBufferToString(pwszMessage)+#13#10;
      if bClass<=10 then begin

        Connection.fOleDBInfoMessage := Connection.fOleDBInfoMessage+msg;
        RawUnicodeToUtf8(pwszMessage,StrLenW(pwszMessage),utf8);
        SynDBLog.Add.Log(sllDB,utf8,self);
        with Connection.Properties do
          if Assigned(OnStatementInfo) then
            OnStatementInfo(nil,utf8);
      end else begin
        if pwszProcedure<>nil then
          tmp := UnicodeBufferToString(pwszProcedure) else
          tmp := 'Error '+IntToStr(lNative);
        Connection.fOleDBErrorMessage := Format('%s %s (line %d): %s',
          [Connection.fOleDBErrorMessage,tmp,wLineNumber,msg]);
      end;

Changes to SynopseCommit.inc.

1
'1.18.1125'
|
1
'1.18.1126'