3524 lines
146 KiB
ObjectPascal
3524 lines
146 KiB
ObjectPascal
/// Oracle DB direct access classes (via OCI)
|
|
// - this unit is a part of the freeware Synopse framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit SynDBOracle;
|
|
|
|
{
|
|
This file is part of Synopse framework.
|
|
|
|
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
|
|
Synopse Informatique - https://synopse.info
|
|
|
|
*** BEGIN LICENSE BLOCK *****
|
|
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version
|
|
1.1 (the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
for the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is Synopse mORMot framework.
|
|
|
|
The Initial Developer of the Original Code is Arnaud Bouchez.
|
|
|
|
Portions created by the Initial Developer are Copyright (C) 2022
|
|
the Initial Developer. All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
- Adam Siwon (asiwon)
|
|
- richard6688
|
|
- mpv
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
in which case the provisions of the GPL or the LGPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of either the GPL or the LGPL, and not to allow others to
|
|
use your version of this file under the terms of the MPL, indicate your
|
|
decision by deleting the provisions above and replace them with the notice
|
|
and other provisions required by the GPL or the LGPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
}
|
|
|
|
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
|
|
|
|
interface
|
|
|
|
uses
|
|
{$ifdef MSWINDOWS}
|
|
Windows,
|
|
{$else}
|
|
dynlibs,
|
|
{$endif}
|
|
SysUtils,
|
|
{$ifndef DELPHI5OROLDER}
|
|
Variants,
|
|
{$endif}
|
|
Classes,
|
|
Contnrs,
|
|
SynCommons,
|
|
SynTable, // for TSynTableStatement
|
|
SynLog,
|
|
SynDB;
|
|
|
|
{ -------------- Oracle Client Interface native connection }
|
|
|
|
type
|
|
/// exception type associated to the native Oracle Client Interface (OCI)
|
|
ESQLDBOracle = class(ESQLDBException);
|
|
|
|
POracleDate = ^TOracleDate;
|
|
{$A-}
|
|
/// memory structure used to store a date and time in native Oracle format
|
|
// - follow the SQLT_DAT column type layout
|
|
{$ifdef USERECORDWITHMETHODS}TOracleDate = record
|
|
{$else}TOracleDate = object{$endif}
|
|
Cent, Year, Month, Day, Hour, Min, Sec: byte;
|
|
/// convert an Oracle date and time into Delphi TDateTime
|
|
// - this method will ignore any date before 30 Dec 1899 (i.e. any
|
|
// TDateTime result < 0), to avoid e.g. wrong DecodeTime() computation from
|
|
// retrieved value: if you need to retrieve dates before 1899, you should
|
|
// better retrieve the content using ISO-8601 text encoding
|
|
function ToDateTime: TDateTime;
|
|
/// convert an Oracle date and time into its textual expanded ISO-8601
|
|
// - will fill up to 21 characters, including double quotes
|
|
function ToIso8601(Dest: PUTF8Char): integer; overload;
|
|
/// convert an Oracle date and time into its textual expanded ISO-8601
|
|
// - return the ISO-8601 text, without double quotes
|
|
procedure ToIso8601(var aIso8601: RawByteString); overload;
|
|
/// convert Delphi TDateTime into native Oracle date and time format
|
|
procedure From(const aValue: TDateTime); overload;
|
|
/// convert textual ISO-8601 into native Oracle date and time format
|
|
procedure From(const aIso8601: RawUTF8); overload;
|
|
/// convert textual ISO-8601 into native Oracle date and time format
|
|
procedure From(aIso8601: PUTF8Char; Length: integer); overload;
|
|
end;
|
|
{$A+}
|
|
/// wrapper to an array of TOracleDate items
|
|
TOracleDateArray = array[0..(maxInt div sizeof(TOracleDate))-1] of TOracleDate;
|
|
|
|
/// event triggered when an expired password is detected
|
|
// - will allow to provide a new password
|
|
TOnPasswordExpired = function (Sender: TSQLDBConnection; var APassword: RawUTF8): Boolean of object;
|
|
|
|
/// will implement properties shared by native Oracle Client Interface connections
|
|
TSQLDBOracleConnectionProperties = class(TSQLDBConnectionPropertiesThreadSafe)
|
|
protected
|
|
fRowsPrefetchSize: Integer;
|
|
fBlobPrefetchSize: Integer;
|
|
fStatementCacheSize: integer;
|
|
fInternalBufferSize: integer;
|
|
fEnvironmentInitializationMode: integer;
|
|
fOnPasswordChanged: TNotifyEvent;
|
|
fOnPasswordExpired: TOnPasswordExpired;
|
|
fUseWallet: boolean;
|
|
fIgnoreORA01453OnStartTransaction: boolean;
|
|
function GetClientVersion: RawUTF8;
|
|
/// initialize fForeignKeys content with all foreign keys of this DB
|
|
// - used by GetForeignKey method
|
|
procedure GetForeignKeys; override;
|
|
procedure PasswordChanged(const ANewPassword: RawUTF8);
|
|
public
|
|
/// initialize the connection properties
|
|
// - we don't need a database name parameter for Oracle connection: only
|
|
// aServerName is to be set
|
|
// - you may specify the TNSName in aServerName, or a connection string
|
|
// like '//host[:port]/[service_name]', e.g. '//sales-server:1523/sales'
|
|
// - connection is opened globaly as UTF-8, to match the internal encoding
|
|
// of our units; but CHAR / NVARCHAR2 fields will use the Oracle charset
|
|
// as retrieved from the opened connection (to avoid any conversion error)
|
|
constructor Create(const aServerName, aDatabaseName, aUserID, aPassWord: RawUTF8); override;
|
|
/// create a new connection
|
|
// - call this method if the shared MainConnection is not enough (e.g. for
|
|
// multi-thread access)
|
|
// - the caller is responsible of freeing this instance
|
|
// - this overridden method will create an TSQLDBOracleConnection instance
|
|
function NewConnection: TSQLDBConnection; override;
|
|
/// extract the TNS listener name from a Oracle full connection string
|
|
// - e.g. ExtractTnsName('1.2.3.4:1521/dbname') returns 'dbname'
|
|
class function ExtractTnsName(const aServerName: RawUTF8): RawUTF8;
|
|
/// determine if the SQL statement can be cached
|
|
// - always returns false, to force server-side caching only on this driver
|
|
function IsCachable(P: PUTF8Char): boolean; override;
|
|
function SQLLimitClause(AStmt: TSynTableStatement): TSQLDBDefinitionLimitClause; override;
|
|
published
|
|
/// returns the Client version e.g. 'oci.dll rev. 11.2.0.1'
|
|
property ClientVersion: RawUTF8 read GetClientVersion;
|
|
/// the OCI initialization mode used for the connection
|
|
// - equals OCI_EVENTS or OCI_THREADED by default, since will likely be
|
|
// used in a multi-threaded context (even if this class is inheriting from
|
|
// TSQLDBConnectionPropertiesThreadSafe), and OCI_EVENTS is needed to support
|
|
// Oracle RAC Connection Load Balancing
|
|
// - can be tuned depending on the configuration or the Oracle version
|
|
property EnvironmentInitializationMode: integer
|
|
read fEnvironmentInitializationMode write fEnvironmentInitializationMode;
|
|
/// the size (in bytes) of the internal buffer used to retrieve rows in statements
|
|
// - default is 128 KB, which gives very good results
|
|
property InternalBufferSize: integer read fInternalBufferSize write fInternalBufferSize;
|
|
/// the size (in bytes) of rows data prefecth at OCI driver level
|
|
// - is set to 128 KB by default, but may be changed for tuned performance
|
|
property RowsPrefetchSize: integer read fRowsPrefetchSize write fRowsPrefetchSize;
|
|
/// the size (in bytes) of LOB prefecth
|
|
// - is set to 4096 (4 KB) by default, but may be changed for tuned performance
|
|
property BlobPrefetchSize: integer read fBlobPrefetchSize write fBlobPrefetchSize;
|
|
/// Password Expired event
|
|
property OnPasswordExpired: TOnPasswordExpired read FOnPasswordExpired write FOnPasswordExpired;
|
|
/// Password changed event
|
|
property OnPasswordChanged: TNotifyEvent read FOnPasswordChanged write FOnPasswordChanged;
|
|
/// the number of prepared statements cached by OCI on the Client side
|
|
// - is set to 30 by default
|
|
// - only used if UseCache=true
|
|
property StatementCacheSize: integer read fStatementCacheSize write fStatementCacheSize;
|
|
/// use the Secure External Password Store for Password Credentials
|
|
// - see Oracle documentation
|
|
// http://docs.oracle.com/cd/B28359_01/network.111/b28531/authentication.htm#DBSEG97906
|
|
property UseWallet: boolean read fUseWallet write fUseWallet;
|
|
/// When we execute a SELECT statement across a database link, a transaction lock is placed
|
|
// on the undo segments (transaction is implicity started).
|
|
// Setting this options to true allow to ignore ORA-01453 during
|
|
// TSQLDBOracleConnection.StartTransaction call.
|
|
// - see Oracle documentation
|
|
// http://docs.oracle.com/cd/B28359_01/server.111/b28310/ds_appdev002.htm
|
|
property IgnoreORA01453OnStartTransaction: boolean
|
|
read fIgnoreORA01453OnStartTransaction write fIgnoreORA01453OnStartTransaction;
|
|
end;
|
|
|
|
/// implements a direct connection to the native Oracle Client Interface (OCI)
|
|
TSQLDBOracleConnection = class(TSQLDBConnectionThreadSafe)
|
|
protected
|
|
fEnv: pointer;
|
|
fError: pointer;
|
|
fServer: pointer;
|
|
fContext: pointer;
|
|
fSession: pointer;
|
|
fTrans: pointer;
|
|
fOCICharSet: cardinal;
|
|
fType_numList: pointer;
|
|
fType_strList: pointer;
|
|
// match DB charset for CHAR/NVARCHAR2, nil for OCI_UTF8/OCI_AL32UTF8
|
|
fAnsiConvert: TSynAnsiConvert;
|
|
procedure STRToUTF8(P: PAnsiChar; var result: RawUTF8;
|
|
ColumnDBCharSet,ColumnDBForm: Cardinal);
|
|
{$ifndef UNICODE}
|
|
procedure STRToAnsiString(P: PAnsiChar; var result: AnsiString;
|
|
ColumnDBCharSet,ColumnDBForm: Cardinal);
|
|
{$endif}
|
|
public
|
|
/// prepare a connection to a specified Oracle database server
|
|
constructor Create(aProperties: TSQLDBConnectionProperties); override;
|
|
/// release memory and connection
|
|
destructor Destroy; override;
|
|
/// connect to the specified Oracle database server
|
|
// - should raise an Exception on error
|
|
// - the connection will be globaly opened with UTF-8 encoding; for CHAR /
|
|
// NVARCHAR2 fields, the DB charset encoding will be retrieved from the
|
|
// server, to avoid any truncation during data retrieval
|
|
// - BlobPrefetchSize, RowsPrefetchSize and StatementCacheSize field values
|
|
// of the associated properties will be used to tune the opened connection
|
|
procedure Connect; override;
|
|
/// stop connection to the specified Oracle database server
|
|
// - should raise an Exception on error
|
|
procedure Disconnect; override;
|
|
/// return TRUE if Connect has been already successfully called
|
|
function IsConnected: boolean; override;
|
|
/// initialize a new SQL query statement for the given connection
|
|
// - if UseCache=true, this overridden implementation will use server-side
|
|
// Oracle statement cache - in this case, StatementCacheSize will define
|
|
// how many statements are to be cached - not that IsCachable() has been
|
|
// overriden to return false, so statement cache on client side is disabled
|
|
// - the caller should free the instance after use
|
|
function NewStatement: TSQLDBStatement; override;
|
|
/// begin a Transaction for this connection
|
|
// - current implementation do not support nested transaction with those
|
|
// methods: exception will be raised in such case
|
|
// - by default, TSQLDBOracleStatement works in AutoCommit mode, unless
|
|
// StartTransaction is called
|
|
procedure StartTransaction; override;
|
|
/// commit changes of a Transaction for this connection
|
|
// - StartTransaction method must have been called before
|
|
procedure Commit; override;
|
|
/// discard changes of a Transaction for this connection
|
|
// - StartTransaction method must have been called before
|
|
procedure Rollback; override;
|
|
/// allows to change the password of the current connected user
|
|
// - will first launch the OnPasswordExpired event to retrieve the new
|
|
// password, then change it and call OnPasswordChanged event on success
|
|
function PasswordChange: Boolean;
|
|
end;
|
|
|
|
/// implements a statement via the native Oracle Client Interface (OCI)
|
|
// - those statements can be prepared on the Delphi side, but by default we
|
|
// enabled the OCI-side statement cache, not to reinvent the wheel this time
|
|
// - note that bound OUT ftUTF8 parameters will need to be pre-allocated
|
|
// before calling - e.g. via BindTextU(StringOfChar(3000),paramOut)
|
|
// - you can also bind an TInt64DynArray or TRawUTF8DynArray as parameter to
|
|
// be assigned later as an OCI_OBJECT so that you may write such statements:
|
|
// ! var arr: TInt64DynArray = [1, 2, 3];
|
|
// ! Query := TSQLDBOracleConnectionProperties.NewThreadSafeStatementPrepared(
|
|
// ! 'select * from table where table.id in '+
|
|
// ! '(select column_value from table(cast(? as SYS.ODCINUMBERLIST)))');
|
|
// ! Query.BindArray(1,arr);
|
|
// ! Query.ExecutePrepared;
|
|
// (use SYS.ODCIVARCHAR2LIST type cast for TRawUTF8DynArray values)
|
|
TSQLDBOracleStatement = class(TSQLDBStatementWithParamsAndColumns)
|
|
protected
|
|
fStatement: pointer;
|
|
fError: pointer;
|
|
fPreparedParamsCount: integer;
|
|
fRowCount: cardinal;
|
|
fRowBufferCount: cardinal;
|
|
fRowFetched: cardinal;
|
|
fRowFetchedCurrent: cardinal;
|
|
fRowFetchedEnded: boolean;
|
|
fRowBuffer: TByteDynArray;
|
|
fBoundCursor: array of pointer;
|
|
fInternalBufferSize: cardinal;
|
|
// warning: shall be 32 bits aligned!
|
|
fTimeElapsed: TPrecisionTimer;
|
|
fUseServerSideStatementCache: boolean;
|
|
function DateTimeToDescriptor(aDateTime: TDateTime): pointer;
|
|
procedure FreeHandles(AfterError: boolean);
|
|
procedure FetchTest(Status: integer);
|
|
/// Col=0...fColumnCount-1
|
|
function GetCol(Col: Integer; out Column: PSQLDBColumnProperty): pointer;
|
|
// called by Prepare and CreateFromExistingStatement
|
|
procedure SetColumnsForPreparedStatement;
|
|
// called by Step and CreateFromExistingStatement
|
|
procedure FetchRows;
|
|
public
|
|
/// create an OCI statement instance, from an existing OCI connection
|
|
// - the Execute method can be called once per TSQLDBOracleStatement instance,
|
|
// but you can use the Prepare once followed by several ExecutePrepared methods
|
|
// - if the supplied connection is not of TOleDBConnection type, will raise
|
|
// an exception
|
|
constructor Create(aConnection: TSQLDBConnection); override;
|
|
/// initialize the class from an existing OCI statement (and connection)
|
|
// - to be called e.g. by ColumnCursor() for SQLT_RSET kind of column
|
|
constructor CreateFromExistingStatement(aConnection: TSQLDBConnection; aStatement: pointer);
|
|
/// release all associated memory and OCI handles
|
|
destructor Destroy; override;
|
|
|
|
/// Prepare an UTF-8 encoded SQL statement
|
|
// - parameters marked as ? will be bound later, before ExecutePrepared call
|
|
// - if ExpectResults is TRUE, then Step() and Column*() methods are available
|
|
// to retrieve the data rows
|
|
// - raise an ESQLDBOracle on any error
|
|
// - if aSQL requires a trailing ';', you should end it with ';;' e.g. for
|
|
// $ DB.ExecuteNoResult(
|
|
// $ 'CREATE OR REPLACE FUNCTION ORA_POC(MAIN_TABLE IN VARCHAR2, REC_COUNT IN NUMBER, BATCH_SIZE IN NUMBER) RETURN VARCHAR2' +
|
|
// $ ' AS LANGUAGE JAVA' +
|
|
// $ ' NAME ''OraMain.selectTable(java.lang.String, int, int) return java.lang.String'';;', []);
|
|
procedure Prepare(const aSQL: RawUTF8; ExpectResults: Boolean=false); overload; override;
|
|
/// Execute a prepared SQL statement
|
|
// - parameters marked as ? should have been already bound with Bind*() functions
|
|
// - raise an ESQLDBOracle on any error
|
|
procedure ExecutePrepared; override;
|
|
|
|
/// After a statement has been prepared via Prepare() + ExecutePrepared() or
|
|
// Execute(), this method must be called one or more times to evaluate it
|
|
// - you shall call this method before calling any Column*() methods
|
|
// - return TRUE on success, with data ready to be retrieved by Column*()
|
|
// - return FALSE if no more row is available (e.g. if the SQL statement
|
|
// is not a SELECT but an UPDATE or INSERT command)
|
|
// - access the first or next row of data from the SQL Statement result:
|
|
// if SeekFirst is TRUE, will put the cursor on the first row of results,
|
|
// otherwise, it will fetch one row of data, to be called within a loop
|
|
// - raise an ESQLDBOracle on any error
|
|
function Step(SeekFirst: boolean=false): boolean; override;
|
|
/// finalize the OCI cursor resources - not implemented yet
|
|
procedure ReleaseRows; override;
|
|
/// returns TRUE if the column contains NULL
|
|
function ColumnNull(Col: integer): boolean; override;
|
|
/// return a Column integer value of the current Row, first Col is 0
|
|
function ColumnInt(Col: integer): Int64; override;
|
|
/// return a Column floating point value of the current Row, first Col is 0
|
|
function ColumnDouble(Col: integer): double; override;
|
|
/// return a Column date and time value of the current Row, first Col is 0
|
|
function ColumnDateTime(Col: integer): TDateTime; override;
|
|
/// return a Column currency value of the current Row, first Col is 0
|
|
// - should retrieve directly the 64 bit Currency content, to avoid
|
|
// any rounding/conversion error from floating-point types
|
|
function ColumnCurrency(Col: integer): currency; override;
|
|
/// return a Column UTF-8 encoded text value of the current Row, first Col is 0
|
|
function ColumnUTF8(Col: integer): RawUTF8; override;
|
|
/// return a Column as a blob value of the current Row, first Col is 0
|
|
// - ColumnBlob() will return the binary content of the field is was not ftBlob,
|
|
// e.g. a 8 bytes RawByteString for a vtInt64/vtDouble/vtDate/vtCurrency,
|
|
// or a direct mapping of the RawUnicode
|
|
function ColumnBlob(Col: integer): RawByteString; override;
|
|
/// return a Column as a blob value of the current Row, first Col is 0
|
|
// - this function will return the BLOB content as a TBytes
|
|
// - this default virtual method will call ColumnBlob()
|
|
function ColumnBlobBytes(Col: integer): TBytes; override;
|
|
/// read a blob Column into the Stream parameter
|
|
procedure ColumnBlobToStream(Col: integer; Stream: TStream); override;
|
|
/// write a blob Column into the Stream parameter
|
|
// - expected to be used with 'SELECT .. FOR UPDATE' locking statements
|
|
procedure ColumnBlobFromStream(Col: integer; Stream: TStream); override;
|
|
/// return a Column as a variant
|
|
// - this implementation will retrieve the data with no temporary variable
|
|
// (since TQuery calls this method a lot, we tried to optimize it)
|
|
// - a ftUTF8 content will be mapped into a generic WideString variant
|
|
// for pre-Unicode version of Delphi, and a generic UnicodeString (=string)
|
|
// since Delphi 2009: you may not loose any data during charset conversion
|
|
// - a ftBlob content will be mapped into a TBlobData AnsiString variant
|
|
function ColumnToVariant(Col: integer; var Value: Variant): TSQLDBFieldType; override;
|
|
/// return a Column as a TSQLVar value, first Col is 0
|
|
// - the specified Temp variable will be used for temporary storage of
|
|
// svtUTF8/svtBlob values
|
|
// - this implementation will retrieve the data with no temporary variable,
|
|
// and handling ftCurrency/NUMBER(22,0) as fast as possible, directly from
|
|
// the memory buffers returned by OCI: it will ensure best performance
|
|
// possible when called from TSQLVirtualTableCursorExternal.Column method
|
|
// as defined in mORMotDB unit (i.e. mORMot external DB access)
|
|
procedure ColumnToSQLVar(Col: Integer; var Value: TSQLVar;
|
|
var Temp: RawByteString); override;
|
|
/// append all columns values of the current Row to a JSON stream
|
|
// - will use WR.Expand to guess the expected output format
|
|
// - fast overridden implementation with no temporary variable (about 20%
|
|
// faster when run over high number of data rows)
|
|
// - BLOB field value is saved as Base64, in the '"\uFFF0base64encodedbinary"
|
|
// format and contains true BLOB data
|
|
procedure ColumnsToJSON(WR: TJSONWriter); override;
|
|
/// return a special CURSOR Column content as a SynDB result set
|
|
// - Cursors are not handled internally by mORMot, but Oracle usually use
|
|
// such structures to get data from strored procedures
|
|
// - such columns are mapped as ftUTF8, with the rows converted to JSON
|
|
// - this overridden method will allow direct access to the data rows
|
|
function ColumnCursor(Col: integer): ISQLDBRows; override;
|
|
|
|
/// bind a special CURSOR parameter to be returned as a SynDB result set
|
|
// - Cursors are not handled internally by mORMot, but some databases (e.g.
|
|
// Oracle) usually use such structures to get data from strored procedures
|
|
// - such parameters are mapped as ftUnknown, and is always of paramOut type
|
|
// - use BoundCursor() method to retrieve the corresponding ISQLDBRows after
|
|
// execution of the statement
|
|
// - this overridden method will prepare direct access to the data rows
|
|
procedure BindCursor(Param: integer); override;
|
|
/// return a special CURSOR parameter content as a SynDB result set
|
|
// - this method is not about a column, but a parameter defined with
|
|
// BindCursor() before method execution
|
|
// - Cursors are not handled internally by mORMot, but some databases (e.g.
|
|
// Oracle) usually use such structures to get data from strored procedures
|
|
// - this method allow direct access to the data rows after execution
|
|
// - this overridden method will allow direct access to the data rows
|
|
function BoundCursor(Param: Integer): ISQLDBRows; override;
|
|
|
|
/// returns the number of rows updated by the execution of this statement
|
|
function UpdateCount: integer; override;
|
|
end;
|
|
|
|
|
|
var
|
|
/// optional folder where the Oracle Client Library is to be searched
|
|
// - by default, the oci.dll library is searched in the system PATH, then
|
|
// in %ORACLE_HOME%\bin
|
|
// - you can specify here a folder name in which the oci.dll is to be found
|
|
SynDBOracleOCIpath: TFileName;
|
|
|
|
const
|
|
// defined here for overriding OCI_CHARSET_UTF8/OCI_CHARSET_WIN1252 if needed
|
|
OCI_UTF8 = $367;
|
|
OCI_AL32UTF8 = $369;
|
|
OCI_UTF16ID = 1000;
|
|
OCI_WE8MSWIN1252 = 178;
|
|
|
|
var
|
|
/// the OCI charset used for UTF-8 encoding
|
|
// - OCI_UTF8 is a deprecated encoding, and OCI_AL32UTF8 should be preferred
|
|
// - but you can fallback for OCI_UTF8 for compatibility purposes
|
|
OCI_CHARSET_UTF8: cardinal = OCI_AL32UTF8;
|
|
|
|
/// the OCI charset used for WinAnsi encoding
|
|
OCI_CHARSET_WIN1252: cardinal = OCI_WE8MSWIN1252;
|
|
|
|
/// how many blob chunks should be handled at once
|
|
SynDBOracleBlobChunksCount: integer = 250;
|
|
|
|
|
|
implementation
|
|
|
|
{ TOracleDate }
|
|
|
|
// see http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28395/oci03typ.htm#sthref389
|
|
|
|
function TOracleDate.ToDateTime: TDateTime;
|
|
begin
|
|
if (PInteger(@self)^=0) and (PInteger(PtrUInt(@self)+3)^=0) then
|
|
// Cent=Year=Month=Day=Hour=Main=Sec=0 -> returns 0
|
|
result := 0 else begin
|
|
if Cent<=100 then // avoid TDateTime values < 0 (generates wrong DecodeTime)
|
|
result := 0 else
|
|
result := EncodeDate((Cent-100)*100+Year-100,Month,Day);
|
|
if (Hour>1) or (Min>1) or (Sec>1) then
|
|
result := result+EncodeTime(Hour-1,Min-1,Sec-1,0);
|
|
end;
|
|
end;
|
|
|
|
procedure TOracleDate.ToIso8601(var aIso8601: RawByteString);
|
|
var tmp: array[0..23] of AnsiChar;
|
|
begin
|
|
if (PInteger(@self)^=0) and (PInteger(PtrUInt(@self)+3)^=0) then
|
|
// Cent=Year=Month=Day=Hour=Main=Sec=0 -> stored as ""
|
|
aIso8601 := '' else begin
|
|
DateToIso8601PChar(tmp,true,(Cent-100)*100+Year-100,Month,Day);
|
|
if (Hour>1) or (Min>1) or (Sec>1) then begin
|
|
TimeToIso8601PChar(@tmp[10],true,Hour-1,Min-1,Sec-1,0,'T');
|
|
SetString(aIso8601,tmp,19); // we use 'T' as TTextWriter.AddDateTime
|
|
end else
|
|
SetString(aIso8601,tmp,10); // only date
|
|
end;
|
|
end;
|
|
|
|
function TOracleDate.ToIso8601(Dest: PUTF8Char): integer;
|
|
var Y: cardinal;
|
|
begin
|
|
Dest^ := '"';
|
|
if (PInteger(@self)^=0) and (PInteger(PtrUInt(@self)+3)^=0) then
|
|
// Cent=Year=Month=Day=Hour=Main=Sec=0 -> stored as ""
|
|
result := 2 else begin
|
|
Y := (Cent-100)*100+Year-100;
|
|
if Y>9999 then // avoid integer overflow -> stored as ""
|
|
result := 2 else begin
|
|
DateToIso8601PChar(Dest+1,true,Y,Month,Day);
|
|
if (Hour>1) or (Min>1) or (Sec>1) then begin
|
|
TimeToIso8601PChar(Dest+11,true,Hour-1,Min-1,Sec-1,0,'T');
|
|
result := 21; // we use 'T' as TTextWriter.AddDateTime
|
|
end else
|
|
result := 12; // only date
|
|
end;
|
|
end;
|
|
Dest[result-1] := '"';
|
|
end;
|
|
|
|
procedure TOracleDate.From(const aValue: TDateTime);
|
|
var T: TSynSystemTime;
|
|
begin
|
|
if aValue<=0 then begin
|
|
PInteger(@self)^ := 0;
|
|
PInteger(PtrUInt(@self)+3)^ := 0; // set Day=Hour=Min=Sec to 0
|
|
exit; // supplied TDateTime value = 0 -> store as null date
|
|
end;
|
|
T.FromDateTime(aValue);
|
|
Cent := (T.Year div 100)+100;
|
|
Year := (T.Year mod 100)+100;
|
|
Month := T.Month;
|
|
Day := T.Day;
|
|
if (T.Hour<>0) or (T.Minute<>0) or (T.Second<>0) then begin
|
|
Hour := T.Hour+1;
|
|
Min := T.Minute+1;
|
|
Sec := T.Second+1;
|
|
end else begin
|
|
Hour := 1;
|
|
Min := 1;
|
|
Sec := 1;
|
|
end;
|
|
end;
|
|
|
|
procedure TOracleDate.From(const aIso8601: RawUTF8);
|
|
begin
|
|
From(pointer(aIso8601),length(aIso8601));
|
|
end;
|
|
|
|
procedure TOracleDate.From(aIso8601: PUTF8Char; Length: integer);
|
|
var Value: QWord;
|
|
Value32: cardinal absolute Value;
|
|
Y: cardinal;
|
|
NoTime: boolean;
|
|
begin
|
|
Value := Iso8601ToTimeLogPUTF8Char(aIso8601,Length,@NoTime);
|
|
if Value=0 then begin
|
|
PInteger(@self)^ := 0;
|
|
PInteger(PtrUInt(@self)+3)^ := 0; // set Day=Hour=Min=Sec to 0
|
|
exit; // invalid ISO-8601 text -> store as null date
|
|
end;
|
|
Y := Value shr (6+6+5+5+4);
|
|
Cent := (Y div 100)+100;
|
|
Year := (Y mod 100)+100;
|
|
Month := ((Value32 shr (6+6+5+5)) and 15)+1;
|
|
Day := ((Value32 shr (6+6+5)) and 31)+1;
|
|
if NoTime then begin
|
|
Hour := 1;
|
|
Min := 1;
|
|
Sec := 1;
|
|
exit;
|
|
end;
|
|
Hour := ((Value32 shr (6+6)) and 31)+1;
|
|
Min := ((Value32 shr 6) and 63)+1;
|
|
Sec := (Value32 and 63)+1;
|
|
end;
|
|
|
|
|
|
{ Native OCI access interface }
|
|
|
|
type
|
|
{ Generic Oracle Types }
|
|
sword = Integer;
|
|
eword = Integer;
|
|
uword = LongInt;
|
|
sb4 = Integer;
|
|
ub4 = LongInt;
|
|
sb2 = SmallInt;
|
|
ub2 = Word;
|
|
sb1 = ShortInt;
|
|
ub1 = Byte;
|
|
dvoid = Pointer;
|
|
text = PAnsiChar;
|
|
OraText = PAnsiChar;
|
|
size_T = PtrUInt;
|
|
|
|
pub1 = ^ub1;
|
|
psb1 = ^sb1;
|
|
pub2 = ^ub2;
|
|
psb2 = ^sb2;
|
|
pub4 = ^ub4;
|
|
psb4 = ^sb4;
|
|
pdvoid = ^dvoid;
|
|
|
|
{ Handle Types }
|
|
POCIHandle = Pointer;
|
|
PPOCIHandle = ^Pointer;
|
|
POCIEnv = POCIHandle;
|
|
POCIServer = POCIHandle;
|
|
POCIError = POCIHandle;
|
|
POCISvcCtx = POCIHandle;
|
|
POCIStmt = POCIHandle;
|
|
POCIDefine = POCIHandle;
|
|
POCISession = POCIHandle;
|
|
POCIBind = POCIHandle;
|
|
POCIDescribe = POCIHandle;
|
|
POCITrans = POCIHandle;
|
|
|
|
{ Descriptor Types }
|
|
POCIDescriptor = Pointer;
|
|
PPOCIDescriptor = ^POCIDescriptor;
|
|
POCISnapshot = POCIDescriptor;
|
|
POCILobLocator = POCIDescriptor;
|
|
POCIParam = POCIDescriptor;
|
|
POCIRowid = POCIDescriptor;
|
|
POCIComplexObjectComp = POCIDescriptor;
|
|
POCIAQEnqOptions = POCIDescriptor;
|
|
POCIAQDeqOptions = POCIDescriptor;
|
|
POCIAQMsgProperties = POCIDescriptor;
|
|
POCIAQAgent = POCIDescriptor;
|
|
POCIDate = POCIDescriptor;
|
|
POCIDateTime = POCIDescriptor;
|
|
POCIString = POCIDescriptor;
|
|
POCIType = POCIDescriptor;
|
|
POCIArray = POCIDescriptor;
|
|
POCIColl = POCIDescriptor;
|
|
|
|
/// OCIDuration - OCI object duration
|
|
// - A client can specify the duration of which an object is pinned (pin
|
|
// duration) and the duration of which the object is in memory (allocation
|
|
// duration). If the objects are still pinned at the end of the pin duration,
|
|
// the object cache manager will automatically unpin the objects for the
|
|
// client. If the objects still exist at the end of the allocation duration,
|
|
// the object cache manager will automatically free the objects for the client.
|
|
// - Objects that are pinned with the option OCI_DURATION_TRANS will get unpinned
|
|
// automatically at the end of the current transaction.
|
|
// - Objects that are pinned with the option OCI_DURATION_SESSION will get
|
|
// unpinned automatically at the end of the current session (connection).
|
|
// - The option OCI_DURATION_NULL is used when the client does not want to set
|
|
// the pin duration. If the object is already loaded into the cache, then the
|
|
// pin duration will remain the same. If the object is not yet loaded, the
|
|
// pin duration of the object will be set to OCI_DURATION_DEFAULT.
|
|
OCIDuration = ub2;
|
|
/// The OCITypeCode type is interchangeable with the existing SQLT type which is a ub2
|
|
OCITypeCode = ub2;
|
|
|
|
const
|
|
{ OCI Handle Types }
|
|
OCI_HTYPE_FIRST = 1;
|
|
OCI_HTYPE_ENV = 1;
|
|
OCI_HTYPE_ERROR = 2;
|
|
OCI_HTYPE_SVCCTX = 3;
|
|
OCI_HTYPE_STMT = 4;
|
|
OCI_HTYPE_BIND = 5;
|
|
OCI_HTYPE_DEFINE = 6;
|
|
OCI_HTYPE_DESCRIBE = 7;
|
|
OCI_HTYPE_SERVER = 8;
|
|
OCI_HTYPE_SESSION = 9;
|
|
OCI_HTYPE_TRANS = 10;
|
|
OCI_HTYPE_COMPLEXOBJECT = 11;
|
|
OCI_HTYPE_SECURITY = 12;
|
|
OCI_HTYPE_SUBSCRIPTION = 13;
|
|
OCI_HTYPE_DIRPATH_CTX = 14;
|
|
OCI_HTYPE_DIRPATH_COLUMN_ARRAY = 15;
|
|
OCI_HTYPE_DIRPATH_STREAM = 16;
|
|
OCI_HTYPE_PROC = 17;
|
|
OCI_HTYPE_LAST = 17;
|
|
|
|
{ OCI Descriptor Types }
|
|
OCI_DTYPE_FIRST = 50;
|
|
OCI_DTYPE_LOB = 50;
|
|
OCI_DTYPE_SNAP = 51;
|
|
OCI_DTYPE_RSET = 52;
|
|
OCI_DTYPE_PARAM = 53;
|
|
OCI_DTYPE_ROWID = 54;
|
|
OCI_DTYPE_COMPLEXOBJECTCOMP = 55;
|
|
OCI_DTYPE_FILE = 56;
|
|
OCI_DTYPE_AQENQ_OPTIONS = 57;
|
|
OCI_DTYPE_AQDEQ_OPTIONS = 58;
|
|
OCI_DTYPE_AQMSG_PROPERTIES = 59;
|
|
OCI_DTYPE_AQAGENT = 60;
|
|
OCI_DTYPE_LOCATOR = 61;
|
|
OCI_DTYPE_DATETIME = 62;
|
|
OCI_DTYPE_INTERVAL = 63;
|
|
OCI_DTYPE_AQNFY_DESCRIPTOR = 64;
|
|
OCI_DTYPE_LAST = 64;
|
|
OCI_DTYPE_DATE = 65; { Date }
|
|
OCI_DTYPE_TIME = 66; { Time }
|
|
OCI_DTYPE_TIME_TZ = 67; { Time with timezone }
|
|
OCI_DTYPE_TIMESTAMP = 68; { Timestamp }
|
|
OCI_DTYPE_TIMESTAMP_TZ = 69; { Timestamp with timezone }
|
|
OCI_DTYPE_TIMESTAMP_LTZ = 70; { Timestamp with local tz }
|
|
|
|
{ OCI Attributes Types }
|
|
OCI_ATTR_FNCODE = 1; // the OCI function code
|
|
OCI_ATTR_OBJECT = 2; // is the environment initialized in object mode
|
|
OCI_ATTR_NONBLOCKING_MODE = 3; // non blocking mode
|
|
OCI_ATTR_SQLCODE = 4; // the SQL verb
|
|
OCI_ATTR_ENV = 5; // the environment handle
|
|
OCI_ATTR_SERVER = 6; // the server handle
|
|
OCI_ATTR_SESSION = 7; // the user session handle
|
|
OCI_ATTR_TRANS = 8; // the transaction handle
|
|
OCI_ATTR_ROW_COUNT = 9; // the rows processed so far
|
|
OCI_ATTR_SQLFNCODE = 10; // the SQL verb of the statement
|
|
OCI_ATTR_PREFETCH_ROWS = 11; // sets the number of rows to prefetch
|
|
OCI_ATTR_NESTED_PREFETCH_ROWS = 12; // the prefetch rows of nested table
|
|
OCI_ATTR_PREFETCH_MEMORY = 13; // memory limit for rows fetched
|
|
OCI_ATTR_NESTED_PREFETCH_MEMORY = 14;// memory limit for nested rows
|
|
OCI_ATTR_CHAR_COUNT = 15; // this specifies the bind and define size in characters
|
|
OCI_ATTR_PDSCL = 16; // packed decimal scale
|
|
OCI_ATTR_FSPRECISION = OCI_ATTR_PDSCL; // fs prec for datetime data types
|
|
OCI_ATTR_PDPRC = 17; // packed decimal format
|
|
OCI_ATTR_LFPRECISION = OCI_ATTR_PDPRC; // fs prec for datetime data types
|
|
OCI_ATTR_PARAM_COUNT = 18; // number of column in the select list
|
|
OCI_ATTR_ROWID = 19; // the rowid
|
|
OCI_ATTR_CHARSET = 20; // the character set value
|
|
OCI_ATTR_NCHAR = 21; // NCHAR type
|
|
OCI_ATTR_USERNAME = 22; // username attribute
|
|
OCI_ATTR_PASSWORD = 23; // password attribute
|
|
OCI_ATTR_STMT_TYPE = 24; // statement type
|
|
OCI_ATTR_INTERNAL_NAME = 25; // user friendly global name
|
|
OCI_ATTR_EXTERNAL_NAME = 26; // the internal name for global txn
|
|
OCI_ATTR_XID = 27; // XOPEN defined global transaction id
|
|
OCI_ATTR_TRANS_LOCK = 28; //
|
|
OCI_ATTR_TRANS_NAME = 29; // string to identify a global transaction
|
|
OCI_ATTR_HEAPALLOC = 30; // memory allocated on the heap
|
|
OCI_ATTR_CHARSET_ID = 31; // Character Set ID
|
|
OCI_ATTR_CHARSET_FORM = 32; // Character Set Form
|
|
OCI_ATTR_MAXDATA_SIZE = 33; // Maximumsize of data on the server
|
|
OCI_ATTR_CACHE_OPT_SIZE = 34; // object cache optimal size
|
|
OCI_ATTR_CACHE_MAX_SIZE = 35; // object cache maximum size percentage
|
|
OCI_ATTR_PINOPTION = 36; // object cache default pin option
|
|
OCI_ATTR_ALLOC_DURATION = 37; // object cache default allocation duration
|
|
OCI_ATTR_PIN_DURATION = 38; // object cache default pin duration
|
|
OCI_ATTR_FDO = 39; // Format Descriptor object attribute
|
|
OCI_ATTR_POSTPROCESSING_CALLBACK = 40; // Callback to process outbind data
|
|
OCI_ATTR_POSTPROCESSING_CONTEXT = 41; // Callback context to process outbind data
|
|
OCI_ATTR_ROWS_RETURNED = 42; // Number of rows returned in current iter - for Bind handles
|
|
OCI_ATTR_FOCBK = 43; // Failover Callback attribute
|
|
OCI_ATTR_IN_V8_MODE = 44; // is the server/service context in V8 mode
|
|
OCI_ATTR_LOBEMPTY = 45; // empty lob ?
|
|
OCI_ATTR_SESSLANG = 46; // session language handle
|
|
|
|
OCI_ATTR_VISIBILITY = 47; // visibility
|
|
OCI_ATTR_RELATIVE_MSGID = 48; // relative message id
|
|
OCI_ATTR_SEQUENCE_DEVIATION = 49; // sequence deviation
|
|
|
|
OCI_ATTR_CONSUMER_NAME = 50; // consumer name
|
|
OCI_ATTR_DEQ_MODE = 51; // dequeue mode
|
|
OCI_ATTR_NAVIGATION = 52; // navigation
|
|
OCI_ATTR_WAIT = 53; // wait
|
|
OCI_ATTR_DEQ_MSGID = 54; // dequeue message id
|
|
|
|
OCI_ATTR_PRIORITY = 55; // priority
|
|
OCI_ATTR_DELAY = 56; // delay
|
|
OCI_ATTR_EXPIRATION = 57; // expiration
|
|
OCI_ATTR_CORRELATION = 58; // correlation id
|
|
OCI_ATTR_ATTEMPTS = 59; // # of attempts
|
|
OCI_ATTR_RECIPIENT_LIST = 60; // recipient list
|
|
OCI_ATTR_EXCEPTION_QUEUE = 61; // exception queue name
|
|
OCI_ATTR_ENQ_TIME = 62; // enqueue time (only OCIAttrGet)
|
|
OCI_ATTR_MSG_STATE = 63; // message state (only OCIAttrGet)
|
|
// NOTE: 64-66 used below
|
|
OCI_ATTR_AGENT_NAME = 64; // agent name
|
|
OCI_ATTR_AGENT_ADDRESS = 65; // agent address
|
|
OCI_ATTR_AGENT_PROTOCOL = 66; // agent protocol
|
|
|
|
OCI_ATTR_SENDER_ID = 68; // sender id
|
|
OCI_ATTR_ORIGINAL_MSGID = 69; // original message id
|
|
|
|
OCI_ATTR_QUEUE_NAME = 70; // queue name
|
|
OCI_ATTR_NFY_MSGID = 71; // message id
|
|
OCI_ATTR_MSG_PROP = 72; // message properties
|
|
|
|
OCI_ATTR_NUM_DML_ERRORS = 73; // num of errs in array DML
|
|
OCI_ATTR_DML_ROW_OFFSET = 74; // row offset in the array
|
|
|
|
OCI_ATTR_DATEFORMAT = 75; // default date format string
|
|
OCI_ATTR_BUF_ADDR = 76; // buffer address
|
|
OCI_ATTR_BUF_SIZE = 77; // buffer size
|
|
OCI_ATTR_DIRPATH_MODE = 78; // mode of direct path operation
|
|
OCI_ATTR_DIRPATH_NOLOG = 79; // nologging option
|
|
OCI_ATTR_DIRPATH_PARALLEL = 80; // parallel (temp seg) option
|
|
OCI_ATTR_NUM_ROWS = 81; // number of rows in column array
|
|
// NOTE that OCI_ATTR_NUM_COLS is a column
|
|
// array attribute too.
|
|
|
|
OCI_ATTR_COL_COUNT = 82; // columns of column array processed so far.
|
|
OCI_ATTR_STREAM_OFFSET = 83; // str off of last row processed
|
|
OCI_ATTR_SHARED_HEAPALLOC = 84; // Shared Heap Allocation Size
|
|
|
|
OCI_ATTR_SERVER_GROUP = 85; // server group name
|
|
|
|
OCI_ATTR_MIGSESSION = 86; // migratable session attribute
|
|
|
|
OCI_ATTR_NOCACHE = 87; // Temporary LOBs
|
|
|
|
OCI_ATTR_MEMPOOL_SIZE = 88; // Pool Size
|
|
OCI_ATTR_MEMPOOL_INSTNAME = 89; // Instance name
|
|
OCI_ATTR_MEMPOOL_APPNAME = 90; // Application name
|
|
OCI_ATTR_MEMPOOL_HOMENAME = 91; // Home Directory name
|
|
OCI_ATTR_MEMPOOL_MODEL = 92; // Pool Model (proc,thrd,both)
|
|
OCI_ATTR_MODES = 93; // Modes
|
|
|
|
OCI_ATTR_SUBSCR_NAME = 94; // name of subscription
|
|
OCI_ATTR_SUBSCR_CALLBACK = 95; // associated callback
|
|
OCI_ATTR_SUBSCR_CTX = 96; // associated callback context
|
|
OCI_ATTR_SUBSCR_PAYLOAD = 97; // associated payload
|
|
OCI_ATTR_SUBSCR_NAMESPACE = 98; // associated namespace
|
|
|
|
OCI_ATTR_PROXY_CREDENTIALS = 99; // Proxy user credentials
|
|
OCI_ATTR_INITIAL_CLIENT_ROLES = 100; // Initial client role list
|
|
|
|
OCI_ATTR_UNK = 101; // unknown attribute
|
|
OCI_ATTR_NUM_COLS = 102; // number of columns
|
|
OCI_ATTR_LIST_COLUMNS = 103; // parameter of the column list
|
|
OCI_ATTR_RDBA = 104; // DBA of the segment header
|
|
OCI_ATTR_CLUSTERED = 105; // whether the table is clustered
|
|
OCI_ATTR_PARTITIONED = 106; // whether the table is partitioned
|
|
OCI_ATTR_INDEX_ONLY = 107; // whether the table is index only
|
|
OCI_ATTR_LIST_ARGUMENTS = 108; // parameter of the argument list
|
|
OCI_ATTR_LIST_SUBPROGRAMS = 109; // parameter of the subprogram list
|
|
OCI_ATTR_REF_TDO = 110; // REF to the type descriptor
|
|
OCI_ATTR_LINK = 111; // the database link name
|
|
OCI_ATTR_MIN = 112; // minimum value
|
|
OCI_ATTR_MAX = 113; // maximum value
|
|
OCI_ATTR_INCR = 114; // increment value
|
|
OCI_ATTR_CACHE = 115; // number of sequence numbers cached
|
|
OCI_ATTR_ORDER = 116; // whether the sequence is ordered
|
|
OCI_ATTR_HW_MARK = 117; // high-water mark
|
|
OCI_ATTR_TYPE_SCHEMA = 118; // type's schema name
|
|
OCI_ATTR_TIMESTAMP = 119; // timestamp of the object
|
|
OCI_ATTR_NUM_ATTRS = 120; // number of sttributes
|
|
OCI_ATTR_NUM_PARAMS = 121; // number of parameters
|
|
OCI_ATTR_OBJID = 122; // object id for a table or view
|
|
OCI_ATTR_PTYPE = 123; // type of info described by
|
|
OCI_ATTR_PARAM = 124; // parameter descriptor
|
|
OCI_ATTR_OVERLOAD_ID = 125; // overload ID for funcs and procs
|
|
OCI_ATTR_TABLESPACE = 126; // table name space
|
|
OCI_ATTR_TDO = 127; // TDO of a type
|
|
OCI_ATTR_LTYPE = 128; // list type
|
|
OCI_ATTR_PARSE_ERROR_OFFSET = 129; // Parse Error offset
|
|
OCI_ATTR_IS_TEMPORARY = 130; // whether table is temporary
|
|
OCI_ATTR_IS_TYPED = 131; // whether table is typed
|
|
OCI_ATTR_DURATION = 132; // duration of temporary table
|
|
OCI_ATTR_IS_INVOKER_RIGHTS = 133; // is invoker rights
|
|
OCI_ATTR_OBJ_NAME = 134; // top level schema obj name
|
|
OCI_ATTR_OBJ_SCHEMA = 135; // schema name
|
|
OCI_ATTR_OBJ_ID = 136; // top level schema object id
|
|
OCI_ATTR_STMTCACHESIZE = 176; // size of the stm cache
|
|
OCI_ATTR_ROWS_FETCHED = 197; // rows fetched in last call
|
|
OCI_ATTR_DEFAULT_LOBPREFETCH_SIZE = 438; // default prefetch size
|
|
|
|
{ OCI Error Return Values }
|
|
OCI_SUCCESS = 0;
|
|
OCI_SUCCESS_WITH_INFO = 1;
|
|
OCI_NO_DATA = 100;
|
|
OCI_ERROR = -1;
|
|
OCI_INVALID_HANDLE = -2;
|
|
OCI_NEED_DATA = 99;
|
|
OCI_STILL_EXECUTING = -3123;
|
|
OCI_CONTINUE = -24200;
|
|
OCI_PASSWORD_INFO = 28002; // the password will expire within ... days
|
|
|
|
{ Generic Default Value for Modes, .... }
|
|
OCI_DEFAULT = $0;
|
|
|
|
{ OCI Init Mode }
|
|
OCI_THREADED = $1;
|
|
OCI_OBJECT = $2;
|
|
OCI_EVENTS = $4;
|
|
OCI_SHARED = $10;
|
|
OCI_NO_UCB = $40;
|
|
OCI_NO_MUTEX = $80;
|
|
|
|
{ OCI Credentials }
|
|
OCI_CRED_RDBMS = 1;
|
|
OCI_CRED_EXT = 2;
|
|
OCI_CRED_PROXY = 3;
|
|
|
|
{ OCI Authentication Mode }
|
|
OCI_MIGRATE = $0001; // migratable auth context
|
|
OCI_SYSDBA = $0002; // for SYSDBA authorization
|
|
OCI_SYSOPER = $0004; // for SYSOPER authorization
|
|
OCI_PRELIM_AUTH = $0008; // for preliminary authorization
|
|
|
|
{ OCIPasswordChange }
|
|
OCI_AUTH = $08; // Change the password but do not login
|
|
|
|
{ OCI Data Types }
|
|
SQLT_CHR = 1;
|
|
SQLT_NUM = 2;
|
|
SQLT_INT = 3;
|
|
SQLT_FLT = 4;
|
|
SQLT_STR = 5;
|
|
SQLT_VNU = 6;
|
|
SQLT_PDN = 7;
|
|
SQLT_LNG = 8;
|
|
SQLT_VCS = 9;
|
|
SQLT_NON = 10;
|
|
SQLT_RID = 11;
|
|
SQLT_DAT = 12;
|
|
SQLT_VBI = 15;
|
|
SQLT_BFLOAT = 21;
|
|
SQLT_BDOUBLE = 22;
|
|
SQLT_BIN = 23;
|
|
SQLT_LBI = 24;
|
|
_SQLT_PLI = 29;
|
|
SQLT_UIN = 68;
|
|
SQLT_SLS = 91;
|
|
SQLT_LVC = 94;
|
|
SQLT_LVB = 95;
|
|
SQLT_AFC = 96;
|
|
SQLT_AVC = 97;
|
|
SQLT_IBFLOAT = 100;
|
|
SQLT_IBDOUBLE = 101;
|
|
SQLT_CUR = 102;
|
|
SQLT_RDD = 104;
|
|
SQLT_LAB = 105;
|
|
SQLT_OSL = 106;
|
|
SQLT_NTY = 108;
|
|
SQLT_REF = 110;
|
|
SQLT_CLOB = 112;
|
|
SQLT_BLOB = 113;
|
|
SQLT_BFILEE = 114;
|
|
SQLT_CFILEE = 115;
|
|
SQLT_RSET = 116;
|
|
SQLT_NCO = 122;
|
|
SQLT_VST = 155;
|
|
SQLT_ODT = 156;
|
|
SQLT_DATE = 184;
|
|
SQLT_TIME = 185;
|
|
SQLT_TIME_TZ = 186;
|
|
SQLT_TIMESTAMP = 187;
|
|
SQLT_TIMESTAMP_TZ = 188;
|
|
SQLT_INTERVAL_YM = 189;
|
|
SQLT_INTERVAL_DS = 190;
|
|
SQLT_TIMESTAMP_LTZ = 232;
|
|
|
|
_SQLT_REC = 250;
|
|
_SQLT_TAB = 251;
|
|
_SQLT_BOL = 252;
|
|
|
|
{ OCI Statement Types }
|
|
OCI_STMT_SELECT = 1; // select statement
|
|
OCI_STMT_UPDATE = 2; // update statement
|
|
OCI_STMT_DELETE = 3; // delete statement
|
|
OCI_STMT_INSERT = 4; // Insert Statement
|
|
OCI_STMT_CREATE = 5; // create statement
|
|
OCI_STMT_DROP = 6; // drop statement
|
|
OCI_STMT_ALTER = 7; // alter statement
|
|
OCI_STMT_BEGIN = 8; // begin ... (pl/sql statement)
|
|
OCI_STMT_DECLARE = 9; // declare .. (pl/sql statement)
|
|
|
|
{ OCI Statement language }
|
|
OCI_NTV_SYNTAX = 1; // Use what so ever is the native lang of server
|
|
OCI_V7_SYNTAX = 2; // V7 language
|
|
OCI_V8_SYNTAX = 3; // V8 language
|
|
|
|
{ OCI Statement Execute mode }
|
|
OCI_BATCH_MODE = $01; // batch the oci statement for execution
|
|
OCI_EXACT_FETCH = $02; // fetch the exact rows specified
|
|
OCI_SCROLLABLE_CURSOR = $08; // cursor scrollable
|
|
OCI_DESCRIBE_ONLY = $10; // only describe the statement
|
|
OCI_COMMIT_ON_SUCCESS = $20; // commit, if successful execution
|
|
OCI_NON_BLOCKING = $40; // non-blocking
|
|
OCI_BATCH_ERRORS = $80; // batch errors in array dmls
|
|
OCI_PARSE_ONLY = $100; // only parse the statement
|
|
|
|
{ Enable OCI Server-Side Statement Caching }
|
|
OCI_STMT_CACHE = $40;
|
|
OCI_STMTCACHE_DELETE = $10;
|
|
|
|
OCI_DATA_AT_EXEC = $02; // data at execute time
|
|
OCI_DYNAMIC_FETCH = $02; // fetch dynamically
|
|
OCI_PIECEWISE = $04; // piecewise DMLs or fetch
|
|
|
|
{ OCI Transaction modes }
|
|
OCI_TRANS_NEW = $00000001; // starts a new transaction branch
|
|
OCI_TRANS_JOIN = $00000002; // join an existing transaction
|
|
OCI_TRANS_RESUME = $00000004; // resume this transaction
|
|
OCI_TRANS_STARTMASK = $000000ff;
|
|
|
|
OCI_TRANS_READONLY = $00000100; // starts a readonly transaction
|
|
OCI_TRANS_READWRITE = $00000200; // starts a read-write transaction
|
|
OCI_TRANS_SERIALIZABLE = $00000400; // starts a serializable transaction
|
|
OCI_TRANS_ISOLMASK = $0000ff00;
|
|
|
|
OCI_TRANS_LOOSE = $00010000; // a loosely coupled branch
|
|
OCI_TRANS_TIGHT = $00020000; // a tightly coupled branch
|
|
OCI_TRANS_TYPEMASK = $000f0000;
|
|
|
|
OCI_TRANS_NOMIGRATE = $00100000; // non migratable transaction
|
|
OCI_TRANS_TWOPHASE = $01000000; // use two phase commit
|
|
|
|
{ OCI pece wise fetch }
|
|
OCI_ONE_PIECE = 0; // one piece
|
|
OCI_FIRST_PIECE = 1; // the first piece
|
|
OCI_NEXT_PIECE = 2; // the next of many pieces
|
|
OCI_LAST_PIECE = 3; // the last piece
|
|
|
|
{ OCI fetch modes }
|
|
OCI_FETCH_NEXT = $02; // next row
|
|
OCI_FETCH_FIRST = $04; // first row of the result set
|
|
OCI_FETCH_LAST = $08; // the last row of the result set
|
|
OCI_FETCH_PRIOR = $10; // the previous row relative to current
|
|
OCI_FETCH_ABSOLUTE = $20; // absolute offset from first
|
|
OCI_FETCH_RELATIVE = $40; // offset relative to current
|
|
|
|
{****************** Describe Handle Parameter Attributes *****************}
|
|
|
|
{ Attributes common to Columns and Stored Procs }
|
|
OCI_ATTR_DATA_SIZE = 1; // maximum size of the data
|
|
OCI_ATTR_DATA_TYPE = 2; // the SQL type of the column/argument
|
|
OCI_ATTR_DISP_SIZE = 3; // the display size
|
|
OCI_ATTR_NAME = 4; // the name of the column/argument
|
|
OCI_ATTR_PRECISION = 5; // precision if number type
|
|
OCI_ATTR_SCALE = 6; // scale if number type
|
|
OCI_ATTR_IS_NULL = 7; // is it null ?
|
|
OCI_ATTR_TYPE_NAME = 8; // name of the named data type or a package name for package private types
|
|
OCI_ATTR_SCHEMA_NAME = 9; // the schema name
|
|
OCI_ATTR_SUB_NAME = 10; // type name if package private type
|
|
OCI_ATTR_POSITION = 11; // relative position of col/arg in the list of cols/args
|
|
|
|
{ complex object retrieval parameter attributes }
|
|
OCI_ATTR_COMPLEXOBJECTCOMP_TYPE = 50;
|
|
OCI_ATTR_COMPLEXOBJECTCOMP_TYPE_LEVEL = 51;
|
|
OCI_ATTR_COMPLEXOBJECT_LEVEL = 52;
|
|
OCI_ATTR_COMPLEXOBJECT_COLL_OUTOFLINE = 53;
|
|
|
|
{ Only Columns }
|
|
OCI_ATTR_DISP_NAME = 100; // the display name
|
|
|
|
{ Only Stored Procs }
|
|
OCI_ATTR_OVERLOAD = 210; // is this position overloaded
|
|
OCI_ATTR_LEVEL = 211; // level for structured types
|
|
OCI_ATTR_HAS_DEFAULT = 212; // has a default value
|
|
OCI_ATTR_IOMODE = 213; // in, out inout
|
|
OCI_ATTR_RADIX = 214; // returns a radix
|
|
OCI_ATTR_NUM_ARGS = 215; // total number of arguments
|
|
|
|
{ only named type attributes }
|
|
OCI_ATTR_TYPECODE = 216; // object or collection
|
|
OCI_ATTR_COLLECTION_TYPECODE = 217; // varray or nested table
|
|
OCI_ATTR_VERSION = 218; // user assigned version
|
|
OCI_ATTR_IS_INCOMPLETE_TYPE = 219; // is this an incomplete type
|
|
OCI_ATTR_IS_SYSTEM_TYPE = 220; // a system type
|
|
OCI_ATTR_IS_PREDEFINED_TYPE = 221; // a predefined type
|
|
OCI_ATTR_IS_TRANSIENT_TYPE = 222; // a transient type
|
|
OCI_ATTR_IS_SYSTEM_GENERATED_TYPE = 223; // system generated type
|
|
OCI_ATTR_HAS_NESTED_TABLE = 224; // contains nested table attr
|
|
OCI_ATTR_HAS_LOB = 225; // has a lob attribute
|
|
OCI_ATTR_HAS_FILE = 226; // has a file attribute
|
|
OCI_ATTR_COLLECTION_ELEMENT = 227; // has a collection attribute
|
|
OCI_ATTR_NUM_TYPE_ATTRS = 228; // number of attribute types
|
|
OCI_ATTR_LIST_TYPE_ATTRS = 229; // list of type attributes
|
|
OCI_ATTR_NUM_TYPE_METHODS = 230; // number of type methods
|
|
OCI_ATTR_LIST_TYPE_METHODS = 231; // list of type methods
|
|
OCI_ATTR_MAP_METHOD = 232; // map method of type
|
|
OCI_ATTR_ORDER_METHOD = 233; // order method of type
|
|
|
|
{ only collection element }
|
|
OCI_ATTR_NUM_ELEMS = 234; // number of elements
|
|
|
|
{ only type methods }
|
|
OCI_ATTR_ENCAPSULATION = 235; // encapsulation level
|
|
OCI_ATTR_IS_SELFISH = 236; // method selfish
|
|
OCI_ATTR_IS_VIRTUAL = 237; // virtual
|
|
OCI_ATTR_IS_INLINE = 238; // inline
|
|
OCI_ATTR_IS_CONSTANT = 239; // constant
|
|
OCI_ATTR_HAS_RESULT = 240; // has result
|
|
OCI_ATTR_IS_CONSTRUCTOR = 241; // constructor
|
|
OCI_ATTR_IS_DESTRUCTOR = 242; // destructor
|
|
OCI_ATTR_IS_OPERATOR = 243; // operator
|
|
OCI_ATTR_IS_MAP = 244; // a map method
|
|
OCI_ATTR_IS_ORDER = 245; // order method
|
|
OCI_ATTR_IS_RNDS = 246; // read no data state method
|
|
OCI_ATTR_IS_RNPS = 247; // read no process state
|
|
OCI_ATTR_IS_WNDS = 248; // write no data state method
|
|
OCI_ATTR_IS_WNPS = 249; // write no process state
|
|
|
|
OCI_ATTR_DESC_PUBLIC = 250; // public object
|
|
|
|
{ Object Cache Enhancements : attributes for User Constructed Instances }
|
|
OCI_ATTR_CACHE_CLIENT_CONTEXT = 251;
|
|
OCI_ATTR_UCI_CONSTRUCT = 252;
|
|
OCI_ATTR_UCI_DESTRUCT = 253;
|
|
OCI_ATTR_UCI_COPY = 254;
|
|
OCI_ATTR_UCI_PICKLE = 255;
|
|
OCI_ATTR_UCI_UNPICKLE = 256;
|
|
OCI_ATTR_UCI_REFRESH = 257;
|
|
|
|
{ for type inheritance }
|
|
OCI_ATTR_IS_SUBTYPE = 258;
|
|
OCI_ATTR_SUPERTYPE_SCHEMA_NAME = 259;
|
|
OCI_ATTR_SUPERTYPE_NAME = 260;
|
|
|
|
{ for schemas }
|
|
OCI_ATTR_LIST_OBJECTS = 261; // list of objects in schema
|
|
|
|
{ for database }
|
|
OCI_ATTR_NCHARSET_ID = 262; // char set id
|
|
OCI_ATTR_LIST_SCHEMAS = 263; // list of schemas
|
|
OCI_ATTR_MAX_PROC_LEN = 264; // max procedure length
|
|
OCI_ATTR_MAX_COLUMN_LEN = 265; // max column name length
|
|
OCI_ATTR_CURSOR_COMMIT_BEHAVIOR = 266; // cursor commit behavior
|
|
OCI_ATTR_MAX_CATALOG_NAMELEN = 267; // catalog namelength
|
|
OCI_ATTR_CATALOG_LOCATION = 268; // catalog location
|
|
OCI_ATTR_SAVEPOINT_SUPPORT = 269; // savepoint support
|
|
OCI_ATTR_NOWAIT_SUPPORT = 270; // nowait support
|
|
OCI_ATTR_AUTOCOMMIT_DDL = 271; // autocommit DDL
|
|
OCI_ATTR_LOCKING_MODE = 272; // locking mode
|
|
|
|
OCI_ATTR_CACHE_ARRAYFLUSH = $40;
|
|
OCI_ATTR_OBJECT_NEWNOTNULL = $10;
|
|
OCI_ATTR_OBJECT_DETECTCHANGE = $20;
|
|
|
|
{ Piece Information }
|
|
OCI_PARAM_IN = $01; // in parameter
|
|
OCI_PARAM_OUT = $02; // out parameter
|
|
|
|
{ LOB Buffering Flush Flags }
|
|
OCI_LOB_BUFFER_FREE = 1;
|
|
OCI_LOB_BUFFER_NOFREE = 2;
|
|
|
|
{ FILE open modes }
|
|
OCI_FILE_READONLY = 1; // readonly mode open for FILE types
|
|
{ LOB open modes }
|
|
OCI_LOB_READONLY = 1; // readonly mode open for ILOB types
|
|
OCI_LOB_READWRITE = 2; // read write mode open for ILOBs
|
|
{ LOB types }
|
|
OCI_TEMP_BLOB = 1; // LOB type - BLOB
|
|
OCI_TEMP_CLOB = 2; // LOB type - CLOB
|
|
|
|
{ CHAR/NCHAR/VARCHAR2/NVARCHAR2/CLOB/NCLOB char set "form" information
|
|
(used e.g. by OCI_ATTR_CHARSET_FORM attribute) }
|
|
SQLCS_IMPLICIT = 1; // for CHAR, VARCHAR2, CLOB w/o a specified set
|
|
SQLCS_NCHAR = 2; // for NCHAR, NCHAR VARYING, NCLOB
|
|
SQLCS_EXPLICIT = 3; // for CHAR, etc, with "CHARACTER SET ..." syntax
|
|
SQLCS_FLEXIBLE = 4; // for PL/SQL "flexible" parameters
|
|
SQLCS_LIT_NULL = 5; // for typecheck of NULL and empty_clob() lits
|
|
|
|
{ OCI_NUMBER }
|
|
OCI_NUMBER_SIZE = 22;
|
|
OCI_NUMBER_UNSIGNED = 0;
|
|
OCI_NUMBER_SIGNED = 2;
|
|
|
|
{ OBJECT Duration }
|
|
OCI_DURATION_BEGIN_ = 10;
|
|
OCI_DURATION_CALLOUT_ = OCI_DURATION_BEGIN_ + 4;
|
|
|
|
OCI_DURATION_INVALID: OCIDuration = $FFFF; // Invalid duration
|
|
OCI_DURATION_BEGIN: OCIDuration = OCI_DURATION_BEGIN_; // beginning sequence of duration
|
|
OCI_DURATION_NULL: OCIDuration = OCI_DURATION_BEGIN_ - 1; // null duration
|
|
OCI_DURATION_DEFAULT: OCIDuration = OCI_DURATION_BEGIN_ - 2; // default
|
|
OCI_DURATION_USER_CALLBACK: OCIDuration = OCI_DURATION_BEGIN_ - 3;
|
|
OCI_DURATION_NEXT: OCIDuration = OCI_DURATION_BEGIN_ - 4; // next special duration
|
|
OCI_DURATION_SESSION: OCIDuration = OCI_DURATION_BEGIN_; // the end of user session
|
|
OCI_DURATION_TRANS: OCIDuration = OCI_DURATION_BEGIN_ + 1; // the end of user transaction
|
|
// DO NOT USE OCI_DURATION_CALL. IT IS UNSUPPORTED
|
|
// WILL BE REMOVED/CHANGED IN A FUTURE RELEASE
|
|
OCI_DURATION_CALL: OCIDuration = OCI_DURATION_BEGIN_ + 2; // the end of user client/server call
|
|
OCI_DURATION_STATEMENT: OCIDuration = OCI_DURATION_BEGIN_ + 3;
|
|
// This is to be used only during callouts. It is similar to that
|
|
// of OCI_DURATION_CALL, but lasts only for the duration of a callout.
|
|
// Its heap is from PGA
|
|
OCI_DURATION_CALLOUT: OCIDuration = OCI_DURATION_CALLOUT_;
|
|
OCI_DURATION_LAST: OCIDuration = OCI_DURATION_CALLOUT_; // last of predefined durations
|
|
// This is not being treated as other predefined durations such as
|
|
// SESSION, CALL etc, because this would not have an entry in the duration
|
|
// table and its functionality is primitive such that only allocate, free,
|
|
// resize memory are allowed, but one cannot create subduration out of this
|
|
OCI_DURATION_PROCESS: OCIDuration = OCI_DURATION_BEGIN_ - 5; // next special duration
|
|
|
|
{ TYPE CODE }
|
|
/// Type manager typecodes
|
|
// - These are typecodes designed to be used with the type manager;
|
|
// they also include longer, more readable versions of existing SQLT names
|
|
// - Those types that are directly related to existing SQLT types are #define'd
|
|
// to their SQLT equivalents
|
|
// - The type manager typecodes are designed to be useable for all OCI calls.
|
|
// They are in the range from 192 to 320 for typecodes, so as not to conflict
|
|
// with existing OCI SQLT typecodes (see ocidfn.h)
|
|
OCI_TYPECODE_REF = SQLT_REF; // SQL/OTS OBJECT REFERENCE
|
|
OCI_TYPECODE_DATE = SQLT_DAT; // SQL DATE OTS DATE
|
|
OCI_TYPECODE_SIGNED8 = 27; // SQL SIGNED INTEGER(8) OTS SINT8
|
|
OCI_TYPECODE_SIGNED16 = 28; // SQL SIGNED INTEGER(16) OTS SINT16
|
|
OCI_TYPECODE_SIGNED32 = 29; // SQL SIGNED INTEGER(32) OTS SINT32
|
|
OCI_TYPECODE_REAL = 21; // SQL REAL OTS SQL_REAL
|
|
OCI_TYPECODE_DOUBLE = 22; // SQL DOUBLE PRECISION OTS SQL_DOUBLE
|
|
OCI_TYPECODE_BFLOAT = SQLT_IBFLOAT; // Binary float
|
|
OCI_TYPECODE_BDOUBLE = SQLT_IBDOUBLE; // Binary double
|
|
OCI_TYPECODE_FLOAT = SQLT_FLT; // SQL FLOAT(P) OTS FLOAT(P)
|
|
OCI_TYPECODE_NUMBER = SQLT_NUM; // SQL NUMBER(P S) OTS NUMBER(P S)
|
|
OCI_TYPECODE_DECIMAL = SQLT_PDN; // SQL DECIMAL(P S) OTS DECIMAL(P S)
|
|
OCI_TYPECODE_UNSIGNED8 = SQLT_BIN; // SQL UNSIGNED INTEGER(8) OTS UINT8
|
|
OCI_TYPECODE_UNSIGNED16 = 25; // SQL UNSIGNED INTEGER(16) OTS UINT16
|
|
OCI_TYPECODE_UNSIGNED32 = 26; // SQL UNSIGNED INTEGER(32) OTS UINT32
|
|
OCI_TYPECODE_OCTET = 245; // SQL ??? OTS OCTET
|
|
OCI_TYPECODE_SMALLINT = 246; // SQL SMALLINT OTS SMALLINT
|
|
OCI_TYPECODE_INTEGER = SQLT_INT; // SQL INTEGER OTS INTEGER
|
|
OCI_TYPECODE_RAW = SQLT_LVB; // SQL RAW(N) OTS RAW(N)
|
|
OCI_TYPECODE_PTR = 32; // SQL POINTER OTS POINTER
|
|
OCI_TYPECODE_VARCHAR2 = SQLT_VCS; // SQL VARCHAR2(N) OTS SQL_VARCHAR2(N)
|
|
OCI_TYPECODE_CHAR = SQLT_AFC; // SQL CHAR(N) OTS SQL_CHAR(N)
|
|
OCI_TYPECODE_VARCHAR = SQLT_CHR; // SQL VARCHAR(N) OTS SQL_VARCHAR(N)
|
|
OCI_TYPECODE_MLSLABEL = SQLT_LAB; // OTS MLSLABEL
|
|
OCI_TYPECODE_VARRAY = 247; // SQL VARRAY OTS PAGED VARRAY
|
|
OCI_TYPECODE_TABLE = 248; // SQL TABLE OTS MULTISET
|
|
OCI_TYPECODE_OBJECT = SQLT_NTY; // SQL/OTS NAMED OBJECT TYPE
|
|
OCI_TYPECODE_OPAQUE = 58; // SQL/OTS Opaque Types
|
|
OCI_TYPECODE_NAMEDCOLLECTION = SQLT_NCO; // SQL/OTS NAMED COLLECTION TYPE
|
|
OCI_TYPECODE_BLOB = SQLT_BLOB; // SQL/OTS BINARY LARGE OBJECT
|
|
OCI_TYPECODE_BFILE = SQLT_BFILEE; // SQL/OTS BINARY FILE OBJECT
|
|
OCI_TYPECODE_CLOB = SQLT_CLOB; // SQL/OTS CHARACTER LARGE OBJECT
|
|
OCI_TYPECODE_CFILE = SQLT_CFILEE; // SQL/OTS CHARACTER FILE OBJECT
|
|
|
|
// the following are ANSI datetime datatypes added in 8.1
|
|
OCI_TYPECODE_TIME = SQLT_TIME; // SQL/OTS TIME
|
|
OCI_TYPECODE_TIME_TZ = SQLT_TIME_TZ; // SQL/OTS TIME_TZ
|
|
OCI_TYPECODE_TIMESTAMP = SQLT_TIMESTAMP; // SQL/OTS TIMESTAMP
|
|
OCI_TYPECODE_TIMESTAMP_TZ = SQLT_TIMESTAMP_TZ; // SQL/OTS TIMESTAMP_TZ
|
|
|
|
OCI_TYPECODE_TIMESTAMP_LTZ = SQLT_TIMESTAMP_LTZ; // TIMESTAMP_LTZ
|
|
|
|
OCI_TYPECODE_INTERVAL_YM = SQLT_INTERVAL_YM; // SQL/OTS INTRVL YR-MON
|
|
OCI_TYPECODE_INTERVAL_DS = SQLT_INTERVAL_DS; // SQL/OTS INTRVL DAY-SEC
|
|
OCI_TYPECODE_UROWID = SQLT_RDD; // Urowid type
|
|
|
|
OCI_TYPECODE_OTMFIRST = 228; // first Open Type Manager typecode
|
|
OCI_TYPECODE_OTMLAST = 320; // last OTM typecode
|
|
OCI_TYPECODE_SYSFIRST = 228; // first OTM system type (internal)
|
|
OCI_TYPECODE_SYSLAST = 235; // last OTM system type (internal)
|
|
OCI_TYPECODE_PLS_INTEGER = 266; // type code for PLS_INTEGER
|
|
|
|
//// the following are PL/SQL-only internal. They should not be used
|
|
// OCI_TYPECODE_ITABLE = SQLT_TAB; // PLSQL indexed table
|
|
// OCI_TYPECODE_RECORD = SQLT_REC; // PLSQL record
|
|
// OCI_TYPECODE_BOOLEAN = SQLT_BOL; // PLSQL boolean
|
|
|
|
// NOTE : The following NCHAR related codes are just short forms for saying
|
|
// OCI_TYPECODE_VARCHAR2 with a charset form of SQLCS_NCHAR. These codes are
|
|
// intended for use in the OCIAnyData API only and nowhere else.
|
|
OCI_TYPECODE_NCHAR = 286;
|
|
OCI_TYPECODE_NVARCHAR2 = 287;
|
|
OCI_TYPECODE_NCLOB = 288;
|
|
|
|
// To indicate absence of typecode being specified
|
|
OCI_TYPECODE_NONE = 0;
|
|
// To indicate error has to be taken from error handle - reserved for sqlplus use
|
|
OCI_TYPECODE_ERRHP = 283;
|
|
|
|
{ TYPEGET options }
|
|
OCI_TYPEGET_HEADER = 0;
|
|
OCI_TYPEGET_ALL = 1;
|
|
|
|
{ OBJECT FREE OPTION }
|
|
/// OCIObjectFreeFlag - Object free flag
|
|
// - If OCI_OBJECTCOPY_FORCE is specified when freeing an instance, the instance
|
|
// is freed regardless it is pinned or diritied.
|
|
// If OCI_OBJECTCOPY_NONULL is specified when freeing an instance, the null
|
|
// structure is not freed.
|
|
OCI_OBJECTFREE_FORCE : ub2 = $0001;
|
|
OCI_OBJECTFREE_NONULL: ub2 = $0002;
|
|
OCI_OBJECTFREE_HEADER: ub2 = $0004;
|
|
|
|
OCI_PREP2_CACHE_SEARCHONLY: ub4 = $0010;
|
|
|
|
type
|
|
/// Oracle native number low-level representation
|
|
OCINumber = packed record
|
|
OCINumberPart: array [0..OCI_NUMBER_SIZE-1] of ub1;
|
|
end;
|
|
|
|
{ TSQLDBOracleLib }
|
|
|
|
const
|
|
OCI_ENTRIES: array[0..40] of PChar = (
|
|
'OCIClientVersion', 'OCIEnvNlsCreate', 'OCIHandleAlloc', 'OCIHandleFree',
|
|
'OCIServerAttach', 'OCIServerDetach', 'OCIAttrGet', 'OCIAttrSet',
|
|
'OCISessionBegin', 'OCISessionEnd', 'OCIErrorGet', 'OCIStmtPrepare',
|
|
'OCIStmtExecute', 'OCIStmtFetch', 'OCIBindByPos', 'OCIParamGet',
|
|
'OCITransStart', 'OCITransRollback', 'OCITransCommit', 'OCIDescriptorAlloc',
|
|
'OCIDescriptorFree', 'OCIDateTimeConstruct', 'OCIDateTimeGetDate',
|
|
'OCIDefineByPos', 'OCILobGetLength', 'OCILobGetChunkSize', 'OCILobOpen',
|
|
'OCILobRead', 'OCILobClose', 'OCILobWrite',
|
|
'OCINlsCharSetNameToId', 'OCIStmtPrepare2',
|
|
'OCIStmtRelease', 'OCITypeByName', 'OCIObjectNew', 'OCIObjectFree',
|
|
'OCINumberFromInt','OCIStringAssignText', 'OCICollAppend', 'OCIBindObject',
|
|
'OCIPasswordChange');
|
|
|
|
type
|
|
/// direct access to the native Oracle Client Interface (OCI)
|
|
TSQLDBOracleLib = class(TSQLDBLib)
|
|
protected
|
|
procedure HandleError(Conn: TSQLDBConnection; Stmt: TSQLDBStatement;
|
|
Status: Integer; ErrorHandle: POCIError; InfoRaiseException: Boolean=false;
|
|
LogLevelNoRaise: TSynLogInfo=sllNone);
|
|
procedure RetrieveVersion;
|
|
function BlobOpen(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor): ub4;
|
|
function BlobRead(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; Blob: PByte; BlobLen: ub4;
|
|
csid: ub2=0; csfrm: ub1=SQLCS_IMPLICIT): integer;
|
|
function BlobReadToStream(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; stream: TStream; BlobLen: ub4;
|
|
csid: ub2=0; csfrm: ub1=SQLCS_IMPLICIT): integer;
|
|
function BlobWriteFromStream(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; stream: TStream; BlobLen: ub4;
|
|
csid: ub2=0; csfrm: ub1=SQLCS_IMPLICIT): integer;
|
|
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;
|
|
usrmempp: PPointer; charset, ncharset: ub2): sword; cdecl;
|
|
HandleAlloc: function(parenth: POCIHandle; var hndlpp: pointer;
|
|
atype: ub4; xtramem_sz: size_T=0; usrmempp: PPointer=nil): sword; cdecl;
|
|
HandleFree: function(hndlp: Pointer; atype: ub4): sword; cdecl;
|
|
ServerAttach: function(srvhp: POCIServer; errhp: POCIError; dblink: text;
|
|
dblink_len: sb4; mode: ub4): sword; cdecl;
|
|
ServerDetach: function(srvhp: POCIServer; errhp: POCIError;
|
|
mode: ub4): sword; cdecl;
|
|
AttrGet: function(trgthndlp: POCIHandle; trghndltyp: ub4;
|
|
attributep: Pointer; sizep: Pointer; attrtype: ub4;
|
|
errhp: POCIError): sword; cdecl;
|
|
AttrSet: function(trgthndlp: POCIHandle; trghndltyp: ub4;
|
|
attributep: Pointer; size: ub4; attrtype: ub4; errhp: POCIError): sword; cdecl;
|
|
SessionBegin: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
usrhp: POCISession; credt: ub4; mode: ub4): sword; cdecl;
|
|
SessionEnd: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
usrhp: POCISession; mode: ub4): sword; cdecl;
|
|
ErrorGet: function(hndlp: Pointer; recordno: ub4; sqlstate: text;
|
|
var errcodep: sb4; bufp: text; bufsiz: ub4; atype: ub4): sword; cdecl;
|
|
StmtPrepare: function(stmtp: POCIStmt; errhp: POCIError; stmt: text;
|
|
stmt_len: ub4; language:ub4; mode: ub4): sword; cdecl;
|
|
StmtExecute: function(svchp: POCISvcCtx; stmtp: POCIStmt;
|
|
errhp: POCIError; iters: ub4; rowoff: ub4; snap_in: POCISnapshot;
|
|
snap_out: POCISnapshot; mode: ub4): sword; cdecl;
|
|
StmtFetch: function(stmtp: POCIStmt; errhp: POCIError; nrows: ub4;
|
|
orientation: ub2; mode: ub4): sword; cdecl;
|
|
BindByPos: function(stmtp: POCIStmt; var bindpp: POCIBind;
|
|
errhp: POCIError; position: ub4; valuep: Pointer; value_sz: sb4; dty: ub2;
|
|
indp: Pointer; alenp: Pointer; rcodep: Pointer; maxarr_len: ub4;
|
|
curelep: Pointer; mode: ub4): sword; cdecl;
|
|
ParamGet: function(hndlp: Pointer; htype: ub4; errhp: POCIError;
|
|
var parmdpp: Pointer; pos: ub4): sword; cdecl;
|
|
TransStart: function(svchp: POCISvcCtx; errhp: POCIError; timeout: word;
|
|
flags: ub4): sword; cdecl;
|
|
TransRollback: function(svchp:POCISvcCtx; errhp:POCIError;
|
|
flags: ub4): sword; cdecl;
|
|
TransCommit: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
flags: ub4) :sword; cdecl;
|
|
DescriptorAlloc: function(parenth: POCIEnv; var descpp: pointer;
|
|
htype: ub4; xtramem_sz: integer; usrmempp: Pointer): sword; cdecl;
|
|
DescriptorFree: function(descp: Pointer; htype: ub4): sword; cdecl;
|
|
DateTimeConstruct: function(hndl: POCIEnv; err: POCIError;
|
|
datetime: POCIDateTime; year: sb2; month: ub1; day: ub1; hour: ub1;
|
|
min: ub1; sec: ub1; fsec: ub4; timezone: text;
|
|
timezone_length: size_t): sword; cdecl;
|
|
DateTimeGetDate: function(hndl: POCIEnv; err: POCIError;
|
|
const date: POCIDateTime; var year: sb2; var month: ub1;
|
|
var day: ub1): sword; cdecl;
|
|
DefineByPos: function(stmtp: POCIStmt; var defnpp: POCIDefine;
|
|
errhp: POCIError; position: ub4; valuep: Pointer; value_sz: sb4; dty: ub2;
|
|
indp: Pointer; rlenp: Pointer; rcodep: Pointer; mode: ub4): sword; cdecl;
|
|
LobGetLength: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
locp: POCILobLocator; var lenp: ub4): sword; cdecl;
|
|
LobGetChunkSize: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
locp: POCILobLocator; var chunk_size: ub4): sword; cdecl;
|
|
LobOpen: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
locp: POCILobLocator; mode: ub1): sword; cdecl;
|
|
LobRead: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
locp: POCILobLocator; var amtp: ub4; offset: ub4; bufp: Pointer; bufl: ub4;
|
|
ctxp: Pointer=nil; cbfp: Pointer=nil; csid: ub2=0; csfrm: ub1=SQLCS_IMPLICIT): sword; cdecl;
|
|
LobClose: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
locp: POCILobLocator): sword; cdecl;
|
|
LobWrite: function(svchp: POCISvcCtx; errhp: POCIError;
|
|
locp: POCILobLocator; var amtp: ub4; offset: ub4; bufp: Pointer; buflen: ub4;
|
|
piece: ub1; ctxp: Pointer=nil; cbfp: Pointer=nil; csid: ub2=0; csfrm: ub1=SQLCS_IMPLICIT): sword; cdecl;
|
|
NlsCharSetNameToID: function(env: POCIEnv; name: PUTF8Char): sword; cdecl;
|
|
StmtPrepare2: function(svchp: POCISvcCtx; var stmtp: POCIStmt; errhp: POCIError;
|
|
stmt: text; stmt_len: ub4; key: text; key_len: ub4;
|
|
language:ub4; mode: ub4): sword; cdecl;
|
|
StmtRelease: function(stmtp: POCIStmt; errhp: POCIError; key: text; key_len: ub4;
|
|
mode: ub4): sword; cdecl;
|
|
TypeByName: function(env: POCIEnv; errhp: POCIError; svchp: POCISvcCtx;
|
|
schema_name: text; s_length: ub4; type_name: text; t_length: ub4; version_name: text; v_length: ub4;
|
|
pin_duration: OCIDuration; get_option: ub4; var tdo: POCIType): sword; cdecl;
|
|
ObjectNew: function(env: POCIEnv; errhp: POCIError; svchp: POCISvcCtx; typecode: OCITypeCode;
|
|
tdo: POCIType; table: dvoid; duration: OCIDuration; value: boolean; var instance: dvoid): sword; cdecl;
|
|
ObjectFree: function(env: POCIEnv; errhp: POCIError; instance: dvoid; flag: ub2): sword; cdecl;
|
|
NumberFromInt: function(errhp: POCIError; inum: dvoid; inum_length: uword; inum_s_flag: uword;
|
|
var number: OCINumber): sword; cdecl;
|
|
StringAssignText : function(env: POCIEnv; errhp: POCIError; rhs: OraText; rhs_len: ub4;
|
|
var lhs: POCIString): sword; cdecl;
|
|
CollAppend: function(env: POCIEnv; errhp: POCIError; elem: dvoid; elemind: dvoid;
|
|
coll: POCIColl): sword; cdecl;
|
|
BindObject: function(bindp: POCIBind; errhp: POCIError; type_: POCIType; var pgvpp: dvoid;
|
|
pvszsp: pub4; indpp: pdvoid; indszp: pub4): sword; cdecl;
|
|
PasswordChange: function(svchp: POCISvcCtx; errhp: POCIError; const user_name: text; usernm_len: ub4;
|
|
const opasswd: text; opasswd_len: ub4; const npasswd: text; npasswd_len: sb4; mode: ub4): sword; cdecl;
|
|
public
|
|
// the client verion numbers
|
|
major_version, minor_version, update_num, patch_num, port_update_num: sword;
|
|
/// if OCI handles directly Int64 bound parameters (revision >= 11.2)
|
|
SupportsInt64Params: boolean;
|
|
/// OCI will call OCILobGetChunkSize when retrieving BLOB/CLOB content
|
|
// - is enabled by default, to avoid ORA-2481 errors when reading more than
|
|
// 96 MB of data, but you may disable chunking if you prefer by setting false
|
|
UseLobChunks: boolean;
|
|
/// load the oci.dll library
|
|
// - and retrieve all Oci*() addresses for OCI_ENTRIES[] items
|
|
constructor Create;
|
|
/// retrieve the client version as 'oci.dll rev. 11.2.0.1'
|
|
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}
|
|
procedure CheckSession(Conn: TSQLDBOracleConnection; Stmt: TSQLDBStatement;
|
|
Status: Integer; ErrorHandle: POCIError;
|
|
InfoRaiseException: Boolean=false; LogLevelNoRaise: TSynLogInfo=sllNone);
|
|
/// 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;
|
|
errhp: POCIError; locp: POCIDescriptor; out result: TBytes); overload;
|
|
/// retrieve some BLOB content, save it to the stream
|
|
procedure BlobFromDescriptorToStream(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; stream: TStream);
|
|
/// write some BLOB content, read it from the stream
|
|
procedure BlobToDescriptorFromStream(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; stream: TStream);
|
|
/// retrieve some CLOB/NCLOB content as UTF-8 text
|
|
function ClobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; ColumnDBForm: integer;
|
|
out Text: RawUTF8; TextResize: boolean=true): ub4;
|
|
end;
|
|
|
|
|
|
procedure TSQLDBOracleLib.RetrieveVersion;
|
|
begin
|
|
if major_version=0 then begin
|
|
ClientVersion(major_version, minor_version,
|
|
update_num, patch_num, port_update_num);
|
|
SupportsInt64Params := (major_version>11) or ((major_version=11) and (minor_version>1));
|
|
UseLobChunks := true;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleLib.BlobOpen(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor): ub4;
|
|
begin
|
|
result := 0;
|
|
Check(nil,Stmt,LobOpen(svchp,errhp,locp,OCI_LOB_READONLY),errhp);
|
|
try
|
|
Check(nil,Stmt,LobGetLength(svchp,errhp,locp,result),errhp);
|
|
except
|
|
Check(nil,Stmt,LobClose(svchp,errhp,locp),errhp);
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleLib.BlobRead(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; Blob: PByte; BlobLen: ub4;
|
|
csid: ub2; csfrm: ub1): integer;
|
|
var Read, ChunkSize: ub4;
|
|
Status: sword;
|
|
begin
|
|
result := BlobLen;
|
|
if BlobLen=0 then
|
|
exit; // nothing to read
|
|
if UseLobChunks then begin
|
|
Check(nil,Stmt,LobGetChunkSize(svchp,errhp,locp,ChunkSize),errhp);
|
|
result := 0;
|
|
repeat
|
|
Read := BlobLen;
|
|
Status := LobRead(svchp,errhp,locp,Read,1,Blob,ChunkSize,nil,nil,csid,csfrm);
|
|
inc(Blob,Read);
|
|
inc(result,Read);
|
|
until Status<>OCI_NEED_DATA;
|
|
Check(nil,Stmt,Status,errhp);
|
|
end else
|
|
Check(nil,Stmt,LobRead(svchp,errhp,locp,result,1,Blob,result,nil,nil,csid,csfrm),errhp);
|
|
end;
|
|
|
|
function TSQLDBOracleLib.BlobReadToStream(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; stream: TStream; BlobLen: ub4;
|
|
csid: ub2; csfrm: ub1): integer;
|
|
var Read, ChunkSize: ub4;
|
|
Status: sword;
|
|
tmp: RawByteString;
|
|
begin
|
|
result := BlobLen;
|
|
if BlobLen=0 then
|
|
exit; // nothing to read
|
|
if UseLobChunks then begin
|
|
Check(nil,Stmt,LobGetChunkSize(svchp,errhp,locp,ChunkSize),errhp);
|
|
SetLength(tmp,ChunkSize*SynDBOracleBlobChunksCount);
|
|
result := 0;
|
|
repeat
|
|
Read := BlobLen;
|
|
Status := LobRead(svchp,errhp,locp,Read,1,pointer(tmp),length(tmp),nil,nil,csid,csfrm);
|
|
stream.WriteBuffer(pointer(tmp)^,Read);
|
|
inc(result,Read);
|
|
until Status<>OCI_NEED_DATA;
|
|
Check(nil,Stmt,Status,errhp);
|
|
end else begin
|
|
SetLength(tmp,BlobLen);
|
|
Check(nil,Stmt,LobRead(svchp,errhp,locp,result,1,pointer(tmp),result,nil,nil,csid,csfrm),errhp);
|
|
stream.WriteBuffer(pointer(tmp)^,result);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleLib.BlobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; out result: RawByteString);
|
|
var Len, Read: ub4;
|
|
begin
|
|
Len := BlobOpen(Stmt,svchp,errhp,locp);
|
|
try
|
|
SetLength(result,Len);
|
|
Read := BlobRead(Stmt,svchp,errhp,locp,pointer(result),Len);
|
|
if Read<>Len then
|
|
SetLength(result,Read);
|
|
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
|
|
Len := BlobOpen(Stmt,svchp,errhp,locp);
|
|
try
|
|
SetLength(result,Len);
|
|
Read := BlobRead(Stmt,svchp,errhp,locp,pointer(result),Len);
|
|
if Read<>Len then
|
|
SetLength(result,Read);
|
|
finally
|
|
Check(nil,Stmt,LobClose(svchp,errhp,locp),errhp);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleLib.BlobFromDescriptorToStream(Stmt: TSQLDBStatement;
|
|
svchp: POCISvcCtx; errhp: POCIError; locp: POCIDescriptor; stream: TStream);
|
|
var Len: ub4;
|
|
begin
|
|
Len := BlobOpen(Stmt,svchp,errhp,locp);
|
|
try
|
|
BlobReadToStream(Stmt,svchp,errhp,locp,stream,Len);
|
|
finally
|
|
Check(nil,Stmt,LobClose(svchp,errhp,locp),errhp);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleLib.BlobToDescriptorFromStream(Stmt: TSQLDBStatement;
|
|
svchp: POCISvcCtx; errhp: POCIError; locp: POCIDescriptor; stream: TStream);
|
|
begin
|
|
BlobWriteFromStream(Stmt,svchp,errhp,locp,stream,stream.Size);
|
|
end;
|
|
|
|
function TSQLDBOracleLib.BlobWriteFromStream(Stmt: TSQLDBStatement;
|
|
svchp: POCISvcCtx; errhp: POCIError; locp: POCIDescriptor; stream: TStream;
|
|
BlobLen: ub4; csid: ub2; csfrm: ub1): integer;
|
|
var ChunkSize, l_Read, l_Write, l_Offset: Longint;
|
|
tmp: RawByteString;
|
|
begin
|
|
Check(nil,Stmt,LobGetChunkSize(svchp,errhp,locp,ChunkSize),errhp);
|
|
SetLength(tmp,ChunkSize*SynDBOracleBlobChunksCount);
|
|
l_Offset := 1;
|
|
while stream.Position<stream.Size do begin
|
|
l_Read := stream.Read(pointer(tmp)^,length(tmp));
|
|
l_Write := l_Read;
|
|
Check(nil,Stmt,LobWrite(svchp,errhp,locp,l_Write,l_Offset,
|
|
pointer(tmp),l_Read,OCI_ONE_PIECE),errhp);
|
|
inc(l_Offset,l_Write);
|
|
end;
|
|
result := l_Offset;
|
|
end;
|
|
|
|
function TSQLDBOracleLib.ClobFromDescriptor(Stmt: TSQLDBStatement; svchp: POCISvcCtx;
|
|
errhp: POCIError; locp: POCIDescriptor; ColumnDBForm: integer;
|
|
out Text: RawUTF8; TextResize: boolean): ub4;
|
|
var Len: ub4;
|
|
begin
|
|
Len := BlobOpen(Stmt,svchp,errhp,locp);
|
|
try
|
|
if Len>0 then begin
|
|
Len := Len*3; // max UTF-8 size according to number of characters
|
|
SetLength(Text,Len);
|
|
result := BlobRead(Stmt,svchp,errhp,locp,pointer(Text),Len,OCI_CHARSET_UTF8,ColumnDBForm);
|
|
if TextResize then
|
|
SetLength(Text,result) else
|
|
Text[result+1] := #0; // ensure ASCIIZ (e.g. when escaping to JSON)
|
|
end else
|
|
result := 0;
|
|
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';
|
|
OCI_STILL_EXECUTING:
|
|
msg := 'OCI_STILL_EXECUTING';
|
|
OCI_CONTINUE:
|
|
msg := 'OCI_CONTINUE';
|
|
end;
|
|
if LogLevelNoRaise<>sllNone then
|
|
SynDBLog.Add.Log(LogLevelNoRaise,msg,self) 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;
|
|
|
|
procedure TSQLDBOracleLib.CheckSession(Conn: TSQLDBOracleConnection; Stmt: TSQLDBStatement; Status: Integer;
|
|
ErrorHandle: POCIError; InfoRaiseException: Boolean; LogLevelNoRaise: TSynLogInfo);
|
|
var msg: RawUTF8;
|
|
tmp: array[0..3071] of AnsiChar;
|
|
L, ErrNum: integer;
|
|
begin
|
|
if Status <> OCI_ERROR then
|
|
Check(Conn, Stmt, Status, ErrorHandle, InfoRaiseException, LogLevelNoRaise) else 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 ErrNum = 28001 then
|
|
if Conn <> nil then
|
|
if Conn.PasswordChange then
|
|
Exit;
|
|
if LogLevelNoRaise<>sllNone then
|
|
SynDBLog.Add.Log(LogLevelNoRaise,msg,self) else
|
|
if Stmt=nil then
|
|
raise ESQLDBOracle.CreateUTF8('% error: %',[self,msg]) else
|
|
raise ESQLDBOracle.CreateUTF8('% error: %',[Stmt,msg]);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleLib.ClientRevision: RawUTF8;
|
|
const EXE_FMT: PUTF8Char = '% rev. %.%.%.%';
|
|
begin
|
|
if self=nil then
|
|
result := '' else begin
|
|
RetrieveVersion;
|
|
result := FormatUTF8(EXE_FMT,[fLibraryPath,
|
|
major_version,minor_version,update_num,patch_num]);
|
|
end;
|
|
end;
|
|
|
|
const
|
|
// http://download.oracle.com/docs/cd/B19306_01/server.102/b14225/applocaledata.htm#i635016
|
|
// http://www.mydul.net/charsets.html
|
|
CODEPAGES: array[0..26] of record
|
|
Num: cardinal; Charset: cardinal; Text: PUTF8Char end = (
|
|
(Num: 1252; Charset: OCI_WE8MSWIN1252; Text: 'WE8MSWIN1252'),
|
|
(Num: 1250; Charset: 170; Text: 'EE8MSWIN1250'),
|
|
(Num: 1251; Charset: 171; Text: 'CL8MSWIN1251'),
|
|
(Num: 1253; Charset: 174; Text: 'EL8MSWIN1253'),
|
|
(Num: 1254; Charset: 177; Text: 'TR8MSWIN1254'),
|
|
(Num: 1255; Charset: 175; Text: 'IW8MSWIN1255'),
|
|
(Num: 1256; Charset: 560; Text: 'AR8MSWIN1256'),
|
|
(Num: 1257; Charset: 179; Text: 'BLT8MSWIN1257'),
|
|
(Num: 874; Charset: 41; Text: 'TH8TISASCII'),
|
|
(Num: 932; Charset: 832; Text: 'JA16SJIS'),
|
|
(Num: 949; Charset: 846; Text: 'KO16MSWIN949'),
|
|
(Num: 936; Charset: 852; Text: 'ZHS16GBK'),
|
|
(Num: 950; Charset: 867; Text: 'ZHT16MSWIN950'),
|
|
(Num: 1258; Charset: 45; Text: 'VN8MSWIN1258'),
|
|
(Num: CP_UTF8; CharSet: OCI_UTF8; Text: 'UTF8'),
|
|
(Num: CP_UTF16; CharSet: OCI_UTF16ID; Text: 'UTF16'),
|
|
(Num: 437; CharSet: 4; Text: 'US8PC437'),
|
|
(Num: 850; CharSet: 10; Text: 'WE8PC850'),
|
|
(Num: 858; CharSet: 28; Text: 'WE8PC858'),
|
|
(Num: 921; Charset: 176; Text: 'LT8MSWIN921'),
|
|
(Num: 923; Charset: 172; Text: 'ET8MSWIN923'),
|
|
// handle some aliases of code page Num values
|
|
(Num: CP_UTF8; CharSet: OCI_AL32UTF8; Text: 'AL32UTF8'),
|
|
(Num: CP_UTF16; CharSet: 2000; Text: 'AL16UTF16'),
|
|
(Num: CP_UTF16; CharSet: 2002; Text: 'AL16UTF16LE'),
|
|
// wrong approximation (to be fixed)
|
|
(Num: 932; Charset: 830; Text: 'JA16EUC'),
|
|
(Num: 1252; Charset: 46; Text: 'WE8ISO8859P15'),
|
|
(Num: 1252; Charset: 31; Text: 'WE8ISO8859P1'));
|
|
|
|
function SimilarCharSet(aCharset1, aCharset2: cardinal): Boolean;
|
|
var i1,i2: integer;
|
|
begin
|
|
result := true;
|
|
if aCharset1=aCharset2 then
|
|
exit;
|
|
for i1 := 0 to high(CODEPAGES) do
|
|
if CODEPAGES[i1].Charset=aCharset1 then
|
|
for i2 := 0 to High(CODEPAGES) do
|
|
if (CODEPAGES[i2].Charset=aCharset2) and
|
|
(CODEPAGES[i1].Num=CODEPAGES[i2].Num) then
|
|
exit; // aliases are allowed
|
|
result := false;
|
|
end;
|
|
|
|
function OracleCharSetName(aCharsetID: cardinal): PUTF8Char;
|
|
var i: integer;
|
|
begin
|
|
for i := 0 to high(CODEPAGES) do
|
|
with CODEPAGES[i] do
|
|
if Charset=aCharsetID then begin
|
|
result := Text;
|
|
exit;
|
|
end;
|
|
result := '?';
|
|
end;
|
|
|
|
function CharSetIDToCodePage(aCharSetID: cardinal): cardinal;
|
|
var i: integer;
|
|
begin
|
|
for i := 0 to high(CODEPAGES) do
|
|
with CODEPAGES[i] do
|
|
if Charset=aCharsetID then begin
|
|
result := Num;
|
|
exit;
|
|
end;
|
|
result := GetACP; // return the default OS code page if not found
|
|
end;
|
|
|
|
function TSQLDBOracleLib.CodePageToCharSetID(env: pointer;
|
|
aCodePage: cardinal): cardinal;
|
|
var ocp: PUTF8Char;
|
|
i: integer;
|
|
nlslang: AnsiString;
|
|
begin
|
|
case aCodePage of
|
|
0: begin
|
|
nlslang := AnsiString(GetEnvironmentVariable('NLS_LANG'));
|
|
if nlslang<>'' then
|
|
result := NlsCharSetNameToID(env,pointer(nlslang)) else
|
|
result := CodePageToCharSetID(env,GetACP);
|
|
end;
|
|
CP_UTF8:
|
|
result := OCI_CHARSET_UTF8;
|
|
CP_UTF16:
|
|
result := OCI_UTF16ID;
|
|
else begin
|
|
ocp := CODEPAGES[0].Text; // default is MS Windows Code Page 1252
|
|
for i := 0 to high(CODEPAGES) do
|
|
if aCodePage=CODEPAGES[i].Num then begin
|
|
ocp := CODEPAGES[i].Text;
|
|
break;
|
|
end;
|
|
result := NlsCharSetNameToID(env,ocp);
|
|
end;
|
|
end;
|
|
if result=0 then
|
|
result := OCI_WE8MSWIN1252;
|
|
end;
|
|
|
|
constructor TSQLDBOracleLib.Create;
|
|
const LIBNAME = {$ifdef MSWINDOWS}'oci.dll'{$else}'libclntsh.so'{$endif};
|
|
var P: PPointer;
|
|
i: integer;
|
|
l1, l2, l3: TFileName;
|
|
begin
|
|
if (SynDBOracleOCIpath<>'') and DirectoryExists(SynDBOracleOCIpath) then
|
|
l1 := ExtractFilePath(ExpandFileName(SynDBOracleOCIpath+PathDelim))+LIBNAME;
|
|
l2 := ExeVersion.ProgramFilePath+LIBNAME;
|
|
if not FileExists(l2) then begin
|
|
l2 := ExeVersion.ProgramFilePath+'OracleInstantClient';
|
|
if not DirectoryExists(l2) then begin
|
|
l2 := ExeVersion.ProgramFilePath+'OCI';
|
|
if not DirectoryExists(l2) then
|
|
l2 := ExeVersion.ProgramFilePath+'Oracle';
|
|
end;
|
|
l2 := l2+PathDelim+LIBNAME;
|
|
end;
|
|
l3 := GetEnvironmentVariable('ORACLE_HOME');
|
|
if l3<>'' then
|
|
l3 := IncludeTrailingPathDelimiter(l3)+'bin'+PathDelim+LIBNAME;
|
|
TryLoadLibrary([l1, l2, l3, LIBNAME], ESQLDBOracle);
|
|
P := @@ClientVersion;
|
|
for i := 0 to High(OCI_ENTRIES) do begin
|
|
P^ := GetProcAddress(fHandle,OCI_ENTRIES[i]);
|
|
if P^=nil then begin
|
|
FreeLibrary(fHandle);
|
|
fHandle := 0;
|
|
raise ESQLDBOracle.CreateUTF8('Invalid %: missing %',[LIBNAME,OCI_ENTRIES[i]]);
|
|
end;
|
|
inc(P);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
OCI: TSQLDBOracleLib = nil;
|
|
|
|
|
|
{ TSQLDBOracleConnectionProperties }
|
|
|
|
class function TSQLDBOracleConnectionProperties.ExtractTnsName(
|
|
const aServerName: RawUTF8): RawUTF8;
|
|
var i: integer;
|
|
begin
|
|
i := PosExChar('/',aServerName);
|
|
if i=0 then
|
|
result := aServerName else
|
|
result := copy(aServerName,i+1,100);
|
|
end;
|
|
|
|
function TSQLDBOracleConnectionProperties.IsCachable(P: PUTF8Char): boolean;
|
|
begin
|
|
result := false; // no client-side cache, only server-side
|
|
end;
|
|
|
|
constructor TSQLDBOracleConnectionProperties.Create(const aServerName,
|
|
aDatabaseName, aUserID, aPassWord: RawUTF8);
|
|
begin
|
|
fDBMS := dOracle;
|
|
fBatchSendingAbilities := [cCreate,cUpdate,cDelete]; // array DML feature
|
|
fBatchMaxSentAtOnce := 10000; // iters <= 32767 for better performance
|
|
inherited Create(aServerName,'',aUserID,aPassWord);
|
|
GlobalLock;
|
|
try
|
|
if OCI=nil then
|
|
GarbageCollectorFreeAndNil(OCI,TSQLDBOracleLib.Create);
|
|
finally
|
|
GlobalUnLock;
|
|
end;
|
|
fBlobPrefetchSize := 4096;
|
|
fRowsPrefetchSize := 128*1024;
|
|
fStatementCacheSize := 30; // default is 20
|
|
fInternalBufferSize := 128*1024; // 128 KB
|
|
fEnvironmentInitializationMode := OCI_EVENTS or OCI_THREADED or OCI_OBJECT;
|
|
end;
|
|
|
|
function TSQLDBOracleConnectionProperties.GetClientVersion: RawUTF8;
|
|
begin
|
|
result := OCI.ClientRevision;
|
|
end;
|
|
|
|
procedure TSQLDBOracleConnectionProperties.GetForeignKeys;
|
|
begin
|
|
with Execute(
|
|
'select b.owner||''.''||b.table_name||''.''||b.column_name col,'+
|
|
' c.owner||''.''||c.table_name||''.''||c.column_name ref'+
|
|
' from all_cons_columns b, all_cons_columns c, all_constraints a'+
|
|
' where b.constraint_name=a.constraint_name and a.owner=b.owner '+
|
|
'and b.position=c.position and c.constraint_name=a.r_constraint_name '+
|
|
'and c.owner=a.r_owner and a.constraint_type = ''R''',[]) do
|
|
while Step do
|
|
fForeignKeys.Add(ColumnUTF8(0),ColumnUTF8(1));
|
|
end;
|
|
|
|
function TSQLDBOracleConnectionProperties.NewConnection: TSQLDBConnection;
|
|
begin
|
|
result := TSQLDBOracleConnection.Create(self);
|
|
end;
|
|
|
|
procedure TSQLDBOracleConnectionProperties.PasswordChanged(const ANewPassword: RawUTF8);
|
|
begin
|
|
SynDBLog.Add.Log(sllDB, 'PasswordChanged method called',self);
|
|
fPassWord := ANewPassword;
|
|
if Assigned(FOnPasswordChanged) then
|
|
FOnPasswordChanged(Self);
|
|
end;
|
|
|
|
function TSQLDBOracleConnectionProperties.SQLLimitClause(AStmt: TSynTableStatement): TSQLDBDefinitionLimitClause;
|
|
begin
|
|
if AStmt.OrderByField<>nil then begin
|
|
result.Position := posOuter;
|
|
result.InsertFmt := 'select * from (%) where rownum<=%';
|
|
end else
|
|
result := inherited SQLLimitClause(AStmt);
|
|
end;
|
|
|
|
|
|
{ TSQLDBOracleConnection }
|
|
|
|
procedure TSQLDBOracleConnection.Commit;
|
|
begin
|
|
inherited Commit;
|
|
if fTrans=nil then
|
|
raise ESQLDBOracle.CreateUTF8('Invalid %.Commit call',[self]);
|
|
try
|
|
OCI.Check(self,nil,OCI.TransCommit(fContext,fError,OCI_DEFAULT),fError);
|
|
except
|
|
inc(fTransactionCount); // the transaction is still active
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleConnection.Connect;
|
|
var log: ISynLog;
|
|
Props: TSQLDBOracleConnectionProperties;
|
|
mode: ub4;
|
|
msg: RawUTF8;
|
|
r: sword;
|
|
const
|
|
type_owner_name: RawUTF8 = 'SYS';
|
|
type_NymberListName: RawUTF8 = 'ODCINUMBERLIST';
|
|
type_Varchar2ListName: RawUTF8 = 'ODCIVARCHAR2LIST';
|
|
type_Credential: array[boolean] of integer = (OCI_CRED_RDBMS,OCI_CRED_EXT);
|
|
begin
|
|
log := SynDBLog.Enter(self,'Connect');
|
|
Disconnect; // force fTrans=fError=fServer=fContext=nil
|
|
Props := Properties as TSQLDBOracleConnectionProperties;
|
|
with OCI do
|
|
try
|
|
if fEnv=nil then begin
|
|
// will use UTF-8 encoding by default, in a multi-threaded context
|
|
// OCI_EVENTS is needed to support Oracle RAC Connection Load Balancing
|
|
r := EnvNlsCreate(fEnv,Props.EnvironmentInitializationMode,
|
|
nil,nil,nil,nil,0,nil,OCI_CHARSET_UTF8,OCI_CHARSET_UTF8);
|
|
if r <> OCI_SUCCESS then
|
|
raise ESQLDBOracle.CreateUTF8('OCIEnvNlsCreate fails with code %', [r]);
|
|
end;
|
|
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),
|
|
length(Props.Password),OCI_ATTR_PASSWORD,fError);
|
|
AttrSet(fSession,OCI_HTYPE_SESSION,@Props.fBlobPrefetchSize,0,
|
|
OCI_ATTR_DEFAULT_LOBPREFETCH_SIZE,fError);
|
|
AttrSet(fContext,OCI_HTYPE_SVCCTX,fSession,0,OCI_ATTR_SESSION,fError);
|
|
HandleAlloc(fEnv,fTrans,OCI_HTYPE_TRANS);
|
|
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;
|
|
if Props.UserID='SYS' then
|
|
mode := mode or OCI_SYSDBA;
|
|
CheckSession(self,nil,SessionBegin(fContext,fError,fSession,type_Credential[Props.UseWallet],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
|
|
Execute('SELECT NLS_CHARSET_ID(PROPERTY_VALUE) FROM DATABASE_PROPERTIES'+
|
|
' WHERE PROPERTY_NAME=''NLS_CHARACTERSET''',true);
|
|
if Step then
|
|
fOCICharSet := ColumnInt(0) else
|
|
fOCICharSet := CodePageToCharSetID(fEnv,0); // retrieve from NLS_LANG
|
|
except // on error, retrieve from NLS_LANG
|
|
fOCICharSet := CodePageToCharSetID(fEnv,0);
|
|
end;
|
|
finally
|
|
Free;
|
|
end;
|
|
fAnsiConvert := TSynAnsiConvert.Engine(CharSetIDToCodePage(fOCICharSet));
|
|
end;
|
|
if Props.UseWallet then
|
|
msg := 'using Oracle Wallet' else
|
|
msg := 'as '+Props.UserID;
|
|
if log<>nil then
|
|
log.log(sllInfo,'Connected to % % with %, codepage % (%/%)',
|
|
[Props.ServerName,msg,Props.ClientVersion,fAnsiConvert.CodePage,
|
|
fOCICharSet,OracleCharSetName(fOCICharSet)],self);
|
|
with NewStatement do
|
|
try // ORM will send date/time as ISO8601 text -> force encoding
|
|
Execute('ALTER SESSION SET NLS_DATE_FORMAT=''YYYY-MM-DD-HH24:MI:SS''',false);
|
|
finally
|
|
Free;
|
|
end;
|
|
with NewStatement do
|
|
try // currency content is returned as SQLT_STR -> force '.' decimal separator
|
|
Execute('alter session set NLS_NUMERIC_CHARACTERS = ". "',false);
|
|
finally
|
|
Free;
|
|
end;
|
|
//Check(TransStart(fContext,fError,0,OCI_DEFAULT),fError);
|
|
inherited Connect; // notify any re-connection
|
|
except
|
|
on E: Exception do begin
|
|
if log<>nil then
|
|
log.log(sllError,E);
|
|
Disconnect; // clean up on fail
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
constructor TSQLDBOracleConnection.Create(aProperties: TSQLDBConnectionProperties);
|
|
var log: ISynLog;
|
|
begin
|
|
log := SynDBLog.Enter(self,'Create');
|
|
if not aProperties.InheritsFrom(TSQLDBOracleConnectionProperties) then
|
|
raise ESQLDBOracle.CreateUTF8('Invalid %.Create(%)',[self,aProperties]);
|
|
OCI.RetrieveVersion;
|
|
inherited;
|
|
end;
|
|
|
|
destructor TSQLDBOracleConnection.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
if (OCI<>nil) and (fEnv<>nil) then
|
|
OCI.HandleFree(fEnv,OCI_HTYPE_ENV);
|
|
end;
|
|
|
|
procedure TSQLDBOracleConnection.Disconnect;
|
|
begin
|
|
try
|
|
inherited Disconnect; // flush any cached statement
|
|
finally
|
|
if (fError<>nil) and (OCI<>nil) then
|
|
with SynDBLog.Enter(self,'Disconnect'), OCI do begin
|
|
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;
|
|
fServer := nil;
|
|
fError := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleConnection.IsConnected: boolean;
|
|
begin
|
|
result := fTrans<>nil;
|
|
end;
|
|
|
|
function TSQLDBOracleConnection.NewStatement: TSQLDBStatement;
|
|
begin
|
|
result := TSQLDBOracleStatement.Create(self);
|
|
if fProperties.UseCache then // client-side cache is disabled in this unit
|
|
TSQLDBOracleStatement(result).fUseServerSideStatementCache := true;
|
|
end;
|
|
|
|
function TSQLDBOracleConnection.PasswordChange: Boolean;
|
|
var password: RawUTF8;
|
|
begin
|
|
Result := False;
|
|
if Properties is TSQLDBOracleConnectionProperties then
|
|
if Assigned(TSQLDBOracleConnectionProperties(Properties).OnPasswordExpired) then begin
|
|
password := Properties.PassWord;
|
|
if TSQLDBOracleConnectionProperties(Properties).OnPasswordExpired(Self, password) then
|
|
OCI.Check(Self, nil, OCI.PasswordChange(fContext, fError, pointer(Properties.UserID),
|
|
Length(Properties.UserID), Pointer(Properties.PassWord), Length(Properties.PassWord),
|
|
Pointer(password), Length(password), OCI_DEFAULT or OCI_AUTH), fError);
|
|
TSQLDBOracleConnectionProperties(Properties).PasswordChanged(password);
|
|
Result := True;
|
|
end;
|
|
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;
|
|
var log: ISynLog;
|
|
begin
|
|
log := SynDBLog.Enter(self,'StartTransaction');
|
|
if TransactionCount>0 then
|
|
raise ESQLDBOracle.CreateUTF8('Invalid %.StartTransaction: nested '+
|
|
'transactions are not supported by the Oracle driver',[self]);
|
|
try
|
|
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);
|
|
except
|
|
on E: Exception do begin
|
|
if (Properties as TSQLDBOracleConnectionProperties).IgnoreORA01453OnStartTransaction and
|
|
(Pos('ORA-01453', E.Message ) > 0) then begin
|
|
if Log<>nil then
|
|
Log.Log(sllWarning, 'It seems that we use DBLink, and Oracle implicitly started transaction. ORA-01453 ignored');
|
|
end else begin
|
|
if fTransactionCount > 0 then
|
|
dec(fTransactionCount);
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleConnection.STRToUTF8(P: PAnsiChar; var result: RawUTF8;
|
|
ColumnDBCharSet, ColumnDBForm: cardinal);
|
|
var L: integer;
|
|
begin
|
|
L := StrLen(PUTF8Char(P));
|
|
if (L=0) or (ColumnDBCharSet=OCI_AL32UTF8) or (ColumnDBCharSet=OCI_UTF8) or
|
|
(ColumnDBForm=SQLCS_NCHAR) then
|
|
FastSetString(result,P,L) else
|
|
result := fAnsiConvert.AnsiBufferToRawUTF8(P,L);
|
|
end;
|
|
|
|
{$ifndef UNICODE}
|
|
procedure TSQLDBOracleConnection.STRToAnsiString(P: PAnsiChar; var result: AnsiString;
|
|
ColumnDBCharSet, ColumnDBForm: cardinal);
|
|
var L: integer;
|
|
begin
|
|
L := StrLen(PUTF8Char(P));
|
|
if (L=0) or ((ColumnDBCharSet<>OCI_AL32UTF8) and (ColumnDBCharSet<>OCI_UTF8) and
|
|
(ColumnDBForm<>SQLCS_NCHAR) and (fAnsiConvert.CodePage=GetACP)) then
|
|
SetString(result,P,L) else
|
|
result := CurrentAnsiConvert.AnsiToAnsi(fAnsiConvert,P,L);
|
|
end;
|
|
{$endif UNICODE}
|
|
|
|
|
|
{ TSQLDBOracleStatement }
|
|
|
|
function TSQLDBOracleStatement.ColumnBlob(Col: integer): RawByteString;
|
|
var C: PSQLDBColumnProperty;
|
|
V: PPOCIDescriptor;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V=nil then // column is NULL
|
|
result := '' else
|
|
if C^.ColumnType=ftBlob then
|
|
if C^.ColumnValueInlined then
|
|
SetString(result,PAnsiChar(V),C^.ColumnValueDBSize) else
|
|
// conversion from POCILobLocator
|
|
with TSQLDBOracleConnection(Connection) do
|
|
OCI.BlobFromDescriptor(self,fContext,fError,V^,result) else
|
|
// need conversion to destination type
|
|
ColumnToTypedValue(Col,ftBlob,result);
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnBlobBytes(Col: integer): TBytes;
|
|
var C: PSQLDBColumnProperty;
|
|
V: PPOCIDescriptor;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V=nil then // column is NULL
|
|
result := nil else
|
|
if C^.ColumnType=ftBlob then
|
|
if C^.ColumnValueInlined then begin
|
|
SetLength(result,C^.ColumnValueDBSize);
|
|
MoveFast(V^,pointer(result)^,C^.ColumnValueDBSize);
|
|
end else
|
|
// conversion from POCILobLocator
|
|
with TSQLDBOracleConnection(Connection) do
|
|
OCI.BlobFromDescriptor(self,fContext,fError,V^,result) else
|
|
// need conversion to destination type
|
|
result := inherited ColumnBlobBytes(Col);
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.ColumnBlobToStream(Col: integer; Stream: TStream);
|
|
var C: PSQLDBColumnProperty;
|
|
V: PPOCIDescriptor;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V<>nil then // column is NULL
|
|
if C^.ColumnType=ftBlob then
|
|
if C^.ColumnValueInlined then
|
|
Stream.WriteBuffer(V^,C^.ColumnValueDBSize) else
|
|
// conversion from POCILobLocator
|
|
with TSQLDBOracleConnection(Connection) do
|
|
OCI.BlobFromDescriptorToStream(self,fContext,fError,V^,stream);
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.ColumnBlobFromStream(Col: integer; Stream: TStream);
|
|
var C: PSQLDBColumnProperty;
|
|
V: PPOCIDescriptor;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V<>nil then begin // V=nil means column is NULL
|
|
if C^.ColumnType=ftBlob then
|
|
if C^.ColumnValueInlined then
|
|
raise ESQLDBOracle.CreateUTF8('%.ColumnBlobFromStream(ColumnValueInlined) '+
|
|
'not supported',[self]) else
|
|
// conversion from POCILobLocator
|
|
with TSQLDBOracleConnection(Connection) do
|
|
OCI.BlobToDescriptorFromStream(self,fContext,fError,V^,stream);
|
|
end else
|
|
raise ESQLDBOracle.CreateUTF8('Unexpected %.ColumnBlobFromStream(null): '+
|
|
'use EMPTY_BLOB() to initialize it',[self]);
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnCurrency(Col: integer): currency;
|
|
var C: PSQLDBColumnProperty;
|
|
V: PUTF8Char;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V=nil then // column is NULL
|
|
result := 0 else
|
|
if C^.ColumnType=ftCurrency then // encoded as SQLT_STR
|
|
PInt64(@result)^ := StrToCurr64(V) else
|
|
ColumnToTypedValue(Col,ftCurrency,result);
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnDateTime(Col: integer): TDateTime;
|
|
var C: PSQLDBColumnProperty;
|
|
V: POracleDate;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V=nil then // column is NULL
|
|
result := 0 else
|
|
if C^.ColumnType=ftDate then
|
|
if C^.ColumnValueDBType=SQLT_DAT then
|
|
// types match -> fast direct retrieval
|
|
result := V^.ToDateTime else
|
|
// convert from SQLT_INTERVAL_YM/SQLT_INTERVAL_DS text
|
|
IntervalTextToDateTimeVar(pointer(V),result) else
|
|
// need conversion to destination type
|
|
ColumnToTypedValue(Col,ftDate,result);
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnDouble(Col: integer): double;
|
|
var C: PSQLDBColumnProperty;
|
|
V: pointer;
|
|
Curr: currency;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V=nil then // column is NULL
|
|
result := 0 else
|
|
case C^.ColumnType of // optimized for ToDataSet() in SynDBVCL.pas
|
|
ftDouble: result := unaligned(PDouble(V)^);
|
|
ftInt64: result := PInt64(V)^;
|
|
ftCurrency: begin
|
|
PInt64(@Curr)^ := StrToCurr64(V); // handle '.5' - not GetExtended()
|
|
result := Curr;
|
|
end;
|
|
else // need conversion to destination type
|
|
ColumnToTypedValue(Col,ftDouble,result);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnInt(Col: integer): Int64;
|
|
var C: PSQLDBColumnProperty;
|
|
V: pointer;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V=nil then // column is NULL
|
|
result := 0 else
|
|
case C^.ColumnType of
|
|
ftInt64:
|
|
if C^.ColumnValueDBType=SQLT_INT then
|
|
result := PInt64(V)^ else
|
|
SetInt64(V,result);
|
|
ftCurrency:
|
|
SetInt64(V,result); // encoded as SQLT_STR
|
|
else
|
|
ColumnToTypedValue(Col,ftInt64,result);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnNull(Col: integer): boolean;
|
|
var C: PSQLDBColumnProperty;
|
|
begin
|
|
result := GetCol(Col,C)=nil;
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.ColumnsToJSON(WR: TJSONWriter);
|
|
var V: pointer;
|
|
col, indicator: integer;
|
|
tmp: array[0..31] of AnsiChar;
|
|
U: RawUTF8;
|
|
begin // dedicated version to avoid as much memory allocation than possible
|
|
if not Assigned(fStatement) or (CurrentRow<=0) then
|
|
raise ESQLDBOracle.CreateUTF8('%.ColumnsToJSON() with no prior Step',[self]);
|
|
if WR.Expand then
|
|
WR.Add('{');
|
|
for col := 0 to fColumnCount-1 do // fast direct conversion from OleDB buffer
|
|
with fColumns[col] do begin
|
|
if WR.Expand then
|
|
WR.AddFieldName(ColumnName); // add '"ColumnName":'
|
|
indicator := PSmallIntArray(fRowBuffer)[cardinal(col)*fRowCount+fRowFetchedCurrent];
|
|
if (indicator=-1) or (ColumnType=ftNull) then // ftNull for SQLT_RSET
|
|
WR.AddShort('null') else begin
|
|
if indicator<>0 then
|
|
LogTruncatedColumn(fColumns[col]);
|
|
V := @fRowBuffer[ColumnAttr+fRowFetchedCurrent*ColumnValueDBSize];
|
|
case ColumnType of
|
|
ftInt64:
|
|
if ColumnValueDBType=SQLT_INT then
|
|
WR.Add(PInt64(V)^) else
|
|
WR.AddNoJSONEscape(V); // already as SQLT_STR
|
|
ftDouble:
|
|
WR.AddDouble(unaligned(PDouble(V)^));
|
|
ftCurrency:
|
|
WR.AddFloatStr(V); // already as SQLT_STR
|
|
ftDate:
|
|
if ColumnValueDBType=SQLT_DAT then
|
|
WR.AddNoJSONEscape(@tmp,POracleDate(V)^.ToIso8601(tmp)) else begin
|
|
WR.Add('"'); // SQLT_INTERVAL_YM/SQLT_INTERVAL_DS
|
|
WR.AddDateTime(IntervalTextToDateTime(V));
|
|
WR.Add('"');
|
|
end;
|
|
ftUTF8: begin
|
|
WR.Add('"');
|
|
with TSQLDBOracleConnection(Connection) do
|
|
if ColumnValueInlined then
|
|
STRToUTF8(V,U,ColumnValueDBCharSet,ColumnValueDBForm) else
|
|
OCI.ClobFromDescriptor(self,fContext,fError,PPOCIDescriptor(V)^,ColumnValueDBForm,U,false);
|
|
WR.AddJSONEscape(pointer(U));
|
|
WR.Add('"');
|
|
end;
|
|
ftBlob:
|
|
if fForceBlobAsNull then
|
|
WR.AddShort('null') else
|
|
if ColumnValueInlined then
|
|
SetString(U,PAnsiChar(V),ColumnValueDBSize) else begin
|
|
with TSQLDBOracleConnection(Connection) do
|
|
OCI.BlobFromDescriptor(self,fContext,fError,PPOCIDescriptor(V)^,RawByteString(U));
|
|
WR.WrBase64(Pointer(U),length(U),true);
|
|
end;
|
|
else assert(false);
|
|
end;
|
|
end;
|
|
WR.Add(',');
|
|
end;
|
|
WR.CancelLastComma; // cancel last ','
|
|
if WR.Expand then
|
|
WR.Add('}');
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.ColumnToSQLVar(Col: Integer; var Value: TSQLVar;
|
|
var Temp: RawByteString);
|
|
var C: PSQLDBColumnProperty;
|
|
V: pointer;
|
|
NoDecimal: boolean;
|
|
begin // dedicated version to avoid as much memory allocation than possible
|
|
Value.Options := [];
|
|
V := GetCol(Col,C);
|
|
if V=nil then
|
|
Value.VType := ftNull else
|
|
Value.VType := C^.ColumnType;
|
|
case Value.VType of
|
|
ftNull: ; // do nothing
|
|
ftInt64:
|
|
if C^.ColumnValueDBType=SQLT_INT then
|
|
Value.VInt64 := PInt64(V)^ else
|
|
SetInt64(V,Value.VInt64); // encoded as SQLT_STR
|
|
ftCurrency: begin
|
|
Value.VInt64 := StrToCurr64(V,@NoDecimal); // encoded as SQLT_STR
|
|
if NoDecimal then
|
|
Value.VType := ftInt64; // encoded e.g. from SQLT_NUM as NUMBER(22,0)
|
|
end;
|
|
ftDouble:
|
|
Value.VInt64 := PInt64(V)^; // copy 64 bit content
|
|
ftDate:
|
|
if C^.ColumnValueDBType=SQLT_DAT then // types match -> fast direct retrieval
|
|
Value.VDateTime := POracleDate(V)^.ToDateTime else
|
|
Value.VDateTime := IntervalTextToDateTime(V);
|
|
ftUTF8: begin
|
|
with TSQLDBOracleConnection(Connection) do
|
|
if C^.ColumnValueInlined then
|
|
STRToUTF8(V,RawUTF8(Temp),C^.ColumnValueDBCharSet,C^.ColumnValueDBForm) else
|
|
OCI.ClobFromDescriptor(self,fContext,fError,PPOCIDescriptor(V)^,
|
|
C^.ColumnValueDBForm,RawUTF8(Temp),false);
|
|
Value.VText := pointer(Temp);
|
|
end;
|
|
ftBlob:
|
|
if fForceBlobAsNull then begin
|
|
Value.VBlob := nil;
|
|
Value.VBlobLen := 0;
|
|
Value.VType := ftNull;
|
|
end else begin
|
|
if C^.ColumnValueInlined then
|
|
SetString(Temp,PAnsiChar(V),C^.ColumnValueDBSize) else
|
|
with TSQLDBOracleConnection(Connection) do
|
|
OCI.BlobFromDescriptor(self,fContext,fError,PPOCIDescriptor(V)^,Temp);
|
|
Value.VBlob := pointer(Temp);
|
|
Value.VBlobLen := length(Temp);
|
|
end;
|
|
else raise ESQLDBOracle.CreateUTF8('%.ColumnToSQLVar: unexpected VType=%',
|
|
[self,ord(Value.VType)]);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnToVariant(Col: integer;
|
|
var Value: Variant): TSQLDBFieldType;
|
|
var C: PSQLDBColumnProperty;
|
|
V: pointer;
|
|
tmp: RawUTF8;
|
|
NoDecimal: boolean;
|
|
begin // dedicated version to avoid as much memory allocation than possible
|
|
V := GetCol(Col,C);
|
|
if V=nil then
|
|
result := ftNull else
|
|
result := C^.ColumnType;
|
|
VarClear(Value);
|
|
with TVarData(Value) do begin
|
|
VType := MAP_FIELDTYPE2VARTYPE[result];
|
|
case result of
|
|
ftNull: ; // do nothing
|
|
ftInt64:
|
|
if C^.ColumnValueDBType=SQLT_INT then
|
|
VInt64 := PInt64(V)^ else
|
|
SetInt64(V,VInt64); // encoded as SQLT_STR
|
|
ftCurrency: begin
|
|
VInt64 := StrToCurr64(V,@NoDecimal); // encoded as SQLT_STR
|
|
if NoDecimal then begin
|
|
VType := varInt64; // encoded e.g. from SQLT_NUM as NUMBER(22,0)
|
|
result := ftInt64;
|
|
end;
|
|
end;
|
|
ftDouble:
|
|
VInt64 := PInt64(V)^; // copy 64 bit content
|
|
ftDate:
|
|
if C^.ColumnValueDBType=SQLT_DAT then
|
|
VDate := POracleDate(V)^.ToDateTime else // direct retrieval
|
|
IntervalTextToDateTimeVar(V,VDate); // from SQLT_INTERVAL_* text
|
|
ftUTF8: begin // see TSQLDBStatement.ColumnToVariant() for reference
|
|
VAny := nil;
|
|
with TSQLDBOracleConnection(Connection) do
|
|
if C^.ColumnValueInlined then
|
|
{$ifndef UNICODE}
|
|
if not Connection.Properties.VariantStringAsWideString then begin
|
|
VType := varString;
|
|
STRToAnsiString(V,AnsiString(VAny),C^.ColumnValueDBCharSet,C^.ColumnValueDBForm);
|
|
exit;
|
|
end else
|
|
{$endif}
|
|
STRToUTF8(V,tmp,C^.ColumnValueDBCharSet,C^.ColumnValueDBForm) else
|
|
OCI.ClobFromDescriptor(self,fContext,fError,PPOCIDescriptor(V)^,
|
|
C^.ColumnValueDBForm,tmp);
|
|
{$ifndef UNICODE}
|
|
if not Connection.Properties.VariantStringAsWideString then begin
|
|
VType := varString;
|
|
AnsiString(VAny) := UTF8DecodeToString(pointer(tmp),length(tmp));
|
|
end else
|
|
{$endif}
|
|
UTF8ToSynUnicode(tmp,SynUnicode(VAny));
|
|
end;
|
|
ftBlob: begin
|
|
VAny := nil;
|
|
if C^.ColumnValueInlined then
|
|
SetString(RawByteString(VAny),PAnsiChar(V),C^.ColumnValueDBSize) else
|
|
with TSQLDBOracleConnection(Connection) do
|
|
OCI.BlobFromDescriptor(self,fContext,fError,PPOCIDescriptor(V)^,RawByteString(VAny));
|
|
end;
|
|
else raise ESQLDBOracle.CreateUTF8('%.ColumnToVariant: unexpected % type',
|
|
[self,ord(result)]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnUTF8(Col: integer): RawUTF8;
|
|
var C: PSQLDBColumnProperty;
|
|
V: PAnsiChar;
|
|
begin
|
|
V := GetCol(Col,C);
|
|
if V=nil then // column is NULL
|
|
result := '' else
|
|
if C^.ColumnType=ftUTF8 then
|
|
with TSQLDBOracleConnection(Connection) do
|
|
if C^.ColumnValueInlined then
|
|
// conversion from SQLT_STR (null-terminated string)
|
|
STRToUTF8(V,result,C^.ColumnValueDBCharSet,C^.ColumnValueDBForm) else
|
|
// conversion from POCILobLocator
|
|
OCI.ClobFromDescriptor(self,fContext,fError,PPOCIDescriptor(V)^,
|
|
C^.ColumnValueDBForm,result) else
|
|
// need conversion to destination type
|
|
ColumnToTypedValue(Col,ftUTF8,result);
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.ColumnCursor(Col: integer): ISQLDBRows;
|
|
var C: PSQLDBColumnProperty;
|
|
V: PAnsiChar;
|
|
begin
|
|
result := nil;
|
|
V := GetCol(Col,C);
|
|
if V<>nil then // column is NULL
|
|
if C^.ColumnValueDBType=SQLT_RSET then begin
|
|
result := TSQLDBOracleStatement.CreateFromExistingStatement(Connection,PPointer(V)^);
|
|
PPointer(V)^ := nil; // caller will release the POCIStmt instance with its ISQLDBRows
|
|
end else
|
|
result := inherited ColumnCursor(Col); // will raise an exception
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.BindCursor(Param: integer);
|
|
begin
|
|
CheckParam(Param,ftUnknown,paramOut); // ftUnknown+paramOut indicate SQLT_RSET
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.BoundCursor(Param: Integer): ISQLDBRows;
|
|
begin
|
|
dec(Param);
|
|
if (cardinal(Param)>=cardinal(length(fBoundCursor))) or
|
|
(fBoundCursor[Param]=nil) then
|
|
raise ESQLDBOracle.CreateUTF8(
|
|
'%.BoundCursor: no BindCursor() on Param #%',[self,Param+1]);
|
|
result := TSQLDBOracleStatement.CreateFromExistingStatement(Connection,fBoundCursor[Param]);
|
|
fBoundCursor[Param] := nil;
|
|
end;
|
|
|
|
constructor TSQLDBOracleStatement.Create(aConnection: TSQLDBConnection);
|
|
begin
|
|
if not aConnection.InheritsFrom(TSQLDBOracleConnection) then
|
|
raise ESQLDBOracle.CreateUTF8('Invalid %.Create(%) call',[self,aConnection]);
|
|
inherited Create(aConnection);
|
|
fInternalBufferSize := TSQLDBOracleConnectionProperties(aConnection.Properties).InternalBufferSize;
|
|
if fInternalBufferSize<16384 then // default is 128 KB
|
|
fInternalBufferSize := 16384; // minimal value
|
|
end;
|
|
|
|
destructor TSQLDBOracleStatement.Destroy;
|
|
begin
|
|
try
|
|
fTimeElapsed.Resume;
|
|
FreeHandles(false);
|
|
{$ifndef SYNDB_SILENCE}
|
|
SynDBLog.Add.Log(sllDB,'Destroy: stats = % row(s) in %',
|
|
[TotalRowsRetrieved,fTimeElapsed.Stop],self);
|
|
{$endif}
|
|
finally
|
|
inherited;
|
|
end;
|
|
end;
|
|
|
|
constructor TSQLDBOracleStatement.CreateFromExistingStatement(
|
|
aConnection: TSQLDBConnection; aStatement: pointer);
|
|
begin
|
|
Create(aConnection);
|
|
fTimeElapsed.Resume;
|
|
try
|
|
fStatement := aStatement;
|
|
try
|
|
fExpectResults := true;
|
|
SetColumnsForPreparedStatement;
|
|
FetchRows;
|
|
if fRowFetched=0 then
|
|
fCurrentRow := -1 else // no data row available
|
|
fCurrentRow := 0; // mark cursor on the first row
|
|
except
|
|
on E: Exception do begin
|
|
fStatement := nil; // do not release the statement in constructor
|
|
FreeHandles(True);
|
|
raise;
|
|
end;
|
|
end;
|
|
finally
|
|
fTimeElapsed.Pause;
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.FetchRows;
|
|
var status: integer;
|
|
begin
|
|
fRowFetched := 0;
|
|
status := OCI.StmtFetch(fStatement,fError,fRowCount,OCI_FETCH_NEXT,OCI_DEFAULT);
|
|
case Status of
|
|
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
|
|
/// Oracle VARNUM memory structure
|
|
TSQLT_VNU = array[0..21] of byte;
|
|
/// points to a Oracle VARNUM memory structure
|
|
PSQLT_VNU = ^TSQLT_VNU;
|
|
|
|
procedure Int64ToSQLT_VNU(Value: Int64; OutData: PSQLT_VNU);
|
|
var V: Byte;
|
|
minus: Boolean; // True, if the sign is positive
|
|
Size, Exp, i: Integer;
|
|
Mant: array[0..19] of byte;
|
|
begin
|
|
FillcharFast(Mant,sizeof(Mant),0);
|
|
Exp := 0;
|
|
Size := 1;
|
|
minus := Value>=0;
|
|
if not minus then
|
|
Value := not Value;
|
|
while Value>0 do begin
|
|
if Value>=100 then begin
|
|
V := Value mod 100;
|
|
Value := Value div 100;
|
|
inc(Exp);
|
|
end else begin
|
|
V := Value;
|
|
Value := 0;
|
|
end;
|
|
if (V<>0) or (Size>1) then begin
|
|
if minus then
|
|
inc(V) else
|
|
V := (100+1)-V;
|
|
Mant[Size-1] := V;
|
|
inc(Size);
|
|
end;
|
|
end;
|
|
if Size>1 then
|
|
for i := 0 to Size-1 do
|
|
OutData[Size-i] := Mant[i];
|
|
Exp := (Exp+65) or $80;
|
|
if not minus and (Size<high(TSQLT_VNU)) then begin
|
|
Exp := not Exp;
|
|
inc(Size);
|
|
OutData[Size] := (100+2);
|
|
end;
|
|
OutData[1] := Exp;
|
|
OutData[0] := Size;
|
|
end;
|
|
|
|
procedure UnQuoteSQLString(S,D: PUTF8Char; SLen: integer);
|
|
begin // internal method, tuned for our OCI process
|
|
if S=nil then
|
|
D^ := #0 else
|
|
if S^<>'''' then
|
|
MoveFast(S^,D^,SLen+1) else begin // +1 to include #0
|
|
inc(S);
|
|
repeat
|
|
if S[0]='''' then
|
|
if S[1]='''' then
|
|
inc(S) else break;
|
|
D^ := S^;
|
|
inc(S);
|
|
inc(D);
|
|
until S^=#0;
|
|
D^ := #0;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
/// 32 MB of data sent at once sounds enough
|
|
MAX_INLINED_PARAM_SIZE = 32*1024*1024;
|
|
|
|
procedure TSQLDBOracleStatement.ExecutePrepared;
|
|
var i,j: PtrInt;
|
|
Env: POCIEnv;
|
|
Context: POCISvcCtx;
|
|
param: PSQLDBParam;
|
|
Type_List: POCIType;
|
|
oData: pointer;
|
|
oDataDAT: ^TOracleDateArray absolute oData;
|
|
oDataINT: ^TInt64Array absolute oData;
|
|
oDataSTR: PUTF8Char;
|
|
oLength: integer;
|
|
oBind: POCIBind;
|
|
oIndicator: array of sb2;
|
|
aIndicator: array of array of sb2;
|
|
oOCIDateTime: POCIDateTime;
|
|
Status, L: integer;
|
|
mode: cardinal;
|
|
Int32: set of 0..127;
|
|
ociArrays: array of POCIArray;
|
|
ociArraysCount: byte;
|
|
num_val: OCINumber;
|
|
tmp: RawUTF8;
|
|
str_val: POCIString;
|
|
{$ifdef FPC_64}
|
|
wasStringHacked: TByteDynArray;
|
|
{$endif FPC_64}
|
|
label txt;
|
|
begin
|
|
if (fStatement=nil) then
|
|
raise ESQLDBOracle.CreateUTF8('%.ExecutePrepared without previous Prepare',[self]);
|
|
inherited ExecutePrepared; // set fConnection.fLastAccessTicks
|
|
SQLLogBegin(sllSQL);
|
|
try
|
|
ociArraysCount := 0;
|
|
Env := (Connection as TSQLDBOracleConnection).fEnv;
|
|
Context := TSQLDBOracleConnection(Connection).fContext;
|
|
Status := OCI_ERROR;
|
|
try
|
|
fRowFetchedEnded := false;
|
|
// 1. bind parameters
|
|
if fPreparedParamsCount<>fParamCount then
|
|
raise ESQLDBOracle.CreateUTF8('%.ExecutePrepared expected % bound parameters, got %',
|
|
[self,fPreparedParamsCount,fParamCount]);
|
|
if not fExpectResults then
|
|
fRowCount := 1; // to avoid ORA-24333 error
|
|
if (fParamCount>0) then
|
|
if (fParamsArrayCount>0) and not fExpectResults then begin
|
|
// 1.1. Array DML binding
|
|
SetLength(aIndicator,fParamCount);
|
|
for i := 0 to fParamCount-1 do
|
|
with fParams[i] do begin
|
|
if VArray=nil then
|
|
raise ESQLDBOracle.CreateUTF8(
|
|
'%.ExecutePrepared: Parameter #% should be an array',[self,i]);
|
|
if VInt64<>fParamsArrayCount then
|
|
raise ESQLDBOracle.CreateUTF8(
|
|
'%.ExecutePrepared: Parameter #% expected array count %, got %',
|
|
[self,i,fParamsArrayCount,VInt64]);
|
|
SetLength(aIndicator[i],fParamsArrayCount);
|
|
VDBType := SQLT_STR;
|
|
oLength := 23; // max size for ftInt64/ftDouble/ftCurrency
|
|
case VType of
|
|
ftDate: begin
|
|
VDBType := SQLT_DAT;
|
|
SetString(VData,nil,fParamsArrayCount*sizeof(TOracleDate));
|
|
oData := pointer(VData);
|
|
oLength := sizeof(TOracleDate);
|
|
end;
|
|
ftInt64:
|
|
if OCI.SupportsInt64Params then begin
|
|
// starting with 11.2, OCI supports NUMBER conversion to/from Int64
|
|
VDBType := SQLT_INT;
|
|
SetString(VData,nil,fParamsArrayCount*sizeof(Int64));
|
|
oData := pointer(VData);
|
|
oLength := sizeof(Int64);
|
|
end; // prior to 11.2, we will stay with the default SQLT_STR type
|
|
ftUTF8:
|
|
oLength := 7; // minimal aligned length
|
|
ftBlob: begin
|
|
VDBTYPE := SQLT_LVB;
|
|
oLength := 7; // minimal aligned length
|
|
end;
|
|
end;
|
|
for j := 0 to fParamsArrayCount-1 do
|
|
if VArray[j]='null' then // bind null (ftUTF8 should be '"null"')
|
|
aIndicator[i][j] := -1 else begin
|
|
if VDBType=SQLT_INT then
|
|
SetInt64(pointer(Varray[j]),oDataINT^[j]) else
|
|
case VType of
|
|
ftUTF8,ftDate: begin
|
|
L := length(VArray[j])-2; // -2 since quotes will be removed
|
|
if VType=ftDate then
|
|
if L<=0 then
|
|
oDataDAT^[j].From(0) else
|
|
oDataDAT^[j].From(PUTF8Char(pointer(VArray[j]))+1,L) else
|
|
if L>oLength then
|
|
if L*fParamsArrayCount>MAX_INLINED_PARAM_SIZE then
|
|
raise ESQLDBOracle.CreateUTF8(
|
|
'%.ExecutePrepared: Array parameter #% STR too big',[self,i+1]) else
|
|
oLength := L;
|
|
end;
|
|
ftBlob: begin
|
|
L := length(VArray[j])+sizeof(integer);
|
|
if L*fParamsArrayCount>MAX_INLINED_PARAM_SIZE then
|
|
raise ESQLDBOracle.CreateUTF8(
|
|
'%.ExecutePrepared: Array parameter #% BLOB too big',[self,i+1]) else
|
|
if L>oLength then
|
|
oLength := L;
|
|
end;
|
|
end;
|
|
end;
|
|
case VDBType of
|
|
SQLT_STR: begin
|
|
inc(oLength); // space for trailing #0
|
|
SetString(VData,nil,oLength*fParamsArrayCount);
|
|
oData := Pointer(VData); // in-place quote removal in text
|
|
oDataSTR := oData;
|
|
for j := 0 to fParamsArrayCount-1 do begin
|
|
UnQuoteSQLString(pointer(VArray[j]),oDataSTR,length(VArray[j]));
|
|
inc(oDataSTR,oLength);
|
|
end;
|
|
end;
|
|
SQLT_LVB: begin
|
|
SetString(VData,nil,oLength*fParamsArrayCount);
|
|
oData := Pointer(VData);
|
|
oDataSTR := oData;
|
|
for j := 0 to fParamsArrayCount-1 do begin
|
|
{$ifdef FPC}
|
|
PInteger(oDataSTR)^ := length(VArray[j]);
|
|
MoveFast(Pointer(VArray[j])^,oDataSTR[4],length(VArray[j]));
|
|
{$else}
|
|
MoveFast(Pointer(PtrInt(VArray[j])-4)^,oDataSTR^,length(VArray[j])+4);
|
|
{$endif}
|
|
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
|
|
FillcharFast(Int32,sizeof(Int32),0);
|
|
SetLength(oIndicator,fParamCount);
|
|
SetLength(ociArrays,fParamCount);
|
|
for i := 0 to fParamCount-1 do
|
|
if Length(fParams[i].VArray)>0 then begin
|
|
// 1.2.1. Bind an array as one object
|
|
param := @fParams[i];
|
|
case param.VType of
|
|
ftInt64:
|
|
Type_List := TSQLDBOracleConnection(Connection).fType_numList;
|
|
ftUTF8:
|
|
Type_List := TSQLDBOracleConnection(Connection).fType_strList;
|
|
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(param.VData,nil,Length(param.VArray)*sizeof(Int64));
|
|
oData := pointer(param.VData);
|
|
for j := 0 to Length(param.VArray)-1 do
|
|
case param.VType of
|
|
ftInt64: begin
|
|
SetInt64(pointer(param.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(param.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
|
|
oLength := 0;
|
|
oData := nil;
|
|
VDBType := SQLT_STR;
|
|
end else begin
|
|
oLength := sizeof(Int64);
|
|
oData := @VInt64;
|
|
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
|
|
if VInOut=paramIn then
|
|
if (VInt64>low(integer)) and (VInt64<high(Integer)) then begin
|
|
// map to 32 bit will always work
|
|
VDBType := SQLT_INT;
|
|
Include(Int32,i);
|
|
oLength := SizeOf(integer); // truncate to 32 bit integer value
|
|
end else begin
|
|
VData := Int64ToUtf8(VInt64); // (SQLT_VNU did not work)
|
|
goto txt; // IN huge integers will be managed as text
|
|
end else begin
|
|
VDBType := SQLT_FLT; // OUT values will be converted as double
|
|
unaligned(PDouble(oData)^) := VInt64;
|
|
end;
|
|
ftDouble:
|
|
VDBType := SQLT_FLT;
|
|
ftCurrency:
|
|
if VInOut=paramIn then begin
|
|
VData := Curr64ToStr(VInt64);
|
|
goto txt; // input-only currency values will be managed as text
|
|
end else begin
|
|
VDBType := SQLT_FLT; // OUT values will be converted as double
|
|
unaligned(PDouble(oData)^) := PCurrency(oData)^;
|
|
end;
|
|
ftDate:
|
|
if VInOut=paramIn then begin
|
|
VDBType := SQLT_TIMESTAMP; // SQLT_DAT is wrong within WHERE clause
|
|
oOCIDateTime := DateTimeToDescriptor(PDateTime(@VInt64)^);
|
|
SetString(VData,PAnsiChar(@oOCIDateTime),sizeof(oOCIDateTime));
|
|
oData := pointer(VData);
|
|
oLength := sizeof(oOCIDateTime);
|
|
end else begin
|
|
VDBType := SQLT_DAT; // will work for OUT parameters
|
|
POracleDate(@VInt64)^.From(PDateTime(@VInt64)^);
|
|
end;
|
|
ftUTF8: begin
|
|
txt: VDBType := SQLT_STR; // use STR external data type (SQLT_LVC fails)
|
|
oLength := Length(VData)+1; // include #0
|
|
if oLength=1 then // '' will just map one #0
|
|
oData := @VData else
|
|
oData := pointer(VData);
|
|
// for OUT param, input text shall be pre-allocated
|
|
end;
|
|
ftBlob:
|
|
if VInOut<>paramIn then
|
|
raise ESQLDBOracle.CreateUTF8(
|
|
'%.ExecutePrepared: Unexpected OUT blob parameter #%',[self,i+1]) else begin
|
|
oLength := Length(VData);
|
|
if oLength<2000 then begin
|
|
VDBTYPE := SQLT_BIN;
|
|
oData := pointer(VData);
|
|
end else begin
|
|
VDBTYPE := SQLT_LVB; // layout: raw data prepended by int32 len
|
|
{$ifdef FPC_64}
|
|
// in case of FPC+CPU64 TSQLDBParam.VData is a RawByteString and
|
|
// length is stored as SizeInt = Int64 (not int32) -> patch
|
|
// (no patch needed for Delphi, in which len is always longint)
|
|
if Length(VData)>MaxInt then
|
|
raise ESQLDBOracle.CreateUTF8('%.ExecutePrepared: % blob length ' +
|
|
'exceeds max size for parameter #%',[self,KB(oLength),i+1]);
|
|
UniqueString(VData); // for thread-safety
|
|
PInteger(PtrInt(VData)-sizeof(Integer))^ := oLength;
|
|
if wasStringHacked=nil then
|
|
SetLength(wasStringHacked,fParamCount shr 3+1);
|
|
SetBitPtr(pointer(wasStringHacked),i); // for unpatching below
|
|
{$endif FPC_64}
|
|
oData := Pointer(PtrInt(VData)-sizeof(Integer));
|
|
Inc(oLength,sizeof(Integer));
|
|
end;
|
|
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. retrieve column information (if not already done)
|
|
if fExpectResults and (fColumn.Count = 0) then
|
|
// We move this after params binding to prevent "ORA-00932: inconsistent
|
|
// datatypes" during call to StmtExecute with OCI_DESCRIBE_ONLY.
|
|
// Because if called here sometimes it breaks the Oracle shared pool and
|
|
// only `ALTER system flush shared_pool` seems to fix the DB state
|
|
SetColumnsForPreparedStatement;
|
|
// 3. execute prepared statement and dispatch data in row buffers
|
|
if (fColumnCount=0) and (Connection.TransactionCount=0) then
|
|
// for INSERT/UPDATE/DELETE without a transaction: AutoCommit after execution
|
|
mode := OCI_COMMIT_ON_SUCCESS else
|
|
// for SELECT or inside a transaction: wait for an explicit COMMIT
|
|
mode := OCI_DEFAULT;
|
|
Status := OCI.StmtExecute(TSQLDBOracleConnection(Connection).fContext,
|
|
fStatement,fError,fRowCount,0,nil,nil,mode);
|
|
// 4. check execution error, and retrieve data result range
|
|
FetchTest(Status); // error + set fRowCount+fCurrentRow+fRowFetchedCurrent
|
|
Status := OCI_SUCCESS; // mark OK for fBoundCursor[] below
|
|
finally
|
|
{$ifdef FPC_64}
|
|
if wasStringHacked<>nil then // restore patched strings length ASAP
|
|
for i := 0 to fParamCount-1 do
|
|
if GetBitPtr(pointer(wasStringHacked),i) then
|
|
PInteger(PtrInt(fParams[i].VData)-sizeof(Integer))^ := 0;
|
|
{$endif FPC_64}
|
|
for i := 0 to ociArraysCount-1 do
|
|
OCI.Check(nil,self,OCI.ObjectFree(Env, fError, ociArrays[i], OCI_OBJECTFREE_FORCE), fError, false, sllError);
|
|
// 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
|
|
ftUnknown:
|
|
if VInOut=paramOut then
|
|
if Status=OCI_SUCCESS then begin
|
|
SetLength(fBoundCursor,fParamCount);
|
|
fBoundCursor[i] := PPointer(@VInt64)^; // available via BoundCursor()
|
|
end else // on error, release bound statement resource
|
|
if OCI.HandleFree(PPointer(@VInt64)^,OCI_HTYPE_STMT)<>OCI_SUCCESS then
|
|
SynDBLog.Add.Log(sllError,'ExecutePrepared: HandleFree(SQLT_RSET)',self);
|
|
ftInt64:
|
|
if VDBType=SQLT_FLT then // retrieve OUT integer parameter
|
|
VInt64 := trunc(unaligned(PDouble(@VInt64)^));
|
|
ftCurrency:
|
|
if VDBType=SQLT_FLT then // retrieve OUT currency parameter
|
|
PCurrency(@VInt64)^ := unaligned(PDouble(@VInt64)^);
|
|
ftDate:
|
|
case VDBType of
|
|
SQLT_DAT: // retrieve OUT date parameter
|
|
PDateTime(@VInt64)^ := POracleDate(@VInt64)^.ToDateTime;
|
|
SQLT_TIMESTAMP: begin // release OCIDateTime resource
|
|
oOCIDateTime := PPointer(VData)^;
|
|
if OCI.DescriptorFree(oOCIDateTime,OCI_DTYPE_TIMESTAMP)<>OCI_SUCCESS then
|
|
SynDBLog.Add.Log(sllError,'ExecutePrepared: DescriptorFree(OCI_DTYPE_TIMESTAMP)',self);
|
|
VData := '';
|
|
end;
|
|
end;
|
|
ftUTF8:
|
|
if VInOut<>paramIn then // retrieve OUT text parameter
|
|
SetLength(VData,StrLen(pointer(VData)));
|
|
end;
|
|
end;
|
|
finally
|
|
fTimeElapsed.FromExternalMicroSeconds(SQLLogEnd);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.FetchTest(Status: integer);
|
|
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;
|
|
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.ReleaseRows;
|
|
begin // not implemented yet
|
|
inherited ReleaseRows;
|
|
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;
|
|
begin
|
|
if self=nil then
|
|
exit; // avoid GPF
|
|
if fRowBuffer<>nil then
|
|
for i := 0 to fColumnCount-1 do
|
|
with fColumns[i] do
|
|
if not ColumnValueInlined then begin
|
|
P := @fRowBuffer[ColumnAttr]; // first POCILobLocator/POCIStmt item
|
|
for j := 1 to fRowBufferCount do begin
|
|
if P^<>nil then begin
|
|
case ColumnValueDBType of
|
|
SQLT_CLOB, SQLT_BLOB:
|
|
if OCI.DescriptorFree(P^,OCI_DTYPE_LOB)<>OCI_SUCCESS then
|
|
SynDBLog.Add.Log(sllError,'FreeHandles: Invalid OCI_DTYPE_LOB',self);
|
|
SQLT_RSET:
|
|
if OCI.HandleFree(P^,OCI_HTYPE_STMT)<>OCI_SUCCESS then
|
|
SynDBLog.Add.Log(sllError,'FreeHandles: Invalid SQLT_RSET',self);
|
|
else raise ESQLDBOracle.CreateUTF8(
|
|
'%.FreeHandles: Wrong % type for inlined column %',
|
|
[self,ColumnValueDBType,ColumnName]);
|
|
end;
|
|
P^ := nil;
|
|
end;
|
|
inc(P);
|
|
end;
|
|
end;
|
|
if fBoundCursor<>nil then begin
|
|
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;
|
|
if fRowBuffer<>nil then
|
|
SetLength(fRowBuffer,0); // release internal buffer memory
|
|
if fColumnCount>0 then
|
|
fColumn.Clear;
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.GetCol(Col: Integer;
|
|
out Column: PSQLDBColumnProperty): pointer;
|
|
begin
|
|
CheckCol(Col); // check Col value against fColumnCount
|
|
if not Assigned(fStatement) or (fColumnCount=0) or (fRowCount=0) or (fRowBuffer=nil) then
|
|
raise ESQLDBOracle.CreateUTF8('%.Column*() with no prior Execute',[self]);
|
|
if CurrentRow<=0 then
|
|
raise ESQLDBOracle.CreateUTF8('%.Column*() with no prior Step',[self]);
|
|
Column := @fColumns[Col];
|
|
result := @fRowBuffer[Column^.ColumnAttr+fRowFetchedCurrent*Column^.ColumnValueDBSize];
|
|
case PSmallIntArray(fRowBuffer)[cardinal(Col)*fRowCount+fRowFetchedCurrent] of
|
|
// 0:OK, >0:untruncated length, -1:NULL, -2:truncated (length>32KB)
|
|
-1: result := nil; // NULL
|
|
0: exit;
|
|
else LogTruncatedColumn(Column^);
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.UpdateCount: integer;
|
|
begin
|
|
result := 0;
|
|
if fStatement<>nil then
|
|
OCI.AttrGet(fStatement,OCI_HTYPE_STMT,@result,nil,OCI_ATTR_ROW_COUNT,fError);
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.SetColumnsForPreparedStatement;
|
|
var aName: RawUTF8;
|
|
Env: POCIEnv;
|
|
i,j: integer;
|
|
oHandle: POCIHandle;
|
|
oDefine: POCIDefine;
|
|
oName: PAnsiChar;
|
|
oNameLen, oScale, oCharSet: integer;
|
|
ColCount, RowSize: cardinal;
|
|
StatementType, oType, oSize: ub2;
|
|
Prefetch: ub4;
|
|
ColumnLongTypes: set of (hasLOB,hasLONG,hasCURS);
|
|
PP: PPointer;
|
|
Indicators: PAnsiChar;
|
|
begin
|
|
Env := (Connection as TSQLDBOracleConnection).fEnv;
|
|
with OCI do begin
|
|
// 1. ensure fStatement is SELECT
|
|
if fError=nil then
|
|
HandleAlloc(Env,fError,OCI_HTYPE_ERROR);
|
|
AttrGet(fStatement,OCI_HTYPE_STMT,@StatementType,nil,OCI_ATTR_STMT_TYPE,fError);
|
|
if fExpectResults<>(StatementType=OCI_STMT_SELECT) then
|
|
raise ESQLDBOracle.CreateUTF8('%.SetColumnsForPreparedStatement called with '+
|
|
'ExpectResults=%, whereas StatementType=%',[self,ord(fExpectResults),StatementType]);
|
|
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 := [];
|
|
fColumn.Capacity := ColCount;
|
|
for i := 1 to ColCount do begin
|
|
oHandle := nil;
|
|
ParamGet(fStatement,OCI_HTYPE_STMT,fError,oHandle,i);
|
|
AttrGet(oHandle,OCI_DTYPE_PARAM,@oName,@oNameLen,OCI_ATTR_NAME,fError);
|
|
if oNameLen=0 then
|
|
aName := 'col_'+Int32ToUtf8(i) else
|
|
SetString(aName,oName,oNameLen);
|
|
AttrGet(oHandle,OCI_DTYPE_PARAM,@oType,nil,OCI_ATTR_DATA_TYPE,fError);
|
|
AttrGet(oHandle,OCI_DTYPE_PARAM,@oSize,nil,OCI_ATTR_DATA_SIZE,fError);
|
|
with PSQLDBColumnProperty(fColumn.AddAndMakeUniqueName(aName))^ do begin
|
|
ColumnValueDBSize := oSize;
|
|
ColumnValueInlined := true;
|
|
case oType of
|
|
SQLT_CHR, SQLT_VCS, SQLT_AFC, SQLT_AVC, SQLT_STR, SQLT_VST, SQLT_NTY: begin
|
|
ColumnType := ftUTF8;
|
|
ColumnValueDBType := SQLT_STR; // null-terminated string
|
|
inc(ColumnValueDBSize); // must include ending #0
|
|
end;
|
|
SQLT_LNG: begin
|
|
ColumnValueDBSize := 32768; // will be truncated at 32 KB
|
|
ColumnType := ftUTF8;
|
|
ColumnValueDBType := SQLT_STR; // null-terminated string
|
|
include(ColumnLongTypes,hasLONG);
|
|
end;
|
|
SQLT_LVC, SQLT_CLOB: begin
|
|
ColumnType := ftUTF8;
|
|
ColumnValueInlined := false;
|
|
ColumnValueDBType := SQLT_CLOB;
|
|
ColumnValueDBSize := sizeof(POCILobLocator);
|
|
include(ColumnLongTypes,hasLOB);
|
|
end;
|
|
SQLT_RID, SQLT_RDD: begin
|
|
ColumnType := ftUTF8;
|
|
ColumnValueDBType := SQLT_STR; // null-terminated string
|
|
ColumnValueDBSize := 24; // 24 will fit 8 bytes alignment
|
|
end;
|
|
SQLT_VNU, SQLT_FLT, SQLT_BFLOAT, SQLT_BDOUBLE,
|
|
SQLT_IBFLOAT, SQLT_IBDOUBLE: begin
|
|
ColumnType := ftDouble;
|
|
ColumnValueDBType := SQLT_BDOUBLE;
|
|
ColumnValueDBSize := sizeof(Double);
|
|
end;
|
|
SQLT_NUM: begin
|
|
oScale:= 5; // OCI_ATTR_PRECISION is always 38 (on Oracle 11g) :(
|
|
AttrGet(oHandle,OCI_DTYPE_PARAM,@oScale,nil,OCI_ATTR_SCALE,fError);
|
|
ColumnValueDBSize := sizeof(Double);
|
|
case oScale of
|
|
{0: if (major_version>11) or ((major_version=11) and (minor_version>1)) then begin
|
|
// starting with 11.2, OCI supports NUMBER conversion into Int64
|
|
ColumnType := ftInt64;
|
|
ColumnValueDBType := SQLT_INT;
|
|
end else begin
|
|
// we'll work out with null-terminated string
|
|
ColumnType := ftCurrency;
|
|
ColumnValueDBType := SQLT_STR;
|
|
ColumnValueDBSize := 24;
|
|
end;}
|
|
// we found out that a computed column is returned with Scale=0
|
|
// even if it is numeric (OCI 11.2 bug) -> so SQLT_INT won't work
|
|
// in fact, SQLT_STR will make JSON creation faster (already ASCII)
|
|
0..4: begin
|
|
ColumnType := ftCurrency; // will guess type from results
|
|
ColumnValueDBType := SQLT_STR; // use null-terminated string
|
|
ColumnValueDBSize := 24;
|
|
end else begin
|
|
ColumnType := ftDouble;
|
|
ColumnValueDBType := SQLT_BDOUBLE;
|
|
end;
|
|
end;
|
|
end;
|
|
SQLT_INT, _SQLT_PLI, SQLT_UIN: begin
|
|
ColumnType := ftInt64;
|
|
ColumnValueDBType := SQLT_INT;
|
|
ColumnValueDBSize := sizeof(Int64);
|
|
end;
|
|
SQLT_DAT, SQLT_DATE, SQLT_TIME, SQLT_TIME_TZ,
|
|
SQLT_TIMESTAMP, SQLT_TIMESTAMP_TZ, SQLT_TIMESTAMP_LTZ: begin
|
|
ColumnType := ftDate;
|
|
ColumnValueDBType := SQLT_DAT;
|
|
ColumnValueDBSize := sizeof(TOracleDate);
|
|
end;
|
|
SQLT_INTERVAL_YM, SQLT_INTERVAL_DS: begin
|
|
ColumnType := ftDate;
|
|
ColumnValueDBType := SQLT_STR; // null-terminated string
|
|
ColumnValueDBSize := 24; // 24 will fit 8 bytes alignment
|
|
end;
|
|
SQLT_BIN: begin
|
|
if fForceBlobAsNull then
|
|
ColumnType := ftNull else
|
|
ColumnType := ftBlob;
|
|
ColumnValueDBType := SQLT_BIN;
|
|
end;
|
|
SQLT_LBI, SQLT_BLOB, SQLT_LVB: begin
|
|
ColumnType := ftBlob;
|
|
ColumnValueInlined := false;
|
|
ColumnValueDBType := SQLT_BLOB;
|
|
ColumnValueDBSize := sizeof(POCILobLocator);
|
|
if fForceBlobAsNull then
|
|
ColumnType := ftNull else
|
|
include(ColumnLongTypes,hasLOB);
|
|
end;
|
|
SQLT_RSET, SQLT_CUR: begin
|
|
ColumnType := ftNull;
|
|
ColumnValueInlined := false;
|
|
ColumnValueDBType := SQLT_RSET;
|
|
ColumnValueDBSize := sizeof(POCIStmt);
|
|
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
|
|
not SimilarCharSet(ColumnValueDBCharSet,oCharSet) then
|
|
// log a warning, but use the connection-level code page
|
|
SynDBLog.Add.Log(sllWarning,'Column [%] has % (%) charset - '+
|
|
'expected % (%) -> possible data loss',[ColumnName,
|
|
ColumnValueDBCharSet,OracleCharSetName(ColumnValueDBCharSet),
|
|
oCharSet,OracleCharSetName(oCharSet)],self);
|
|
end;
|
|
SQLCS_NCHAR: // NVARCHAR2 -> set max UTF-8 bytes from chars
|
|
if ColumnValueInlined then begin
|
|
inc(RowSize,ColumnValueDBSize*2);
|
|
ColumnValueDBSize := ColumnValueDBSize*3;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
// avoid memory leak for cached statement
|
|
if DescriptorFree(oHandle, OCI_DTYPE_PARAM)<>OCI_SUCCESS then
|
|
SynDBLog.Add.Log(sllError, 'Invalid DescriptorFree(OCI_DTYPE_PARAM)',self);
|
|
end;
|
|
assert(fColumn.Count=integer(ColCount));
|
|
// 3. Dispatch data in row buffer
|
|
assert(fRowBuffer=nil);
|
|
fRowCount := (fInternalBufferSize-ColCount shl 4) div RowSize;
|
|
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
|
|
// fRowBuffer[] contains Indicators[] + Col0[] + Col1[] + Col2[]...
|
|
Indicators := pointer(fRowBuffer);
|
|
RowSize := fRowBufferCount*ColCount*sizeof(sb2);
|
|
for i := 0 to ColCount-1 do
|
|
with fColumns[i] do begin
|
|
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,@OCI_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,@OCI_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);
|
|
end;
|
|
end;
|
|
|
|
procedure TSQLDBOracleStatement.Prepare(const aSQL: RawUTF8;
|
|
ExpectResults: Boolean);
|
|
var env: POCIEnv;
|
|
L: PtrInt;
|
|
begin
|
|
SQLLogBegin(sllDB);
|
|
try
|
|
try
|
|
if (fStatement<>nil) or (fColumnCount>0) then
|
|
raise ESQLDBOracle.CreateUTF8('%.Prepare should be called only once',[self]);
|
|
// 1. process SQL
|
|
inherited Prepare(aSQL,ExpectResults); // set fSQL + Connect if necessary
|
|
fPreparedParamsCount := ReplaceParamsByNumbers(aSQL,fSQLPrepared,':',true);
|
|
L := Length(fSQLPrepared);
|
|
while (L>0) and (fSQLPrepared[L]<=' ') do // trim right
|
|
dec(L);
|
|
// allow one trailing ';' by writing ';;' or allows 'END;' at the end of a statement
|
|
if (L>5) and (fSQLPrepared[L]=';') and not
|
|
(IdemPChar(@fSQLPrepared[L-3],'END') and (fSQLPrepared[L-4]<='A')) then
|
|
dec(L);
|
|
if L<>Length(fSQLPrepared) then
|
|
SetLength(fSQLPrepared,L); // trim trailing spaces or ';' if needed
|
|
// 2. prepare statement
|
|
env := (Connection as TSQLDBOracleConnection).fEnv;
|
|
with OCI do begin
|
|
HandleAlloc(env,fError,OCI_HTYPE_ERROR);
|
|
if fUseServerSideStatementCache then begin
|
|
if StmtPrepare2(TSQLDBOracleConnection(Connection).fContext,fStatement,
|
|
fError,pointer(fSQLPrepared),length(fSQLPrepared),nil,0,OCI_NTV_SYNTAX,
|
|
OCI_PREP2_CACHE_SEARCHONLY) = OCI_SUCCESS then
|
|
fCacheIndex := 1 else
|
|
Check(nil,self,StmtPrepare2(TSQLDBOracleConnection(Connection).fContext,fStatement,
|
|
fError,pointer(fSQLPrepared),length(fSQLPrepared),nil,0,OCI_NTV_SYNTAX,OCI_DEFAULT),fError);
|
|
end else begin
|
|
HandleAlloc(env,fStatement,OCI_HTYPE_STMT);
|
|
Check(nil,self,StmtPrepare(fStatement,fError,pointer(fSQLPrepared),length(fSQLPrepared),
|
|
OCI_NTV_SYNTAX,OCI_DEFAULT),fError);
|
|
end;
|
|
end;
|
|
// note: if SetColumnsForPreparedStatement is called here, we randomly got
|
|
// "ORA-00932 : inconsistent datatypes" error -> moved to ExecutePrepared
|
|
except
|
|
on E: Exception do begin
|
|
FreeHandles(True);
|
|
raise;
|
|
end;
|
|
end;
|
|
finally
|
|
fTimeElapsed.FromExternalMicroSeconds(SQLLogEnd(' cache=%',[fCacheIndex]));
|
|
end;
|
|
end;
|
|
|
|
function TSQLDBOracleStatement.Step(SeekFirst: boolean): boolean;
|
|
var sav, status: integer;
|
|
begin
|
|
if not Assigned(fStatement) then
|
|
raise ESQLDBOracle.CreateUTF8('%.Execute should be called before Step',[self]);
|
|
result := false;
|
|
if (fCurrentRow<0) or (fRowCount=0) then
|
|
exit; // no data available at all
|
|
sav := fCurrentRow;
|
|
fCurrentRow := -1;
|
|
if fColumnCount=0 then
|
|
exit; // no row available at all (e.g. for SQL UPDATE) -> return false
|
|
if sav<>0 then begin // ignore if just retrieved ROW #1
|
|
if SeekFirst then begin
|
|
fTimeElapsed.Resume;
|
|
try
|
|
{ if OCI.major_version<9 then
|
|
raise ESQLDBOracle.CreateUTF8('Oracle Client % does not support OCI_FETCH_FIRST',
|
|
[OCI.ClientRevision]); }
|
|
status := OCI.StmtFetch(fStatement,fError,fRowCount,OCI_FETCH_FIRST,OCI_DEFAULT);
|
|
FetchTest(Status); // error + set fRowCount+fRowFetchedCurrent
|
|
if fCurrentRow<0 then // should not happen
|
|
raise ESQLDBOracle.Create('OCI_FETCH_FIRST did not reset cursor');
|
|
finally
|
|
fTimeElapsed.Pause;
|
|
end;
|
|
end else begin
|
|
// ensure we have some data in fRowBuffer[] for this row
|
|
inc(fRowFetchedCurrent);
|
|
if fRowFetchedCurrent>=fRowFetched then begin // reached end of buffer
|
|
if fRowFetchedEnded then
|
|
exit; // no more data
|
|
fTimeElapsed.Resume;
|
|
try
|
|
FetchRows;
|
|
if fRowFetched=0 then
|
|
exit; // no more row available -> return false + fCurrentRow=-1
|
|
finally
|
|
fTimeElapsed.Pause;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
fCurrentRow := sav+1;
|
|
inc(fTotalRowsRetrieved);
|
|
result := true; // mark data available in fRowSetData
|
|
end;
|
|
|
|
|
|
initialization
|
|
TSQLDBOracleConnectionProperties.RegisterClassNameForDefinition;
|
|
end.
|
|
|