There is no easy solution for this
All tests performed with Delphi XE3 and MySQL
Behavior Explanation
At designtime you will get the correct parameter data types as long as the parameters depend on a table field from the FROM table without aliases
SELECT id FROM items WHERE id = :id
but it will also fail if not
SELECT id FROM items WHERE id/2 = :id
and this will also fail
SELECT i.* FROM items i WHERE i.id = :id
At runtime both will result in parameters with datatype ftUnknown
.
Parameters are setup in private method
// Delphi XE3
Data.SqlExpr.TCustomSQLDataSet.SetParameterFromSQL
Inside this method the tablename is extracted and only
if csDesigning in ComponentState then
a temporary DataSet is created with
SELECT * FROM <tablename> WHERE 0 = 1
Every parameter is checked against the fieldnames from this dataset and if names match the parameter datatype is set.
Thats the reason why you get ftUnknown
for your parameters at runtime.
To solve that Delphi does the same you tried with your solution, but dbexpress fails sometime. The Setter for parameters is located at
Data.DB.TParam.SetAsVariant
and value 1234567
has the variant type varLongword
and parameter datatype will be set to ftLongword
and causes this error.
Workaround
As a workaround you can set parameter datatype to ftString/ftWideString, as this will work in most cases.
procedure TTestClass.ExecuteSql(const ASql : String; const ParamVals : Array Of Variant);
var
i : integer;
Qry : TSqlQuery;
begin
Qry := NewQuery(ASql);
with Qry do
try
for i := Low(ParamVals) to High(ParamVals) do
begin
Qry.Params[i].DataType := ftWideString;
Qry.Params[i].Value := ParamVals[i];
end;
ExecSql();
finally
Free;
end;
end;
To get a better solution you get a procedure to set the parameter datatype to ftString
/ftWidestring
only for critical variant types (like your method SetParamValues but more general)
procedure SetParamValues( const AParams : TParams; const AValues : array of Variant );
var
LIdx : Integer;
LParam : TParam;
LValue : Variant;
begin
for LIdx := 0 to Pred( AParams.Count ) do
begin
LParam := AParams[LIdx];
LValue := AValues[LIdx];
// only handle the critical parts
case VarType( LValue ) of
varByte, varLongword : LParam.DataType := ftWideString;
end;
// all other will be set here
LParam.Value := LValue;
end;
end;
Solution: the hard way
As i first stated there is no easy solution for this. For a full functional solution you have to parse the whole WHERE statement
SELECT a.*, b*
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE a.id = :id AND b.count / 2 = :halfcount
and build a query from this
SELECT a.id as Param1, b.count / 2 as Param2
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE 0 = 1
to get the expected datatype.
Solution: the long way
IMHO this is a bug and should be reported to EMBA ...
Solution: the expensive way
I made a test with UniDAC and everything is the same as dbExpress. Parameter datatype is ftUnknown
and setting parameter value will set the datatype to ftLongword
.
But there is one special case: You will not get an error and your query is processed as you expect.