Delphi SQL integration using UPDATE - mysql

I have a database application in Delphi and I am trying to open a record and update it. The following is how I do it now:
procedure TWebsiteRecord.UpdateRecord(Website : TWebsite);
var
SQL : string;
begin
RecordQuery.SQL.Clear;
SQL := 'UPDATE website SET Domain=:D, Template=:T, WebHost=:Wh, DomainRegistrar=:Dr, OrderDate=:Od, RenewalDate=:Rd, RenewalCost=:Rc, PaymentMethod=:Pm,' + 'OwnDomainStatus=:OStat, CancellationStatus=:CStat, ReminderStatus=:RStat, WebsiteNotes=:N, FTPUsername=:U1, FTPPassword=:P1, EmailPassword=:P2, PaidForYear=:PStat, CustomerID=:CID WHERE WebsiteID=:WID;';
RecordQuery.ParamCheck := True;
RecordQuery.SQL.Add(SQL);
RecordQuery.Params.ParamByName('D').AsString := Website.D;
RecordQuery.Params.ParamByName('T').AsString := Website.T;
RecordQuery.Params.ParamByName('Wh').AsString := Website.Wh;
RecordQuery.Params.ParamByName('Dr').AsString := Website.Dr;
RecordQuery.Params.ParamByName('Od').AsString := Website.Od;
RecordQuery.Params.ParamByName('Rd').AsString := Website.Rd;
RecordQuery.Params.ParamByName('Rc').AsInteger := Website.Rc;
RecordQuery.Params.ParamByName('Pm').AsString := Website.Pm;
RecordQuery.Params.ParamByName('OStat').AsInteger := Website.OStat;
RecordQuery.Params.ParamByName('CStat').AsString := Website.Cstat;
RecordQuery.Params.ParamByName('RStat').AsString := Website.Rstat;
RecordQuery.Params.ParamByName('N').AsString := Website.N;
RecordQuery.Params.ParamByName('U1').AsString := Website.U1;
RecordQuery.Params.ParamByName('P1').AsString := Website.P1;
RecordQuery.Params.ParamByName('P2').AsString := Website.P2;
RecordQuery.Params.ParamByName('PStat').AsInteger := Website.PStat;
RecordQuery.Params.ParamByName('CID').AsInteger := Website.CID;
RecordQuery.Params.ParamByName('WID').AsInteger := Website.WID;
RecordQuery.ExecSQL;
end;
and
procedure TWebsiteRecord.SaveBtnClick(Sender: TObject);
var
Website : TWebsite;
begin
if Validate then
begin
Website.D := DomainEdit.Text;
Website.T := TemplateEdit.Text;
Website.Wh := WebHostEdit.Text;
Website.Dr := DomainRegEdit.Text;
Website.Od := GetSQLDate(Date1Edit.Text);
Website.Rd := GetSQLDate(Date2Edit.Text);
if Website.Rd = '' then Website.Rd := '70/01/19';
if CostEdit.Text = '' then website.Rc := 0
else Website.Rc := strtoint(CostEdit.Text);
Website.Pm := GetPaymentMethod(PaymentMethodCombo.ItemIndex);
Website.OStat := integer(OwnDomainCheck.Checked);
if PendingCheck.Checked then Website.Cstat := 'P'
else if CancelledCheck.Checked then Website.Cstat := 'C'
else Website.Cstat := 'A';
Website.Rstat := GetSent(ReminderStatusCombo.ItemIndex);
Website.N := NotesMemo.Text;
Website.U1 := FTPUserEdit.Text;
Website.P1 := FTPPassEdit.Text;
Website.P2 := EmailPassEdit.Text;
Website.PStat := integer(PaidCheck.Checked);
Website.CID := strtoint(CustIDEdit.Text);
UpdateRecord(Website);
messagedlg('Website successfully updated, You will now be returned to the website table',mtinformation,[mbOK],0);
WebsiteTable.WebsiteCDS.Refresh;
Free;
end;
end;
There are no errors caused when this is executed, but the record is not updated and remains exactly the same as it was before. Does anyone know this problem? IIf so what can i do. I can provide more code if it is needed. Thanks in advance

