Stored procedure Text saving in Delphi - oracle

I need to create stored procedure into oracle from delphi with TQuery.
But the SQL.text is difficult to uunderstand.
Is there any way to store direct text as pl/SQL with out quotes?
'create or replace '+
'function WholeTableRecovery(i_tablname IN varchar) return varchar '+
'as '+
Is it possible with resource file
Thanks in advance

Since you are using Delphi 2010 in the tags (I have no Delphi 7 here to test), a comfortable method would be storing the SQLs in separate textfiles, together with a RC file containing the directives for the resource compiler.
The RC files will contain the names of the resource you want to use together with the filenames containing the SQLs you want to store. The content for the example would look like this:
My_First_Speaking_ResourceName RCDATA "MyFirstSQL.sql"
My_Second_Speaking_ResourceName RCDATA "MySecondSQL.sql"
There is no need to call BRCC32 directly if you include the resource containing RC and resulting RES :
{$R 'MySQLResources.res' 'resources\MySQLResources.rc'}
You might wrap the usage of TResourceStream for your convenience, the way shown in the example would use Strings you might also work with the stream directly as mentioned by TLama MyQuery.SQL.LoadFromStream(rs);
implementation
{$R *.dfm}
{$R 'MySQLResources.res' 'resources\MySQLResources.rc'}
function LoadSqlResource(resourceName: string): string;
var
rs: TResourceStream;
sl: TStringList;
s : string;
begin
sl := TStringList.Create;
try
rs := TResourceStream.Create(hinstance, resourceName, RT_RCDATA);
try
rs.Position := 0;
sl.LoadFromStream(rs);
Result := sl.Text;
finally
rs.Free;
end;
finally
sl.Free;
end;
end;
procedure CallOneSql(Q:TADOQuery;ResourceName:String);
begin
Q.SQL.Text := LoadSqlResource('My_First_Speaking_ResourceName');
Q.ExecSQL;
end;
With a call like CallOneSql(MyQuery,'My_First_Speaking_ResourceName');
Make sure to create the project, not just compile if you made changes on the RC or the SQL files.

Related

Assigning procedures to variables and calling them in Pascal

