1

I use the TField.origin property to dynamically build a where clause for SQL queries.

So if I have a query such as;

select
  p.firstname,
  p.lastname,
  g.description
from
  people p
  inner join groups g

I can set the origin of the firstname field to;

FirstNameField.origin = 'p.firstname';

Then use this in the where clause of dynamic queries such as;

SQLWhere = 'where ' + FirstNameField.origin + ' = ''' + MyValue + ''' ';

(obviously i have additional code to prevent against SQL injection).

I do this all the time and it works great. However when trying to track down a bug, I noticed I have one dataset that keeps reseting the value of the origin for example back to;

people.firstname

instead of;

p.firstname

I tracked it down to when the dataset is closed and then reopened. However I do this all the time, so I can't understand why one dataset has different behaviour.

My question is how can I prevent the origin value from getting reset?

srayner
  • 1,799
  • 4
  • 23
  • 39
  • 1
    Why don't you just use parameters insted of additional code to prevent injection? – GabrielF Aug 27 '15 at 12:41
  • What database components do you use? – GabrielF Aug 27 '15 at 12:46
  • I'm using the TIBDataset component. – srayner Aug 27 '15 at 13:17
  • 2
    I know this is not what you're asking, and I really don't know why the property is being reset, since [documentation](http://docwiki.embarcadero.com/Libraries/en/Data.DB.TField.Origin) states it's only assigned at design time (maybe TIBDataSet has different behaviour); but `TField.Origin` is supposed to be in the format `TableName.FIeldName`. Maybe you should just not use aliases in your dynamic queries. – GabrielF Aug 27 '15 at 13:33
  • That's probably how i will solve it. Annoying though because the alias keep the SQL tidy. I only used a simple example but in the real application the can sometimes be over 60 fields and table names are long because this is large database with 100s of tables. – srayner Aug 27 '15 at 13:51
  • Which Delphi version? – MartynA Aug 27 '15 at 17:03

1 Answers1

3

The code below is from D7's IBCustomDataSet unit (the code is more complex in later XEx versions).

It's this code which sets the Origin property of an IBX TField.

TIBCustomDataSet.CreateFields will get called when the dataset calls its InternalOpen method if its FDefaultFields field is True. It will be True if on entry to InternalOpen the FieldCount of the dataset is zero. FieldCount will be zero if no fields have been created in advance, either in user-code or in the IDE using the Fields editor on the dataset. InternalOpen is called by TDataSet.Open via its OpenCursor method.

So the way to avoid 'CreateFields' being executed and the dataset fields' Origin properties being reset as a consequence is to use either of these methods (IDE or user code) to create the fields prior to opening the dataset. In other words, if you set the Origin property by either of these methods, that should avoid it getting reset.

procedure TIBCustomDataSet.CreateFields;
var
  FieldAliasName, RelationName : String;
  i : Integer;
  f : TField;
begin
  inherited;
  for i := 0 to FQSelect.Current.Count - 1 do
    with FQSelect.Current[i].Data^ do
    begin
      { Get the field name }
      SetString(FieldAliasName, aliasname, aliasname_length);
      SetString(RelationName, relname, relname_length);
      f := FindField(FieldAliasname);
      if Assigned(f) then
      begin
        if (RelationName <> '') and (FieldAliasName <> '') then
          f.Origin := RelationName + '.' + FieldAliasName;
      end;
    end;
end;

Update The implementation of TIBCustomDataSet.InternalOpen was evidently changed between XE4 and XE6 so that CreateFields is now called unconditionally (i.e. regardless of whether DefaultFields is True). Therefore, having already-existing TFields will not avoid CreateFields being called and therefore the Origin properties being reset.

MartynA
  • 30,454
  • 4
  • 32
  • 73
  • You mean you're working with XE8? I don't think it makes any difference - the code in CreateFields is more complex, but the principle is the same. – MartynA Sep 08 '15 at 09:13
  • The same code from delphi XE8 checks the FieldName not the FieldAliasName, before setting the origin. Could this be a bug? I dare not change it back as there are other changes and I don't want to break it. – srayner Sep 08 '15 at 09:14
  • ok, i've read your answer again. I am creating the fields in the IDE and setting the origin there. So I would expect that the fields exist before TDataset.Open is called. But maybe for some reason it's not. – srayner Sep 08 '15 at 09:24
  • Well, you can easily enough put a breakpoint in TIBCustomDataSet.CreateFields and see whether (and why) it's getting called at run-time. – MartynA Sep 08 '15 at 10:19
  • ok, the breakpoint verifies that CreateFields is getting executed. I can see that my origin is ok at that point, but CreateFields overwrites it. But how do I tell why CreateFields is being called? thanks for your help btw, much appreciated. – srayner Sep 08 '15 at 11:30
  • found the stack trace. InternalOpen always calls CreateFields. InternalOpen is called because TDataset.OpenCursor finds it's InfoQuery parameter = false. The reason that is false is because TDataset.SetActive calls OpenCursor without passing a parameter.So now i'm wondering if this is an embarcadero bug? – srayner Sep 08 '15 at 11:36
  • for info delphi 2006 works fine. however, I can't debug delphi 2006. the breakpoints go green with a cross through them. – srayner Sep 08 '15 at 11:50
  • It looks deliberate, because TIBCustomDataSet.InternalOpen unconditionally calls CreateFields in XE8. So I guess you need to save your Origin settings before the dataset and restore them after the dataset is opened. – MartynA Sep 08 '15 at 11:52
  • Well at least we've tracked down the cause. My problem is a have a large application with around 300 forms / queries. bummer. Embarcadero strikes again. – srayner Sep 08 '15 at 12:05
  • 1
    I checked and the change to InternalOpen is in XE6 but not XE4 (I don't have XE5 installed). However, note that InternalOpen is virual, so you could derive a TIBCustomDataSet descendant and re-instate the previous way it worked or, as a last resort, copy IBX.IBCustomDataSet to your project directory and modify its InternalOpen in that. Personally, I would ask in the Emb newsgroups whether the change was intentional or not - the author of the IBX components is a regular contributor there. – MartynA Sep 08 '15 at 12:17