As I can see your main problem is that you don't know what exact query is executed by the server.
It may seem a bit old fashioned, but if I were you I'd use the plain old FORMAT() function to debug the query and see what exactly is passed to the server. In this case your code will be something like that:
SQL := FORMAT('UPDATE website SET Domain="%s", Template="%s", WebHost="%s", DomainRegistrar="%s", OrderDate="%s", RenewalDate="%s", RenewalCost=%d, PaymentMethod="%s",' +
'OwnDomainStatus=%d, CancellationStatus="%s", ReminderStatus="%s", WebsiteNotes="%s", FTPUsername="%s", FTPPassword="%s", EmailPassword="%s", PaidForYear=%d, CustomerID=%d WHERE WebsiteID=%d',
[
RecordQuery.Params.ParamByName('D').AsString,
RecordQuery.Params.ParamByName('T').AsString,
RecordQuery.Params.ParamByName('Wh').AsString,
RecordQuery.Params.ParamByName('Dr').AsString,
RecordQuery.Params.ParamByName('Od').AsString,
RecordQuery.Params.ParamByName('Rd').AsString,
RecordQuery.Params.ParamByName('Rc').AsInteger,
RecordQuery.Params.ParamByName('Pm').AsString,
RecordQuery.Params.ParamByName('OStat').AsInteger,
RecordQuery.Params.ParamByName('CStat').AsString,
RecordQuery.Params.ParamByName('RStat').AsString,
RecordQuery.Params.ParamByName('N').AsString,
RecordQuery.Params.ParamByName('U1').AsString,
RecordQuery.Params.ParamByName('P1').AsString,
RecordQuery.Params.ParamByName('P2').AsString,
RecordQuery.Params.ParamByName('PStat').AsInteger,
RecordQuery.Params.ParamByName('CID').AsInteger,
RecordQuery.Params.ParamByName('WID').AsInteger
]);
and you could view the query, log it in a text file or just copy it and paste it somewhere to view it's execution like mysql command prompt or phpmyadmin.
If you have access to server's log file this is not needed - just open it and see the query inside.
Of cource once the query works as expected you may use it with parameters. And depending on what exactly connection type you are using there may be other (and faster) ways to get the actual query, e.g. this question

Related

FireDAC GetTableNames MySQL

I can't get the table names from databases other than the database specified in connection's params.
First, I used GetTableNames and it worked fine, but I was specifying the same database from connection's params.
DM.FDConnection.GetTableNames(ADatabse, '', APattern, tables, [osMy], [tkTable], False);
But when I tried to specify other database, I did not work. Then, I tried to use the TFDMetaInfoQuery, but it did not work either:
FDMetaInfoQuery := TFDMetaInfoQuery.Create(nil);
FDMetaInfoQuery.Connection := DM.FDConnection;
FDMetaInfoQuery.MetaInfoKind := mkTables;
FDMetaInfoQuery.CatalogName := 'databasename'
FDMetaInfoQuery.Open;
while not FDMetaInfoQuery.Eof do
begin
Result := Result + sLineBreak + FDMetaInfoQuery.FieldByName('TABLE_NAME').AsString;
FDMetaInfoQuery.Next;
end;
But I can get all the databases name:
FDMetaInfoQuery := TFDMetaInfoQuery.Create(nil);
FDMetaInfoQuery.Connection := DM.FDConnection;
FDMetaInfoQuery.MetaInfoKind := mkCatalogs;
FDMetaInfoQuery.Open;
while not FDMetaInfoQuery.Eof do
begin
Result := Result + sLineBreak + FDMetaInfoQuery.FieldByName('CATALOG_NAME').AsString;
FDMetaInfoQuery.Next;
end;
I already tried to specify those param in the connection, but nothing changed:
DM.FDConnection.Params.Add('MetaDefSchema=*');
DM.FDConnection.Params.Add('MetaDefCatalog=*');
DM.FDConnection.Params.Add('MetaCurSchema=*');
DM.FDConnection.Params.Add('MetaCurCatalog=*');
So, how should I get the table names from others databases?
I found out, my very question is the answer.. I should include osOther in the TFDPhysObjectScopes..
DM.FDConnection.GetTableNames(ADatabse, '', APattern, tables, [osMy, osOther], [tkTable], False);