I have a small terminal program which displays a menu. I want to have a function which takes the user's input and an array of procedure names and be able to call the procedure the user chose. I know that I could do that using if or case statements in the main program, but I want to make a unit with the above procedure and a couple of other menu functions, and if this was possible, I could make it call arbitrary procedures. Here's more or less how I would like it to work (I know it's wrong, but so you get a general idea).
program menu;
uses crt;
type procs = array [0..1] of procedure;
procedure call_procs(inp: int; procsy: procs);
begin
writeln(procsy[ord(inp)]); {And call it here.}
end;
var procsx : procs;
begin
procsx[0] := readkey; {I would like to somehow store the procedure here.}
procsx[1] := clrscr;
call_procs(0, procsx);
end.
Is there any way to do something like this? Thank you in advance.
There are a few things wrong with your original code which are not cited in your answer.
You have an array of procedure but you are calling writeln with these procedure calls as arguments as if they were function, which they are not.
readkey is a function, not a procedure, so its type doesn't match the element type of your array
Your assignment of the procedures to the array need to use # to reference the procedure pointer and not actually call the procedure
Not sure what compiler or options you're using, but int isn't the standard Pascal integer type, rather integer is.
As a niggle, since you're already using the integer index of the array, you don't need to use ord.
So the minimal changes to your code to make it basically work would be:
program menu;
uses crt;
type procs = array [0..1] of procedure;
procedure call_procs(inp: integer; procsy: procs);
begin
procsy[inp]; { call the procedure here - removed 'ord' since it's superfluous }
end;
var procsx : procs;
begin
{ procsx[0] := readkey; {- 'readkey' is a function and won't work here }
procsx[1] := #clrscr;
call_procs(1, procsx);
end.
You can create an array of functions that return char which matches the type for readkey:
program menu;
uses crt;
type procs = array [0..1] of function: char;
procedure call_procs(inp: integer; procsy: procs);
begin
writeln(procsy[inp]); { call the function; write out the returned char }
end;
function foo: char;
begin
foo := 'X';
end;
var procsx : procs;
begin
procsx[0] := #readkey;
procsx[1] := #foo;
call_procs(0, procsx);
call_procs(1, procsx);
end.
I figured out how to do this. One can use pointers to a procedure, then create an array of those pointers, and pass them to the procedure I wanted to use. Also, for some reason, it doesn't seem to work with the functions that come with Pascal (such as readkey or clrscr). For this example, one could do this:
program menu;
type
Tprocptr = procedure; {This creates a pointer to procedures.}
Tprocarray = array of Tprocptr;
procedure writeHi;
begin
writeln('Hi!');
end;
procedure writeHello;
begin
writeln('Hello!');
end;
procedure call_proc(inp: integer; procsy: Tprocarray);
{This now calls functions like one would expect.}
begin
procsy[ord(inp)];
end;
var
proclist : Tprocarray;
begin
setlength(proclist, 2);
proclist[0] := #writeHi; {The '#' creates a pointer to those procedures.}
proclist[1] := #writeHello;
call_proc(0, proclist);
end.
This works as expected, calling (in this case) the procedure writeHi, so if you run this program it will output Hi! to the terminal. If you change call_proc(0,proclist) to call_proc(1, proclist), it will call writeHello instead.

Get a list of all indexed files in Windows with Delphi

I would like to list all the files that windows has indexed using its Windows Indexing Service.
Specified file extensions are acceptable.
For instance: I am working an a software which presents user media such as photos and videos. I am currently using the following custom procedure to find the files myself:
function FindAllFiles_Safe(aDirectory, aFilter: string; aIncludeSubDirs: boolean): string;
{$IFDEF DCC}
var TD: TDirectory;
SO: TSearchOption;
DF: TStringDynArray;
i: integer;
sl: TStringList;
MaskArray: TStringDynArray;
Predicate: TDirectory.TFilterPredicate;
{$ENDIF}
begin
{$IFDEF FPC}
result:=FindAllFiles(aDirectory,aFilter,aIncludeSubDirs).text;
{$ENDIF}
{$IFDEF DCC}
MaskArray := SplitString(aFilter, ';');
if aIncludeSubDirs=true then SO:=TSearchOption.soAllDirectories;
Predicate :=
function(const Path: string; const SearchRec: TSearchRec): Boolean
var Mask: string;
begin
for Mask in MaskArray do
if MatchesMask(SearchRec.Name, Mask) then
exit(True);
exit(False);
end;
//DF:=TD.GetFiles(aDirectory, Predicate, SO);
DF:=TD.GetFiles(aDirectory, SO, Predicate);
if length(DF)=0 then exit;
sl:=TStringList.Create;
for i := 0 to length(DF)-1 do sl.Add(DF[i]);
result:=sl.Text;
sl.Free;
{$ENDIF}
end;
Is there a way to access files that Windows has already indexed?
I'd like to take advantage of Windows Indexing Service to quickly retrieve files, rather then wasting resources if Windows already has done it before.
One of the ways to query the index of the Windows Search is use ADO and the Query Syntax (AQS) and SQL.
Try this sample code (off course you can improve the SQL sentence to filter and speed up the results)
{$APPTYPE CONSOLE}
{$R *.res}
uses
ADOInt,
SysUtils,
ActiveX,
ComObj,
Variants;
procedure QuerySystemIndex;
var
Connection : _Connection;
RecordSet: _RecordSet;
v: Variant;
begin;
OleCheck(CoCreateInstance(CLASS_Connection, nil, CLSCTX_ALL, IID__Connection, Connection));
OleCheck(CoCreateInstance(CLASS_RecordSet, nil, CLSCTX_ALL, IID__RecordSet, RecordSet));
Connection.CursorLocation := adUseClient;
Connection.Open('Provider=Search.CollatorDSO;Extended Properties=''Application=Windows'';', '', '', adConnectUnspecified);
Recordset.Open('SELECT Top 5 System.ItemPathDisplay FROM SYSTEMINDEX', Connection, adOpenForwardOnly, adLockReadOnly, adCmdText);
Recordset.MoveFirst;
v:='System.ItemPathDisplay';
while not Recordset.EOF do
begin
Writeln(Recordset.Fields.Item[v].Value);
Recordset.MoveNext();
end;
end;
begin
try
CoInitialize(nil);
try
QuerySystemIndex;
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
You can found alternatives ways to access the Search Index on the MSDN documentation.
There is an API for Windows Search (previously known as Windows Desktop Search).
However, whilst the Windows Search API is undoubtedly enormously powerful, I think for simply locating files based on file extension (or even other constituent elements in the file name) the Windows Search API is likely to prove prohibitively complex and provide negligible benefit, unless you are dealing with a truly extraordinary number of files.

Reading from text file into list in FreePascal

I have a text file including:
John###198cm###90kg###19age
Tom###120cm###34kg###8age
And I want to read them from file into two lists in FreePascal.
I have tried to use LoadFromFile function, which should make a line into list, but it is not working for me.
This is a variation of your question Reading from file FreePascal.
Here is an example using ReplaceStr() to convert the ### characters into a CR LF pair.
When assigned to the text property of a new list, it will be splitted into items.
Uses
StrUtils;
procedure HandleText;
var
i : Integer;
sSourceList : TStringList;
sExpandedList : TStringList;
begin
sSourceList := TStringList.Create;
sExpandedList := TStringList.Create;
try
sSourceList.LoadFromFile('MySource.txt');
for i := 0 to sSourceList.Count-1 do begin
sExpandedList.Text := ReplaceStr(sSourceList[i],'###',#13#10);
// Do something with your lists
// sExpandedList[0] = 'John' etc ...
end;
finally
sSourceList.Free;
sExpandedList.Free;
end;
end;

saving a records containing a member of type string to a file (Delphi, Windows)

I have a record that looks similar to:
type
TNote = record
Title : string;
Note : string;
Index : integer;
end;
Simple. The reason I chose to set the variables as string (as opposed to an array of chars) is that I have no idea how long those strings are going to be. They can be 1 char long, 200 or 2000.
Of course when I try to save the record to a type file (file of...) the compiler complains that I have to give a size to string.
Is there a way to overcome this? or a way to save those records to an untyped file and still maintain a sort of searchable way?
Please do not point me to possible solutions, if you know the solution please post code.
Thank you
You can't do it with a typed file. Try something like this, with a TFileStream:
type
TStreamEx = class helper for TStream
public
procedure writeString(const data: string);
function readString: string;
procedure writeInt(data: integer);
function readInt: integer;
end;
function TStreamEx.readString: string;
var
len: integer;
iString: UTF8String;
begin
self.readBuffer(len, 4);
if len > 0 then
begin
setLength(iString, len);
self.ReadBuffer(iString[1], len);
result := string(iString);
end;
end;
procedure TStreamEx.writeString(const data: string);
var
len: cardinal;
oString: UTF8String;
begin
oString := UTF8String(data);
len := length(oString);
self.WriteBuffer(len, 4);
if len > 0 then
self.WriteBuffer(oString[1], len);
end;
function TStreamEx.readInt: integer;
begin
self.readBuffer(result, 4);
end;
procedure TStreamEx.writeInt(data: integer);
begin
self.WriteBuffer(data, 4);
end;
type
TNote = record
Title : string;
Note : string;
Index : integer;
procedure Save(stream: TStream);
end;
procedure TNote.Save(stream: TStream);
var
temp: TMemoryStream;
begin
temp := TMemoryStream.Create;
try
temp.writeString(Title);
temp.writeString(Note);
temp.writeInt(Index);
temp.seek(0, soFromBeginning);
stream.writeInt(temp.size);
stream.copyFrom(temp, temp.size);
finally
temp.Free;
end;
end;
I'll leave the Load procedure to you. Same basic idea, but it shouldn't need a temp stream. With the record size in front of each entry, you can read it and know how far to skip if you're looking for a certain record # instead of reading the whole thing.
EDIT: This was written specifically for versions of Delphi that use Unicode strings. On older versions, you could simplify it quite a bit.
Why not write this out as XML? See my session "Practical XML with Delphi" on how to get started with this.
Another possibility would be to make your records into classes descending form TComponent and store/retreive your data in DFM files.
This Stackoverflow entry shows you how to do that.
--jeroen
PS: Sorry my XML answer was a bit dense; I'm actually on the road for two conferences (BASTA! and DelphiLive! Germany).
Basically what you need to do is very simple: create a sample XML file, then start the Delphi XML Data Binding Wizard (available in Delphi since version 6).
This wizard will generate a unit for you that has the interfaces and classes mapping XML to Delphi objects, and a few helper functions for reading them from file, creating a new object, etc. My session (see the first link above) actually contains most of the details for this process.
The above link is a video demonstrating the usage of the Delphi XML Data Binding Wizard.
You could work with two different files, one that just stores the strings in some convenient way, the other stores the records with a reference to the strings. That way you will still have a file of records for easy access even though you don't know the size of the actual content.
(Sorry no code.)
TNote = record
Title : string;
Note : string;
Index : integer;
end;
could be translated as
TNote = record
Title : string[255];
Note : string[255];
Index : integer;
end;
and use Stream.writebuffer(ANodeVariable, sizeof(TNode), but you said that strings get go over 255 chars in this case IF a string goes over 65535 chars then change WORD to INTEGER
type
TNodeHeader=Record
TitleLen,
NoteLen: Word;
end;
(* this is for writing a TNode *)
procedure saveNodetoStream(theNode: TNode; AStream: TStream);
var
header: TNodeHeader;
pStr: PChar;
begin
...
(* writing to AStream which should be initialized before this *)
Header.TitleLen := Length(theNode.Title);
header.NodeLen := Length(theNode.Note);
AStream.WriteBuffer(Header, sizeof(TNodeHeader);
(* save strings *)
PStr := PChar(theNode.Title);
AStream.writeBuffer(PStr^, Header.TitleLen);
PStr := PChar(theNode.Note);
AStream.writebuffer(PStr^, Header.NoteLen);
(* save index *)
AStream.writebuffer(theNode.Index, sizeof(Integer));
end;
(* this is for reading a TNode *)
function readNode(AStream: TStream): TNode;
var
header: THeader
PStr: PChar;
begin
AStream.ReadBuffer(Header, sizeof(TNodeHeader);
SetLength(Result.Title, Header.TitleLen);
PStr := PChar(Result.Title);
AStream.ReadBuffer(PStr^, Header.TitleLen);
SetLength(Result.Note, Header.NoteLen);
PStr := PChar(Result.Note);
AStream.ReadBuffer(PStr^, Header.NoteLen);
AStream.ReadBuffer(REsult.Index, sizeof(Integer)(* 4 bytes *);
end;
You can use the functions available in this Open Source unit.
It allows you to serialize any record content into binary, including even dynamic arrays within:
type
TNote = record
Title : string;
Note : string;
Index : integer;
end;
var
aSave: TRawByteString;
aNote, aNew: TNote;
begin
// create some content
aNote.Title := 'Title';
aNote.Note := 'Note';
aNote.Index := 10;
// serialize the content
aSave := RecordSave(aNote,TypeInfo(TNote));
// unserialize the content
RecordLoad(aNew,pointer(aSave),TypeInfo(TNote));
// check the content
assert(aNew.Title = 'Title');
assert(aNew.Note = 'Note');
assert(aNew.Index = 10);
end;

BDE vs ADO in Delphi

Please note the Edit below for a lot more information, and a possible solution
We recently modified a large Delphi application to use ADO connections and queries instead of BDE connections and queries. Since that change, performance has become terrible.
I've profiled the application and the bottleneck seems to be at the actual call to TADOQuery.Open. In other words, there isn't much I can do from a code standpoint to improve this, other than restructuring the application to actually use the database less.
Does anyone have suggestions about how to improve the performance of an ADO-connected Delphi application? I've tried both of the suggestions given here, with virtually no impact.
To give an idea of the performance difference, I benchmarked the same large operation:
Under BDE: 11 seconds
Under ADO: 73 seconds
Under ADO after the changes referenced by that article: 72 seconds
We are using an Oracle back-end in a client-server environment. Local machines each maintain a separate connection to the database.
For the record, the connection string looks like this:
const
c_ADOConnString = 'Provider=OraOLEDB.Oracle.1;Persist Security Info=True;' +
'Extended Properties="plsqlrset=1";' +
'Data Source=DATABASE.DOMAIN.COM;OPTION=35;' +
'User ID=******;Password=*******';
To answer the questions posed by zendar:
I'm using Delphi 2007 on Windows Vista and XP.
The back end is an Oracle 10g database.
As indicated by the connection string, we are using the OraOLEDB driver.
The MDAC version on my benchmark machine is 6.0.
Edit:
Under the BDE, we had a lot of code that looked like this:
procedure MyBDEProc;
var
qry: TQuery;
begin
//fast under BDE, but slow under ADO!!
qry := TQuery.Create(Self);
try
with qry do begin
Database := g_Database;
Sql.Clear;
Sql.Add('SELECT');
Sql.Add(' FIELD1');
Sql.Add(' ,FIELD2');
Sql.Add(' ,FIELD3');
Sql.Add('FROM');
Sql.Add(' TABLE1');
Sql.Add('WHERE SOME_FIELD = SOME_CONDITION');
Open;
//do something
Close;
end; //with
finally
FreeAndNil(qry);
end; //try-finally
end; //proc
But we found that the call to Sql.Add is actually very expensive under ADO, because the QueryChanged event is fired every time you change the CommandText. So replacing the above with this was MUCH faster:
procedure MyADOProc;
var
qry: TADOQuery;
begin
//fast(er) under ADO
qry := TADOQuery.Create(Self);
try
with qry do begin
Connection := g_Connection;
Sql.Text := ' SELECT ';
+ ' FIELD1 '
+ ' ,FIELD2 '
+ ' ,FIELD3 '
+ ' FROM '
+ ' TABLE1 '
+ ' WHERE SOME_FIELD = SOME_CONDITION ';
Open;
//do something
Close;
end; //with
finally
FreeAndNil(qry);
end; //try-finally
end; //proc
Better yet, you can copy TADOQuery out of ADODB.pas, rename it under a new name, and rip out the QueryChanged event, which as far as I can tell, is not doing anything useful at all. Then use your new, modified version of TADOQuery, instead of the native one.
type
TADOQueryTurbo = class(TCustomADODataSet)
private
//
protected
procedure QueryChanged(Sender: TObject);
public
FSQL: TWideStrings;
FRowsAffected: Integer;
function GetSQL: TWideStrings;
procedure SetSQL(const Value: TWideStrings);
procedure Open;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function ExecSQL: Integer; {for TQuery compatibility}
property RowsAffected: Integer read FRowsAffected;
published
property CommandTimeout;
property DataSource;
property EnableBCD;
property ParamCheck;
property Parameters;
property Prepared;
property SQL: TWideStrings read FSQL write SetSQL;
end;
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
constructor TADOQueryTurbo.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FSQL := TWideStringList.Create;
TWideStringList(FSQL).OnChange := QueryChanged;
Command.CommandText := 'SQL'; { Do not localize }
end;
destructor TADOQueryTurbo.Destroy;
begin
inherited;
inherited Destroy;
FreeAndNil(FSQL);
end;
function TADOQueryTurbo.ExecSQL: Integer;
begin
CommandText := FSQL.Text;
inherited;
end;
function TADOQueryTurbo.GetSQL: TWideStrings;
begin
Result := FSQL;
end;
procedure TADOQueryTurbo.Open;
begin
CommandText := FSQL.Text;
inherited Open;
end;
procedure TADOQueryTurbo.QueryChanged(Sender: TObject);
begin
// if not (csLoading in ComponentState) then
// Close;
// CommandText := FSQL.Text;
end;
procedure TADOQueryTurbo.SetSQL(const Value: TWideStrings);
begin
FSQL.Assign(Value);
CommandText := FSQL.Text;
end;
I don't know about Delphi 2007, but I did same thing with Delphi 7 and Oracle 8.
Here are things I did:
Set TAdoDataSet.CursorLocation according to query:
clUseClient if query fetches records for GUI and query is relatively "simple" - no grouping or sum
clUseServer if query have some sort of aggregation (sum, grouping, counting)
Set TAdoDataSet.CursorType according to query:
ctForwardOnly for reports where you don't need scroll back through dataset - works only with clUseServer
ctStatic for GUI. This is only mode that works with clUseClient
Set TAdoDataSet.LockType according to query:
ltReadOnly for every dataset that is not used for editing (grids, reports)
ltOptimistic when records are posted to database immediately after change (e.g. user editing data on form)
ltBatchOptimistic when you change large number of records. This is for situations where you fetch number of records, then do some processing on them and then send updates to database in batch. This works best combined with clUseClient and ctStatic.
In my experience, Microsoft OLEDB provider for Oracle worked better than Oracle OleDb provider. You should test that. Edit: Check Fabricio's comment about possible blob problems.
Replace TAdoQUery with TAdoDataSet. TAdoQuery was created for conversion of apps from BDE to ADO, but Borland/Codegear recomendation was to use TAdoDataSet
Recheck Oracle connection string to be sure that you do not have network latency. How long it lasts to connect to Oracle? How long is TnsPing?
i found the performance problems with ADOExpress years ago:
ADO vs ADOExpress time trials. Not good for ADOExpress (6/7/2005)
ADO vs ADO Express Time Trials (redux) (12/30/2007)
Note: Before ADO became a standard part of Delphi, Borland was selling it as an addon called ADOExpress. It was simply object wrappers around Microsoft's ActiveX Data Objects (ADO) COM objects.
i had tested three scenarios
using ADO directly (i.e. Microsoft's COM objects directly)
using ADOExpress (Borland's object wrappers around ADO)
specifying .DisableControls on the TADOQuery before calling Open
i discovered
use Query.DisableControls to make each call .Next 50x faster
use Query.Recordset.Fields.Items['columnName'].Value rather than Query.FieldByName('columnName') to make each value lookup 2.7x faster
using TADODataSet (verses TADOQuery) makes no difference
Loop Results Get Values
ADOExpress: 28.0s 46.6s
ADOExpress w/DisableControls: 0.5s 17.0s
ADO (direct use of interfaces): 0.2s 4.7s
Note: These values are for looping 20,881 rows, and looking up the values of 21 columns.
Baseline Bad Code:
var
qry: TADOQuery;
begin
qry := TADOQuery.Create(nil);
try
qry.SQL.Add(CommandText);
qry.Open;
while not qry.EOF do
begin
...
qry.Next;
end;
Use DisableControls to make looping 5000% faster:
var
qry: TADOQuery;
begin
qry := TADOQuery.Create(nil);
try
qry.DisableControls;
qry.SQL.Add(CommandText);
qry.Open;
while not qry.EOF do
begin
...
qry.Next;
end;
Use Fields collection to make value lookups 270% faster:
var
qry: TADOQuery;
begin
qry := TADOQuery.Create(nil);
try
qry.DisableControls;
qry.SQL.Add(CommandText);
qry.Open;
while not qry.EOF do
begin
value1 := VarAsString(qry.Recordset.Fields['FieldOne'].Value);
value2 := VarAsInt(qry.Recordset.Fields['FieldTwo'].Value);
value3 := VarAsInt64(qry.Recordset.Fields['FieldTwo'].Value);
value4 := VarAsFloat(qry.Recordset.Fields['FieldThree'].Value);
value5 := VarAsWideString(qry.Recordset.Fields['FieldFour'].Value);
...
value56 := VarAsMoney(qry.Recordset.Fields['FieldFive'].Value);
qry.Next;
end;
Since it is a common enough problem, we created a helper method to solve the issue:
class function TADOHelper.Execute(const Connection: TADOConnection;
const CommandText: WideString): TADOQuery;
var
rs: _Recordset;
query: TADOQuery;
nRecords: OleVariant;
begin
Query := TADOQuery.Create(nil);
Query.DisableControls; //speeds up Query.Next by a magnitude
Query.Connection := Connection;
Query.SQL.Text := CommandText;
try
Query.Open();
except
on E:Exception do
begin
Query.Free;
raise;
end;
end;
Result := Query;
end;
For best performance, should get a look at our Open Source direct access to Oracle.
If you are processing a lot of TQuery, without using the DB components, we have a dedicated pseudo-class to use direct OCI connection, as such:
Q := TQuery.Create(aSQLDBConnection);
try
Q.SQL.Clear; // optional
Q.SQL.Add('select * from DOMAIN.TABLE');
Q.SQL.Add(' WHERE ID_DETAIL=:detail;');
Q.ParamByName('DETAIL').AsString := '123420020100000430015';
Q.Open;
Q.First; // optional
while not Q.Eof do begin
assert(Q.FieldByName('id_detail').AsString='123420020100000430015');
Q.Next;
end;
Q.Close; // optional
finally
Q.Free;
end;
And I've added some unique access via a late-binding Variant, to write direct code as such:
procedure Test(Props: TOleDBConnectionProperties; const aName: RawUTF8);
var I: ISQLDBRows;
Customer: Variant;
begin
I := Props.Execute('select * from Domain.Customers where Name=?',[aName],#Customer);
while I.Step do
writeln(Customer.Name,' ',Customer.FirstName,' ',Customer.Address);
end;
var Props: TOleDBConnectionProperties;
begin
Props := TSQLDBOracleConnectionProperties.Create(
'TnsName','UserName','Password',CODEPAGE_US);
try
Test(Props,'Smith');
finally
Props.Free;
end;
end;
Note that all OleDB providers are buggy for handling BLOBs: Microsoft's version just do not handle them, and Oracle's version will randomly return null for 1/4 of rows...
On real database, I found out our direct OCI classes to be 2 to 5 times faster than the OleDB provider, without the need to install this provider. You can even use the Oracle Instant Client provided by Oracle which allows you to run your applications without installing the standard (huge) Oracle client or having an ORACLE_HOME. Just deliver the dll files in the same directory than your application, and it will work.

Resources