Here's my architecture :
Datasnap Client <=> Datasnap Server <=> Oracle 11 XE
I'm using a remote provider with TDSProviderConnection from the client side to access my dataset.
Basically, I'm using a TIdHTTP component to query a website and store the result in an Oracle CLOB column.
When saving result to a file, text, accented and other exotic characters are displayed correctly.
same text inserted into a clob using sqldeveloper is displayed correctly as well.
But when I'm doing this thru datasnap architecture, wrong characters shows up (like black diamonds or "upperscore" (underscore on top)
My DB Charset is AL32UTF8, which is the default charset on Oracle 11 XE.
To better understand where the problem is, I rewrote part of my client to access my database directly. And I can say the problem is not in the communication between datasnap client and server.
Now my architecture is :
Client <=> Database
And I access Oracle XE the following way :
TClientDataSet <=> TDataSetProvider <=> TSQLDataSet <=> TSQLConnection
Response from TIdHTTP, which is a TMemoryStream, is stored in the TClientDataset with :
With ClientDataSet do
begin
Edit;
(Fieldbyname('MYCLOBFIELD') as TBlobField).LoadFromStream(MS);
ApplyUpdates(-1);
end;
EDIT : 21st of may
I did test around TBlobField and this component seems to be part of my problem. Let me explain :
I took a random string containing characters from an extended charset like this : 'ÐÒÙÜßąĀûÆ'
And with my ClientDataSet, changed the assignment to this :
FieldByname('MYCLOB').value := 'ÐÒÙÜßąĀûÆ'; // <-- Inserted correctly into Oracle.
Putting this string in a file 'test.txt' and trying to display a popup with the content does not work :
var
MyBlobField: TBlobField;
begin
MyBlobField.LoadFromFile('test.txt');
ShowMessage(MyBlobField.AsString); // <-- does not display correctly
But Using a TMemo to display the content works like a charm :
var
MyMemo: TMemo;
begin
MyMemo.Lines.LoadFromFile('test.txt'); // <-- Works perfectly !!
I tried to set the TBlobField.BlobType property to ftOraClob or ftBlob with no luck.
Finally, using a TStringList (what TMemo.Lines is actually) to load my string into Oracle does the trick.
I guess either something is wrong with TBlobField.LoadFromFile/LoadFromStream or I'm not using it correctly.
TStringList inherits its LoadFromFile/LoadFromStream method from TStrings, which works.
Any help will be greatly appreciated.
Regards.
If you want to put some data to TBlobField you can try:
procedure SetParamBlob(Param : TParam; sData : String);
var
Str : TStringStream;
begin
Str := TStringStream.Create(sData);
try
Param.LoadFromStream(Str, ftBlob);
finally
Str.Free;
end;
end;
or this:
procedure SetParamBlob(Param : TParam; sData : String);
var List : TStringList;
MemStream : TMemoryStream;
begin
Param.Clear;
Param.DataType := ftBlob;
List := TStringList.Create;
MemStream := TMemoryStream.Create;
try
List.Text := sData;
List.SaveToStream(MemStream);
MemStream.Seek(0, soFromBeginning);
Param.LoadFromStream(MemStream, ftBlob);
finally
FreeAndNil(List);
FreeAndNil(MemStream);
end;
end;
...
...
SetParamBlob(q.ParamByName('FIELD'), MyMemo.Text);
...
You can load data from file by this:
function LoadData(sFileSrc : String) : String;
var F : TFileStream;
begin
F := TFileStream.Create(sFileSrc, fmOpenRead + fmShareDenyNone);
try
SetLength(Result, f.Size);
f.Read(Result[1],f.Size);
finally
F.Free;
end;
end;
Related
Hello everyone who reads this! I hope you can help me with my problem, but if not, thank you for try. I have DataSnap server and client. DataSnap server methods can return to client a DataSet as function result. I'm getting data from MySQL DB with TFDQuery component. Somebody please help me to understand, how can I get a dataset from FDQuery component that already has data?
TDataSet.Data is OleVariant type property containing all data. But FDQuery doesn't have the same property. I need to return a dataset from FDQuery as OleVariant in function.
*Try, Except, FreeAndNil, DisposeOf etc removed from code for better understanding the problem
//Client side
procedure TForm1.GetDataSetFromServer;
var
Server: TServerMethods1Client;
DS: TClientDataSet;
begin
Server := TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
DS := TClientDataSet.Create(nil);
DS.Data := Server.GetDataSet; //Call remote server method
end;
//DataSnap server side
function TServerMethods1.GetDataSet: OleVariant;
begin
FDQuery1.Close;
FDQuery1.SQL.Text := 'SELECT * FROM Table1';
FDQuery1.Open;
//Now i need to return all data as function result
result := ???
end;
Need any information that can be helpful. Thanks in advance! Have a nice day!
The simplest way to do this, AFAIK, is to add a TDataSetProvider, and also a TClientDataSet (if you don't already have one) to your Server module.
Then, you can modify your server code as follows:
function GetDataSet: OleVariant;
begin
if ClientDataSet1.Active then
ClientDataSet1.Close;
FDQuery1.Close;
FDQuery1.SQL.Text := 'SELECT * FROM Table1';
// FDQuery1.Open; Leave this to the DataSetProvider/ClientDataSet
//Now i need to return all data as function result
//result := ???
DataSetProvider1.DataSet := FDQuery1;
ClientDataSet1.ProviderName := 'DataSetProvider1';
ClientDataSet1.Open;
Result := ClientDataSet1.Data;
end;
The point of doing it this way is that TDataSetProvider has all the internal machinery necessary to package up its DataSet's data (i.e. FDQuery1's data) in a form that that can be sent between ClientDataSets. Incorporating the DataSetProvider in the server minimizes the code necessary in the client to consume the CDS data.
Btw, I'm assuming that your server module has the code necessary to 'export' GetDataSet as a server method.
You can also do return a TDataSet from the server function:
function TServerMethods1.GetDataSet: TDataSet;
begin
if ClientDataSet1.Active then
ClientDataSet1.Close;
FDQuery1.Close;
FDQuery1.SQL.Text := 'SELECT * FROM Table1';
DataSetProvider1.DataSet := FDQuery1;
ClientDataSet1.ProviderName := 'DataSetProvider1';
ClientDataSet1.Open;
Result := ClientDataSet1;
end;
For the client side, it now depends on what kind of server you are using.
If it is a DBX Datasnap Server, you will have to use a TsqlServerMethod (note the lower case) 'sql' here, and a TDataSetProvider with a TClientDataset, all preconfigured to retrieve the data from that Server.
If it is a REST Datasnap server, you can do this at the client:
procedure TfrmClientMain.btnRefreshClick(Sender: TObject);
var
Server: TServerMethods1Client;
lDataSet: TDataSet;
DSP: TDataSetProvider;
begin
Server := TServerMethods1Client.Create(ClientModule1.DSRestConnection1);
try
CDS.Close; // a TClientDataSet has been placed on the form
lDataSet := Server.GetDataSet();
DSP := TDataSetProvider.Create(Self);
try
DSP.DataSet := lDataSet;
CDS.SetProvider(DSP);
CDS.Open;
finally
CDS.SetProvider(nil);
DSP.Free;
end;
finally
Server.Free;
end;
end;
I used the CharPrinter.pas unit to send commands in ZPLII for a ZEBRA printer in Delphi RAD2007 and everything worked well, but I change to XE7 and tried to use the same functions and sending to the printer and does not work, and does not give any error message. Does it have to do the new data type versions XE ?
This happened cause Delphi switched to Unicode strings in Delphi 2009.
Unicode strings use 2 bytes for every char. Older Delphi versions used 1 byte for every char.
So you must make some changes in ChatPrinter.pas.
Try to edit this (not tested):
procedure TCharPrinter.SendData (aData : String);
var
Data : array[0..255] of char;
cnt : integer;
ss : TStringStream;
begin
try
ss := TStringStream.Create(aData,TEncoding.ANSI);
fStream.CopyFrom (ss,0);
finally
ss.Free;
end;
// for cnt := 0 to length(aData) - 1
// do Data[cnt] := aData[cnt+1];
end;
Or simpler:
procedure TCharPrinter.SendData (aData : String);
var
Data : AnsiString;
begin
Data := AnsiString(aData);
fStream.Write(PAnsiChar(Data)^, Length(Data));
end;
1st off I am Still a little green to Delphi so this might be a "mundane detail" that's being over looked. [sorry in advance]
I have a need to create a TSQLDataset or TClientDataSet from an Oracle 11g cursor contained in a package. I am using Delphi XE2 and DBExpress to connect to the DB and DataSnap to send the data back to the client.
I'm having problems executing the stored procedure from the Delphi code.
Package Head:
create or replace
PACKAGE KP_DATASNAPTEST AS
procedure GetFaxData(abbr varchar2, Res out SYS_REFCURSOR);
END KP_DATASNAPTEST;
Package Body:
create or replace
PACKAGE body KP_DATASNAPTEST AS
procedure GetFaxData(abbr varchar2, Res out SYS_REFCURSOR)is
Begin
open Res for
SELECT Name,
Address1,
City,
fax_nbr
FROM name
JOIN phone on name.Abrv = phone.abrv
WHERE phone.fax_nbr is not null and name.abrv = abbr;
end;
END KP_DATASNAPTEST;
I have no problem executing this procedure in SQL Developer the problem resides in this code on the DataSnap server:
function TKPSnapMethods.getCDS_Data2(): OleVariant;
var
cds: TClientDataSet;
dsp: TDataSetProvider;
strProc: TSQLStoredProc;
begin
strProc := TSQLStoredProc.Create(self);
try
strProc.MaxBlobSize := -1;
strProc.SQLConnection:= SQLCon;//TSQLConnection
dsp := TDataSetProvider.Create(self);
try
dsp.ResolveToDataSet := True;
dsp.Exported := False;
dsp.DataSet := strProc;
cds := TClientDataSet.Create(self);
try
cds.DisableStringTrim := True;
cds.ReadOnly := True;
cds.SetProvider(dsp);
strProc.Close;
strProc.StoredProcName:= 'KP_DATASNAPTEST.GetFaxData';
strProc.ParamCheck:= true;
strProc.ParamByName('abbr').AsString:= 'ZZZTOP';
strProc.Open; //<--Error: Parameter 'Abbr' not found.
cds.Open;
Result := cds.Data;
finally
FreeAndNil(cds);
end;
finally
FreeAndNil(dsp);
end;
finally
FreeAndNil(strProc);
self.SQLCon.Close;
end;
end;
I have also tried assigning the param value through the ClientDataSet without any luck.
I would not be apposed to returning a TDataSet from the function if its easier or produces results. The data is used to populate custom object attributes.
As paulsm4 mentioned in this answer, Delphi doesn't care about getting stored procedure parameter descriptors, and so that you have to it by yourself. To get params of the Oracle stored procedure from a package, you can try to use the GetProcedureParams method to fill the list with parameter descriptors and with the LoadParamListItems procedure fill with that list Params collection. In code it might look like follows.
Please note, that following code was written just in browser according to documentation, so it's untested. And yes, about freeing ProcParams variable, this is done by the FreeProcParams procedure:
var
ProcParams: TList;
StoredProc: TSQLStoredProc;
...
begin
...
StoredProc.PackageName := 'KP_DATASNAPTEST';
StoredProc.StoredProcName := 'GetFaxData';
ProcParams := TList.Create;
try
GetProcedureParams('GetFaxData', 'KP_DATASNAPTEST', ProcParams);
LoadParamListItems(StoredProc.Params, ProcParams);
StoredProc.ParamByName('abbr').AsString := 'ZZZTOP';
StoredProc.Open;
finally
FreeProcParams(ProcParams);
end;
...
end;
I don't think Delphi will automagically recognize Oracle parameter names and fill them in for you. I think you need to add the parameters. For example:
with strProc.Params.Add do
begin
Name := 'abbr';
ParamType := ptInput;
Value := ZZZTOP';
...
end;
and I am tearing my hair out!!
Even something simple like this work:
procedure MyAdoQueryTest();
const MYSQL_CONNECT_STRING='Driver={MySQL ODBC 5.1 Driver};Server=%s;Port=3306;Database=%s;User=%s;Password=%s;Option=3;';
var AdoConnection : TADOConnection;
ADOQuery : TADOQuery;
Param : TParameter;
begin
AdoConnection := TADOConnection.Create(Nil);
AdoConnection.ConnectionString := Format(MYSQL_CONNECT_STRING,['localhost',
'mysql',
'root',
'']);
AdoConnection.LoginPrompt := False;
AdoConnection.Connected := True;
ADOQuery := TADOQuery.Create(Nil);
ADOQuery.Connection := AdoConnection;
ADOQuery.Sql.Clear();
ADOQuery.SQl.Add('SHOW :what_to_show');
Param := ADOQuery.Parameters.ParamByName('what_to_show');
Param.DataType := ftString;
Param.Value := 'databases';
ADOQuery.Prepared := true;
ADOQuery.Active := True;
end;
(btw, do I really need to use the 'Param' variable and 3 statements, or can I just ` ADOQuery.Parameters.ParamByName('what_to_show').Value := 'databases';?)
Anyway, when I run it, I get an exception at ADOQuery.SQl.Add('SHOW :what_to_show'); which says "Arguments are of the wrong type, are out of the acceptable range or are in conflict with one another".
What I am trying to do is to make 2 central functions: one which will accept and execute any SQL statement which will not return any data (such as INSERT INTO) and oen which will (such as SELECT).
I currently have these working with AdoConnection only, but am now trying to use AdoQuery because I want to parametrize my SQL statements to handle strings with quotes in them.
I can has halpz?
The error is here:
ADOQuery.SQl.Add('SHOW :what_to_show');
The :Param can only be used for values, not for dynamic column/keyword/table/database names.
This is because if it worked like that you'd have an SQL-injection risk depending on the contents of your parameter.
In order to fix that you'll have to inject your what_to_show thingy into the SQL-string.
Like so:
var
what_to_show: string;
begin
....
what_to_show:= 'tables';
ADOQuery.SQL.Text:= ('SHOW '+what_to_show);
....
Now it will work.
Warning
Make sure test everything you inject into the SQL to prevent users from being able inject their SQL-code into your queries.
Parameters prevent SQL injection, but because you cannot use them here you need to check them against a list of pre-approved values. e.g. a stringlist holding all the allowed what_to_shows.
Escaping or use of special chars is useless.
Safe injection example code
var
what_to_show: string;
i: integer;
inputapproved: boolean;
begin
....
what_to_show:= lower(trim(someinput));
i:= 0;
inputapproved:= false;
while (i < WhiteList.count) and not(inputapproved) do begin
inputapproved:= ( what_to_show = lower(Whitelist[i]) );
Inc(i);
end; {while}
if inputapproved then ADOQuery.SQL.Text:= ('SHOW '+what_to_show);
....
In this question is mentioned the wcrypt2.
What I need is simply calculate the MD5 of a file. It would be perfect if I could calculate it without having to save it because it is a downloaded file in stream format.
I would like to have the most straightforward way to do that.
Thanks!
Here is a working code for Indy 10:
function MD5File(const FileName: string): string;
var
IdMD5: TIdHashMessageDigest5;
FS: TFileStream;
begin
IdMD5 := TIdHashMessageDigest5.Create;
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := IdMD5.HashStreamAsHex(FS)
finally
FS.Free;
IdMD5.Free;
end;
end;
Regards,
OscaR1
Based on #dummzeuch answere I wrote this function:
function getMD5checksum(s: TStream): string;
var
md5: TIdHashMessageDigest5;
hash : T4x4LongWordRecord;
begin
md5 := TIdHashMessageDigest5.Create;
s.Seek(0,0);
hash := md5.HashValue(s);
result := IntToHex(Integer(hash[0]), 4) +
IntToHex(Integer(hash[1]), 4) +
IntToHex(Integer(hash[2]), 4) +
IntToHex(Integer(hash[3]), 4);
end;
Indy comes with functions for calculating several hashes, MD5 is one of them. Indy is included in all versions of Delphi since at least Delphi 2006 and available as a free download for older versions.
What about:
function GetFileMD5(const Stream: TStream): String; overload;
var MD5: TIdHashMessageDigest5;
begin
MD5 := TIdHashMessageDigest5.Create;
try
Result := MD5.HashStreamAsHex(Stream);
finally
MD5.Free;
end;
end;
function GetFileMD5(const Filename: String): String; overload;
var FileStream: TFileStream;
begin
FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := GetFileMD5(FileStream);
finally
FileStream.Free;
end;
end;
As you mentioned, the post you linked to talks about wcrypt2, which is a library of cryptographic routines, including MD5. The post you linked to also seems to indicate that it is available for Delphi 7 since the asker includes output labeled "Delphi 7." You have tagged this question delphi7, so I assume that's the version you're using, too. So what's stopping you from using wcrypt2?
The question links to a copy of wcrypt2.pas, and the copyright dates in that file appear to indicate that the unit was available by the time Delphi 7 was released. Check your installation; you might already have it. If not, then the unit also says that it was obtained via Project Jedi, so you could try looking there for the unit as well.
The answers to your referenced question include example Delphi code and the names of units that come with Delphi for doing MD5. They come with Delphi 2009, so you should check whether they're also available for your version.
Take a look at this implementation of MD5SUM in Delphi. It requires a string for input, but I imagine you can easily make it work with a stream.
MessageDigest_5 would work for this as well.
I use the following function in Delphi 7 with Indy 10.1.5
uses IdHashMessageDigest, idHash, Classes;
...
function cc_MD5File(const p_fileName : string) : string;
//returns MD5 has for a file
var
v_idmd5 : TIdHashMessageDigest5;
v_fs : TFileStream;
v_hash : T4x4LongWordRecord;
begin
v_idmd5 := TIdHashMessageDigest5.Create;
v_fs := TFileStream.Create(p_fileName, fmOpenRead OR fmShareDenyWrite) ;
try
v_hash := v_idmd5.HashValue(v_fs);
result := v_idmd5.AsHex(v_hash);
finally
v_fs.Free;
v_idmd5.Free;
end;
end;
If you use Overbyte http://www.overbyte.eu/frame_index.html just add unit and call function FileMD5 with name of file
uses OverbyteIcsMd5;
....
function GetMd5File:String;
begin
Result := FileMD5(FileName);
end;