Delphi FDQuery to Json

I'm trying to convert the result of my Sqlite query into a Json,
to use the same procedures I use with remote binding to Sql Server by php.
The code works, but do you think it's a better solution?
Anyone there do that?
function TLogin.RetornaRegistros(query:String): String;
var
FDQuery : TFDQuery;
field_name,nomeDaColuna,valorDaColuna : String;
I: Integer;
begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1;
FDQuery.SQL.Text := query;
FDQuery.Active := True;
FDQuery.First;
result := '[';
while (not FDQuery.EOF) do
begin
result := result+'{';
for I := 0 to FDQuery.FieldDefs.Count-1 do
begin
nomeDaColuna := FDQuery.FieldDefs[I].Name;
valorDaColuna := FDQuery.FieldByName(nomeDaColuna).AsString;
result := result+'"'+nomeDaColuna+'":"'+valorDaColuna+'",';
end;
Delete(result, Length(Result), 1);
result := result+'},';
FDQuery.Next;
end;
FDQuery.Refresh;
Delete(result, Length(Result), 1);
result := result+']';
finally
FDQuery.Free;
end;
end;
That is not a good approach. I really suggest consider at least three options:
Use the power of System.JSON unit.
Uses {...} System.JSON;
Var
FDQuery : TFDQuery;
field_name,Columnname,ColumnValue : String;
I: Integer;
LJSONObject:TJsonObject;
begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1;
FDQuery.SQL.Text := query;
FDQuery.Active := True;
FdQuery.BeginBatch;//Don't update external references until EndBatch;
FDQuery.First;
LJSONObject:= TJSONObject.Create;
while (not FDQuery.EOF) do
begin
for I := 0 to FDQuery.FieldDefs.Count-1 do
begin
ColumnName := FDQuery.FieldDefs[I].Name;
ColumnValue := FDQuery.FieldByName(ColumnName).AsString;
LJSONObject.AddPair(TJSONPair.Create(TJSONString.Create( ColumnName),TJSONString.Create(ColumnValue)));
FDQuery.Next;
end;
//FDQuery.Refresh; that's wrong
FdQuery.EndBatch;
finally
FDQuery.Free;
Showmessage(LJSonObject.ToString);
end;
end;
https://www.youtube.com/watch?v=MLoeLpII9IE&t=715s
Second approach, use FDMemTable.SaveToStream;
The same works for FDMemTable.SaveToFile;
Put a TFDMemTable on Datamodule (Or form, as well).
fMStream:TMemoryStream;
Begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1;
FDQuery.SQL.Text := query;
FDQuery.Active := True;
//fdMemTable1.Data:=fdQuery.Data; {note *2}
fdMemTable1.CloneCursor(FdQuery,true,true);{note *3}
fMStream:=TMemoryStream.Create;
FdMemTable1.SaveToStream(fMStream,sfJson);
finally
FDQuery.Free;
FdMemTable.Close;
end;
Now you can Read the JSON content
For example, following answer Converting TMemoryStream to 'String' in Delphi 2009
function MemoryStreamToString(M: TMemoryStream): string;
begin
SetString(Result, PChar(M.Memory), M.Size div SizeOf(Char));
end;
and you have the json as String
The BatchMove suggeted by #VictoriaMarotoSilva
You can use BatchMove components, which provides an interface to move data between datasets, but it works better for backup and importation when you want to save data in drive, XML or json format. I didn't find examples yet, using data moving in memory; if somebody else has an example, please comment.
Notes
Using FdMemTable, don't forget drag TFDStanStorageJSONLink component for datamodule
method .Data just works for FiredacDatasets (Datasets with prefix FD).
To assign data for memTable in old Datasets use method .Copydata instead.
Sorry guys, I change .Data to .CloneCursor to share the same Memory Space with both datasets.
I just modified my first answer below to comport different type of field to convert number, date and boolean in appropriate json format.
I comment the Types I didn't test.
Look
Uses {...} System.JSON;
Var
FDQuery : TFDQuery;
field_name, Columnname, ColumnValue : String;
I: Integer;
LJSONObject:TJsonObject;
begin
FDQuery := TFDQuery.Create(nil);
try
FDQuery.Connection := FDConnection1;
FDQuery.SQL.Text := query;
FDQuery.Active := True;
FdQuery.BeginBatch;//Don't update external references until EndBatch;
FDQuery.First;
LJSONObject:= TJSONObject.Create;
while (not FDQuery.EOF) do
begin
for I := 0 to FDQuery.FieldDefs.Count-1 do
begin
ColumnName := FDQuery.FieldDefs[I].Name;
Case FDQuery.FieldDefs[I].Datatype of
ftBoolean:
IF FDQuery.FieldDefs[I].Value=True then LJSONObject.AddPair(TJSONPair.Create(TJSONString.Create( ColumnName),TJSONTrue.Create)) else
LJSONObject.AddPair(TJSONPair.Create(TJSONString.Create( ColumnName),TJSONFalse.Create));
ftInteger,ftFloat{,ftSmallint,ftWord,ftCurrency} :
LJSONObject.AddPair(TJSONPair.Create(TJSONString.Create( ColumnName),TJSONNumber.Create(FDQuery.FieldDefs[I].value)));
ftDate,ftDatetime,ftTime:
LJSONObject.AddPair(TJSONPair.Create(TJSONString.Create( ColumnName),TJSONString.Create(FDQuery.FieldDefs[I].AsString)));
//or TJSONString.Create(formatDateTime('dd/mm/yyyy',FDQuery.FieldDefs[I].Value));
else LJSONObject.AddPair(TJSONPair.Create(TJSONString.Create( ColumnName),TJSONString.Create(FDQuery.FieldDefs[I].AsString)));
End;
FDQuery.Next;
end;
FdQuery.EndBatch;
finally
FDQuery.Free;
Showmessage(LJSonObject.ToString);
end;
end;
More about dataset.DataType http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/DB_TFieldType.html
More about JSONTypes https://community.embarcadero.com/blogs/entry/json-types-for-server-methods-in-datasnap-2010-4
Consider using TFDBatchMove component. It's for direct transferring of data between two databases with additional mappings support. As a source and target can be a text, dataset or an SQL query to any of the FireDAC's supported database engines.
The Delphi MVC Framework contains a powerful mapper to map json to objects and datasets to objects. The Mapper is a sub project. It's independent code that can also be used in other kind of projects. It is open-source!
Advantage is that boolean values for example, are converted to TJSONBool type and not a string. I suggest take a look at the samples.
https://github.com/danieleteti/delphimvcframework/tree/master/samples/objectsmapperssamples
Probably not the best solution, and you can modify this to how you would like the JSON to be formatted... here is a quick sample solution:
function GetDataSetAsJSON(DataSet: TDataSet): TJSONObject;
var
f: TField;
o: TJSOnObject;
a: TJSONArray;
begin
a := TJSONArray.Create;
DataSet.Active := True;
DataSet.First;
while not DataSet.EOF do begin
o := TJSOnObject.Create;
for f in DataSet.Fields do
o.AddPair(f.FieldName, VarToStr(f.Value));
a.AddElement(o);
DataSet.Next;
end;
DataSet.Active := False;
Result := TJSONObject.Create;
Result.AddPair(DataSet.Name, a);
end;

