ADO Query treats goto labels as parameters - delphi

I'm facing an issue executing SQL script in a TADOQuery component. The script that's being executed has goto statements along with their corresponding labels, for example:
goto MyLabel
MyLabel:
--do something else
However, the TADOQuery is seeing these : characters and treating them as parameters (which they are not parameters), and gives me an error:
Parameter object is improperly defined. Inconsistent or incomplete information was provided
How can I instruct the TADOQuery to not try to consider these as parameters?

Set AdoQuery.ParamCheck to false.
Update
The OP said in a follow-up comment that the above was sufficient for his immediate problem but wouldn't have worked if the query had contained actual :parameters. Initially, I couldn't get it to work with those either.
However, looking at the code of TParameters.ParseSQL in ADODB (D7), the author seems to have anticipated colons being embedded in the SQL (I mean, before any :paramname(s) one might enter to act as place-holders for TParameters), by treating a doubled-up colon (::) as a special case. So I think the intent was that one should double up any colon one doesn't want treated as a TParameter. To see what I mean, see Memo1's contents:
(PartialDFM)
object Memo1: TMemo
Left = 32
Top = 112
Width = 297
Height = 113
Lines.Strings = (
'declare'
' #number int'
'select'
' #number = ?'
'if #number > 0 goto positive'
'if #number < 0 goto negative'
''
'select ''zero'''
''
'positive::'
' select ''positive'''
' goto'
' exitpoint'
'negative::'
' select ''negative'''
'exitpoint::')
end
object ADOQuery1: TADOQuery
Connection = ADOConnection1
Left = 64
Top = 24
end
Then, the following works for me (displaying "positive", "negative" or "zero" in a DBGrid according to the value assigned to AdoQuery1.Parameters[0].Value)
procedure TForm1.DoQuery;
begin
if AdoQuery1.Active
then AdoQuery1.Close;
// AdoQuery1.Prepared := True;
AdoQuery1.SQL.Text := Memo1.Lines.Text;
AdoQuery1.ParamCheck := False;
ADOQuery1.Parameters.Clear;
ADOQuery1.Parameters.CreateParameter('Param1', ftInteger, pdInput, 1, Null);
AdoQuery1.Parameters[0].Value := 666;
AdoQuery1.Prepared := True;
AdoQuery1.Open;
end;

Related

Is there a limit for TClientDataSet filter, or is it a bug?

I'm using a TClientDataSet as an in-memory table and must apply a filter with a lot of conditions.
For example, with 400 OR conditions, I get an access violation when I try to enable the filter.
Access violation at address 4DAEDC76 in module 'midas.dll'. Read of address 00000034.
The exception occurs here :
procedure TCustomClientDataSet.AddExprFilter(const Expr: Widestring; Options: TFilterOptions);
begin
if FExprFilter <> nil then FDSCursor.DropFilter(FExprFilter);
if Expr <> '' then
with TExprParser.Create(Self, Expr, Options, [poExtSyntax], '', nil, FieldTypeMap, True) do
try
CheckProviderEOF;
Check(FDSCursor.AddFilter(FilterData, DataSize, FExprFilter)); // ** AV HERE
finally
Free;
end;
end;
Is it a bug on component or it's a limitation of the midas.dll ?
I tested this behavior on these midas's versions: >= 15 and <= 23
I'm using Delphi XE.
Example code:
procedure TForm41.Button1Click(Sender: TObject);
var
I: Integer;
FilterStr: string;
begin
FilterStr := '(vehicleId = -1)';
//It is just an example, the original code I can have any integer number.
for I := 0 to 400 do //If I change the limit value to 40 for example, it works.
FilterStr := FilterStr + ' or (vehicleId = ' + IntToStr(I) + ')';
ClientDataSet1.Filter := FilterStr;
ClientDataSet1.Filtered := True;
ClientDataSet1.CreateDataSet; //Error here
end;
I already tried to use IN statement, but I get the same error.
I didn't found references about this situation on internet.
I can reproduce this error, including the "Read of address 00000034" in Delphi Seattle.
It occurs when more than about 280 terms are in the Filter expression. It's
obviously a midas limitation. You need a better way of translating from your problem domain
to the desired effect of your filter.
If you can evaluate your condition in code, then what I would do is to add
a boolean fkInternalCalc field to the CDS and set it to True of False
depending on the result. Then, filtering the dataset is a simple matter
of applying a filter based on the value of the boolean.