How to use nested transaction in Delphi dbExpress component with MySQL

I'm trying to do a nested transaction using dbExpress in Delphi XE3 that is connected to MySQL 5.6.13.
Here is my example code to do a Nested transaction:
...
dbxTransaction := _connection.BeginTransaction(TDBXIsolations.ReadCommitted);
// just to check if nested is suported
supportsNestedBol := dbxTransaction.Connection.DatabaseMetaData.SupportsNestedTransactions;
try
_sqlQuery.Close;
_sqlQuery.SQL.Clear;
_sqlQuery.SQL.Add('INSERT INTO test(cod, name) VALUES(:cod, :name)');
_sqlQuery.ParamByName('cod').AsInteger := Test.Cod;
_sqlQuery.ParamByName('name').AsAnsiString := Test.Name;
_sqlQuery.ExecSQL(False);
//calls a nested function that has another transaction
Employee.Save();
_connection.CommitFreeAndNil(dbxTransaction);
Result := True;
except
on Exc:Exception do
begin
_connection.RollBackFreeAndNil(dbxTransaction);
raise Exc;
Result := False;
end;
end;
...
function Employee.Save():Boolean;
begin
...
dbxTransaction02 := _connection.BeginTransaction(TDBXIsolations.ReadCommitted);
try
_sqlQuery.Close;
_sqlQuery.SQL.Clear;
_sqlQuery.SQL.Add('INSERT INTO employee(cod, name) VALUES(:cod, :name)');
_sqlQuery.ParamByName('cod').AsInteger := Employee.Cod;
_sqlQuery.ParamByName('name').AsAnsiString := Employee.Name;
_sqlQuery.ExecSQL(False);
_connection.CommitFreeAndNil(dbxTransaction02);
Result := True;
except
on Exc:Exception do
begin
_connection.RollBackFreeAndNil(dbxTransaction02);
raise Exc;
Result := False;
end;
end;
end;
...
If I put a break point and check the variable supportsNestedBol the value is False.
So, I'm not sure if is the connector "libmysql.dll" that I'm using that not support nested transaction or if is the way that I'm trying to do it.
Some help?

How to convert bitmap images stored in a MySQL table to JPEG format?

I have a MySQL table that stores bitmap images and I want to convert them to JPEG format to the same table. Can anyone help me to find a solution ?
I need this to reduce the size of the table...
When you'd use ADO to access your MySQL database, it might look like this (it's untested). This code assumes you have the table you want to work with named as YourTable and the BLOB field you want to convert the images from as ImageField. Note you have to specify the connection string to your DB in the ConnectionString property of the ADOConnection object:
uses
DB, ADODB, JPEG;
procedure ConvertImage(BlobField: TBlobField);
var
BMPImage: TBitmap;
JPEGImage: TJPEGImage;
MemoryStream: TMemoryStream;
begin
MemoryStream := TMemoryStream.Create;
try
BlobField.SaveToStream(MemoryStream);
BMPImage := TBitmap.Create;
try
MemoryStream.Position := 0;
BMPImage.LoadFromStream(MemoryStream);
JPEGImage := TJPEGImage.Create;
try
JPEGImage.Assign(BMPImage);
MemoryStream.Position := 0;
JPEGImage.SaveToStream(MemoryStream);
finally
JPEGImage.Free;
end;
finally
BMPImage.Free;
end;
MemoryStream.Position := 0;
BlobField.LoadFromStream(MemoryStream);
finally
MemoryStream.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ADOTable: TADOTable;
ADOConnection: TADOConnection;
begin
ADOConnection := TADOConnection.Create(nil);
try
ADOConnection.LoginPrompt := False;
// here you have to specify the connection string to your database
// according to your connection parameters
ADOConnection.ConnectionString := '<enter your connection string here>';
ADOConnection.Open;
if ADOConnection.Connected then
begin
ADOTable := TADOTable.Create(nil);
try
ADOTable.Connection := ADOConnection;
ADOTable.TableName := 'YourTable';
ADOTable.Filter := 'ImageField IS NOT NULL';
ADOTable.Filtered := True;
ADOTable.CursorType := ctOpenForwardOnly;
ADOTable.Open;
ADOTable.First;
while not ADOTable.Eof do
begin
ADOTable.Edit;
ConvertImage(TBlobField(ADOTable.FieldByName('ImageField')));
ADOTable.Post;
ADOTable.Next;
end;
finally
ADOTable.Free;
end;
end;
finally
ADOConnection.Free;
end;
end;