Delphi TValueListEditor Strings prop-ed quirk?

Investigating odd behaviour of a TValueListEditor being used to generate a filter
expression for a ClientDataSet, I've traced it to a situation where if the first entry
in it apparently had nothing in the Value column, it returned #13#10 as the Value, rather than
''.
In the following, the TStringlist TL is initialized with the same contents as the ValueListEditor
Strings property has in my app. The Assert does not fail for the TStringlist, but it does for the
ValueListEditor. These results occurred with D7 and XE4.
procedure TDefaultForm.ApplyFilter;
var
i,
Max : Integer;
Key,
Value : String;
TL : TStringlist;
begin
TL := TStringlist.Create;
try
TL.Add('Country=');
TL.Add('Class=CON');
for i:= 0 to TL.Count - 1 do begin
Key := TL.Names[i];
Value := TL.Values[Key];
Assert(Value <> #13#10); // succeeds for all i
end;
Max := ValueListEditor1.RowCount;
for i:= 1 to Max do begin
Key := ValueListEditor1.Keys[i];
Value := ValueListEditor1.Values[Key];
// Value := ValueListEditor1.Strings.ValueFromIndex[i-1];
Assert(Value <> #13#10); //Fails for i = 1!
end;
finally
TL.Free;
end;
end;
Btw, the TVLE was set up entirely in the Object Inspector: I simply dragged a TVLE off the palette, clicked Strings in the OI, clicked in the LH cell and typed 'Country' (sans quotes), pressed the Down key and typed 'Class' then right-arrow and typed 'CON'.
Obviously, I could avoid this by Value := Trim(Value), but was curious where the #13#10 was coming from.
Update: Prompted by #Deltic's answer and helpful comments, I decided to re-trace my steps and added another TVLE to my form. The following extracts from the DFM are revealing:
object ValueListEditor1: TValueListEditor
Left = 16
Top = 224
Width = 306
Height = 135
KeyOptions = [keyEdit, keyAdd]
Strings.Strings = (
'Country='#13#10
'Class=CON')
TabOrder = 2
end
[...]
object ValueListEditor2: TValueListEditor
Left = 440
Top = 192
Width = 306
Height = 246
KeyOptions = [keyEdit, keyAdd]
Strings.Strings = (
'A='
'B=ValueOfB')
TabOrder = 5
end
So, with hindsight, my question really boils down to how did the #13#10 get into the DFM? And then it came back to me ...
With no previous experience of the TVLE, when I set up the form, I got stuck at the point where I needed to add a second row. I tried pressing [Enter], but that did nothing, so then I tried Ctrl-Enter and that did nothing either. But repeating the exercise now has confirmed that that's how the CR/LF got into the TVLE's Strings.
So, it seems that the answer to my q is "No, the TVLE isn't broken, but its Strings property editor
has a quirk regarding Ctrl-Enter". In other circs, I would consider deleting my q, seeing as it's at least partly caused by operator aberration, but perhaps it's better left to assist any others who trip over the same point.
Update #2 I see that my curiousity has earned me a -1. Fair enough, but I'm still inclined to leave this q & a in place, if only as an illustration of the fact that problems have deterministic causes, which can often be identified by simple things such as re-tracing one's steps, particularly with someone obviously knowledgeable looking over one's shoulder, as it were. Perhaps the down-voter would care to enlighten readers what help to future readers such a silent -1 is.
You have not shown how your value list editor is initialised, and I suspect that this is where your problem is. Behind a TValueListEditor is nothing more than a TStringList (strictly speaking a subclass of one, but the subclass doesn't change the fundamental behaviour w.r.t named values).
If your apparently empty value in the value list is yielding a value of #13#10 then it must be because that is the actual value that it has.
This simple test snippet verifies this:
var
i:Integer;
k, v:String;
begin
ed.InsertRow('Country', '', TRUE);
ed.InsertRow('Class', 'CON', TRUE);
for i:= 1 to ed.RowCount - 1 do
begin
k := ed.Keys[i];
v := ed.Values[k];
ASSERT(v <> #13#10); // Never fails
end;
end;
Where ed is a TValueListEditor on the form.
Replace the first line of code in the above snippet with this however:
ed.InsertRow('Country', #13#10, TRUE);
And the ASSERT() fails.
I suggest you investigate the initialisation of your value list editor. My guess is that it is being populated by reading from a file using a mechanism which is reading the entire line into a string, including the line end sequences, and the code that is adding the values for each read line is not stripping the #13#10 line terminators, resulting in the values being added as <name>=<value>#13#10 in each case.

How Can I Create Insert and Update SQL's by pure code

I'm currently using AdoQuery's and append post commands. But for data security I want to change my code with insert into and update table name...
But I have a lot of forms and tables...
Because of that I think maybe someone has already developed code for generating insert statements.
Actually I have found a way but I'm stuck.
I have query1. it contains the fieldlist.
I'm creating a parameter list in another query from this fieldlist.
I'm updating the parameters field by field.
This is not very convenient
Can someone give me a easy ways to do this.
Note: I prefer coding this job with only standard components. I don't want to install additional components.
Maybe not the reply you want. I think you need to raise the abstraction level. You need to skip SQL. An ORM framework can do this for you. It maybe feels like a big step for you but I promise it is also a relief to just use code like:
Person.name := 'Bob';
Invoice.customer.address.street := 'Abbey road';
Edit1.text := Invoice.customer.name;
To actually update database you need to call an update method that differ depending on framework. For a list of frameworks see here. I am also aware of TMS Aurelius. I use Bold on daily use. Bold also have features like OCL, derived attributes and links in the model, some boldaware components (it updates whenever db changes). But it has one big disadvantage. It is only available for D2006/D2007. I am working for a solution on this because I think it is the best and most mature ORM framework for Delphi. See also my blog on Bold for Delphi. Ask if you have questions!
You take the fieldlist from your query.
Create a new query with parameters.
And fill in the values.
Something like this:
const
TableNameEscapeStart = '['; //SQL server, use '`' for MySQL
TableNameEscapeEnd = ']'; //SQL server, use '`' for MySQL
FieldNameEscapeStart = '[';
FieldNameEscapeEnd = ']';
function CreateInsertStatementFromTable1ToTable2(Table1, Table2: TTable): String;
var
i: integer;
comma: string;
begin
i:= 0;
Result:= 'INSERT INTO '+TableNameEscapeStart + Table2.TableName + TableNameEscapeEnd + ' (';
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result + FieldNameEscapeStart + Table1.Fields.Field[i].Name + FieldNameEscapeEnd + comma;
end;
Result:= Result +' ) VALUES ( ';
i:= 0;
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result +':' + IntToStr(i+1) + comma;
end; {while}
Result:= Result + ' ); ';
end;
There are three avenues for SQL injection here.
1. The field values
2. The table name
3. The field names
The first is covered by the use of parameters.
The second and third are covered, because you're using the table and field names of the table directly.
If you don't have a trusted source of table and fields names, then you need to compare these against the table and fieldnames obtained directly from the table.
See: Delphi - prevent against SQL injection
You insert the data using ParamByName (slowly) or more efficiently using Param[i] where i starts at 0.
In MySQL it's even easier:
If table1 and table2 have the same fields, the following SQL will insert all data in table2 into table1:
INSERT INTO table1 SELECT * FROM table2;

Using LIKE and '%' in an ADO dataset filter

I have a problem when trying to apply a filter to a ADO dataset in Delphi XE2 ...
Filter := ' [Name] like ''%john'' ';
It raises an exception:
Project Test.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.
but when using:
Filter := ' [Name] like ''john%'' '
it works fine!
why?
The Operator can only be one of the following:
= < > <= >= <> LIKE
If you use the LIKE operator, you can also use the * or % wildcards as the last character in the string or as the first and last character in the string.
http://www.devguru.com/technologies/ado/quickref/recordset_filter.html
You can catch the filter on the event OnFilterRecord:
procedure TForm1.ADODataSet1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
var
iPos: Integer;
begin
iPos:= pos('john',ADODataSet1name.AsString);
if (iPos>0) and
(iPos = length(ADODataSet1name.AsString)-3) then
begin
Accept:= True;
end
else
begin
Accept:= False;
end;
end;
or
function TForm1.IsLastCriteria(AText: String): Boolean;
var
iPos: Integer;
begin
iPos:= pos(AText,ADODataSet1name.AsString);
Result:= (iPos>0) and
(iPos = length(ADODataSet1name.AsString)-length(AText)-1);
end;
procedure TForm1.ADODataSet1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
Accept:= IsLastCriteria('john');
end;
AS. I still ask you to read http://www.catb.org/esr/faqs/smart-questions.html#beprecise and describe your environment accordingly.
What is database server ?
What version are MDAC/ADO components?
What the query is ?
What is Name column type in SQL ?
You are "upping" the comments, that si nice. But you don't answer the questions. And that is not nice. We are not ESPers, we canot read you mind.
I put you few suggestions in comments above. Did you tried them ? Did they worked ? To quote them:
Maybe you can update ADO/MDAC to 2.8sp1 version. Referenced MSDN KB articles are told to apply to MDAC up to 2.7 version. Maybe 2.8sp1 no more has that limitation.
Maybe you can use some data-server specific tricks like copying last 4 letters of Name into a separate column.
Maybe you can move that condition into SQL SELECT WHERE clause and re-open the query.
Maybe there is event-handler like TBDEDataSet.OnFilterRecord
There are 4 workarounds and you either did not tried them or did not reported the results. Not nice.
Idea #1 is self-explanatory
Idea #3 was detailed by #SertacAkyuz as far as he could do it, giving lack of information about your program.
Idea #4 was detailed by #Ravaut123
Idea #2 is outlined below
Assuming your "ADO database" is backed by Microsoft Access, and according to http://www.databasedev.co.uk/access-sql-string-functions.html ...
Or using MS SQL and according to http://msdn.microsoft.com/en-us/library/ms177532.aspx ...
Or...
Query.SQL.Text := 'select Right(Name, 4) as name_tail, * from table where ...'
See that extra column added to query! 4 is length for "john" there.
With query like that the following filtering conditions are to be equivalent:
Filter := ' [Name] like ''%john'' ';
Filter := ' [name_tail] = ''john'' ';
However, if on some rows Name column is shorter than 4 letters, i don't know what RIGHT function would do. Maybe it will truncate the result, or maybe throw an error and abort the query. The latter maybe - depending on the real data - maybe can be alleviated by padding with spaces to the 4 length like
LTrim(Right(' ' || Name, 4)) as name_tail
That is for you to test, since only you know the details of your environment.
The simplest answer to this question was mentioned in comments.
If you used asterisk instead of percent symbol, everything would work great
Filter := ' [Name] like ''*john'' ';

TStringList problem with values at index

So I have several summary files that I want to read and get the values from.
I am doing the following:
OutputSummary := TStringList.Create;
for idx := 0 to 82 do
OutputSummary.Insert(idx, '');
to initialize the values I'm using
then, I have a loop:
for idx := 0 to SummaryFiles.Count - 1 do
begin
AssignFile(finp, SummaryFiles[idx]);
ReSet(finp);
for ndx := 0 to 5 do
ReadLn(finp, buff);
for ndx := 0 to 82 do
begin
ReadLn(finp, buff);
temp := GetToken(buff, ' ');
buff := GetRemains(buff, '|');
temp := GetToken(buff, '|');
valuestring := OutputSummary[ndx] + delimiter + temp;
OutputSummary.Insert(ndx, valuestring);
end;
CloseFile(finp);
end;
The first 0 to 5 loop skips the lines I don't want to read, and the 0 to 82 reads lines that look like
1. Initial Wait List|1770
So I was debugging the program to see how it works with just 2 SummaryFiles.
The first time through, it works perfectly. The line is read correctly, I get the value and when I insert valuestring, it looks like ",1770" (for example), and I can also highlight OutputSummary[ndx] after the insert command and see that the value was inserted correctly.
Then I open the second file, which also works fine until the line
valuestring := OutputSummary[ndx] + delimiter + temp;
the first time, OutputSummary[0] is correct and the correct line is added.
However, OutputSummary[1] through OutputSummary[82] is the same as OutputSummary[0]! This makes no sense since when I was first adding those values, I could see that OutputSummary[1] through 82 were unique and correct.
Can anyone see a problem? Is it a debugger error? Am I just missing something obvious that I don't see?
thanks
It looks to me like you're trying to create a table of some sort, with one column per input file and one row per line in the file, with the columns separated by the delimiter. If so, calling .Insert on the string list isn't going to quite work right, since you'll end up inserting 83 * SummaryFiles.Count rows.
Instead of the Insert call, you need something like this:
if OutputSummary.count > ndx then
OutputSummary[ndx] := valuestring
else OutputSummary.Add(valuestring);
See if that helps.
Also, you might want to consider replacing the "magic number" 82 with a meaningful constant, like const LINES_TO_READ = 82. That makes it easier to read the code and understand what it's supposed to be doing.

Resources