Using parameters with ADO Query (mysql/MyConnector)

Today I downloaded and installed MyConnector so I can use Mysql with ADO, everything installed, OK!, I can make connection with ODBC and do a connection from my delphi environment.
when I build my Query at runetime, I get an error saying :
Project Project1.exe raised exception class EOleException with message 'Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another'. Process stopped. Use Step or Run to continue.
function TForm1.CreateSQL : TADOQuery;
begin
result := TADOQuery.create(self);
with Result do
begin
Connection := MainConnection;
CursorLocation := clUseServer;
CursorType := ctStatic;
CacheSize := 50;
AutoCalcFields := true;
ParamCheck := true;
Prepared := true;
end;
end;
procedure TForm1.login();
begin
with CreateSQL do
try
with SQL do
begin
add('SELECT ');
add(' * ');
add('FROM ');
add(' LisenswebUsers ');
add('WHERE ');
add(' UserName = :MyUsername '); // debugger exception here
add('AND ');
add(' UserPassword = :MyPassword '); // debugger exception here
with Parameters do
begin
ParamByName('MyUsername').value := txtLogin.text;
ParamByName('MyPassword').value := strmd5(txtPassword.text);
end;
Open;
if Recordcount <> 1 then
begin
lblLoggedinAs.Text := format('Du er logget inn som: %s (%s)',[FieldByName('Username').AsString,FieldByName('UserEmailaddress').AsString]);
MainPageControl.ActivePageIndex := 1;
end else
begin
txtPassword.Text := '';
txtPassword.SetFocus;
end;
end;
finally
free;
end;
end;
The strangest thing is that this works if I turn off debugging in delphi.
I would try adding SQL.BeginUpdate/SQL.EndUpdate around the Adds, otherwise the SQL text will be parsed every time you call "Add".
This is generally a good idea, as ADOQuery.SQL is a TStringList that has an OnChange event that sets the CommandText. SetCommandText text then end up calling TADOCommand.AssignCommandText which does a fair amount of work parsing params, and setting CommandObject.CommandText. Sometimes drivers will fail with partial SQL statements, but this stuff looks OK.
I had a similar problem many years ago - that's why I learnt about this stuff!
procedure TForm1.login();
var
Qry : TADOQuery;
begin
Qry := CreateSQL;
try
Qry.SQL.BeginUpdate;
Qry.SQL.Add('SELECT');
Qry.SQL.Add(' *');
Qry.SQL.Add('FROM');
Qry.SQL.Add(' LisenswebUsers');
Qry.SQL.Add('WHERE UserName = :MyUsername '); // debugger exception here
Qry.SQL.Add(' AND UserPassword = :MyPassword '); // debugger exception here
Qry.SQL.EndUpdate;
Qry.Parameters.ParamByName('MyUsername').value := txtLogin.text;
Qry.Parameters.ParamByName('MyPassword').value := strmd5(txtPassword.text);
Qry.Open;
if Qry.Recordcount <> 1 then
begin
lblLoggedinAs.Text := format('Du er logget inn som: %s (%s)',[FieldByName('Username').AsString,FieldByName('UserEmailaddress').AsString]);
MainPageControl.ActivePageIndex := 1;
end
else
begin
txtPassword.Text := '';
txtPassword.SetFocus;
end;
finally
Qry.Free;
end;
end;
BTW, the nested withs are really ugly (let the holy war begin)
I will sometimes use with, but would never nest three levels! If you are, at least reduce the scope of with SQL so it ends before with Parameters.
Try setting an explicit datatype :
CreateSql.Parameters.ParamByName('MyUserName').DataType := ftString;
In my case, defining the parameters and assigning the query string before assigning the connection corrected the problem. The query executes successfully in both cases, but the TADOQuery component internally raises (and subsequently swallows) the EOleException noted in OP if the connection is assigned before the parameterized query.
//LADOQuery.Connection := LADOConnection; // Exception # LADOQuery.Text:=...
Param := LADOQuery.Parameters.AddParameter;
Param.Name := 'rid';
Param.DataType := ftFixedChar;
Param := LADOQuery.Parameters.AddParameter;
Param.Name := 'qd';
Param.DataType := ftLongWord;
LADOQuery.SQL.Clear;
LADOQuery.SQL.Text:='SELECT Val FROM table WHERE v1=:rid AND v2=:qd';
LADOQuery.Connection := LADOConnection; // OK!
I'm open to explanations as to why this is the case - nothing in the documentation seems to suggest a need for this order of operations.
The exception is raised in ADODB.pas in TADOCommand.AssignCommandText here
try
// Retrieve additional parameter info from the server if supported
Parameters.InternalRefresh;
where this branch is only followed if the TADOQuery is attached to a live connection. InternalRefresh performs :
if OLEDBParameters.GetParameterInfo(ParamCount,
PDBPARAMINFO(ParamInfo),
#NamesBuffer) = S_OK then
for I := 0 to ParamCount - 1 do
with ParamInfo[I] do
begin
// When no default name, fabricate one like ADO does
if pwszName = nil then
Name := 'Param' + IntToStr(I+1) else // Do not localize
Name := pwszName;
// ADO maps DBTYPE_BYTES to adVarBinary
if wType = DBTYPE_BYTES then wType := adVarBinary;
// ADO maps DBTYPE_STR to adVarChar
if wType = DBTYPE_STR then wType := adVarChar;
// ADO maps DBTYPE_WSTR to adVarWChar
if wType = DBTYPE_WSTR then wType := adVarWChar;
Direction := dwFlags and $F;
// Verify that the Direction is initialized
if Direction = adParamUnknown then Direction := adParamInput;
Parameter := Command.CommandObject.CreateParameter(Name, wType, Direction, ulParamSize, EmptyParam);
Parameter.Precision := bPrecision;
Parameter.NumericScale := ParamInfo[I].bScale;
// EOleException raised here vvvvvvvvv
Parameter.Attributes := dwFlags and $FFFFFFF0; //Mask out Input/Output flags
AddParameter.FParameter := Parameter;
end;
The problem definitely seems to be at the OLE level, probably because the MySQL ODBC driver does not support returning this information (or returns invalid information). The exception is raised behind the _Parameter interface when setting
Parameter.Attributes := dwFlags and $FFFFFFF0;
using what seem to be invalid values (dwFlags = 320 -> bits set above DBPARAMFLAGSENUM defined length) returned from GetParameterInfo. Exception handling for flow control seems the only option given that the interface does not provide any mechanism to check values before setting them (and triggering exceptions).
Update :
It turns out there is an open QC about this : http://qc.embarcadero.com/wc/qcmain.aspx?d=107267
BeginUpdate/EndUpdate pair is not adequate. Use AddParameter to add parameters explicity before assigning sql command. Like:
var
Qry : TADOQuery;
begin
Qry := CreateSQL;
try
with Qry.Parameters.AddParameter do
begin
Name := 'MyUsername';
DataType := ftString;
end;
with Qry.Parameters.AddParameter do
begin
Name := 'MyPassword';
DataType := ftString;
end;
Qry.SQL.BeginUpdate;
Qry.SQL.Add('SELECT');
Qry.SQL.Add(' *');
Qry.SQL.Add('FROM');
Qry.SQL.Add(' LisenswebUsers');
Qry.SQL.Add('WHERE UserName = :MyUsername '); // debugger exception here
Qry.SQL.Add(' AND UserPassword = :MyPassword '); // debugger exception here
Qry.SQL.EndUpdate;
...