In a nutshell, how do I know which (primary & unique) keys uniquely identify a FireDAC query record ?
I am using Delphi 10.1 & FireDAC & Firebird.
I am using code generation to generate a file containing all the queries of my application. It is a legacy application, so the current DB structure is all over the place. The person who designed it didn't have the knowledge (was still learning) when he designed it. So we don't have a coherent system for uniquely identifying records. Some of them are on a autoinc int generator. Some have another field for unique identification. Some have combinations. And the foreign keys that link to these fields are not coherent neither.
So. Since the total refactoring of the DB is not an immediate option, we have to deal with it. The best way I could figure was to be able to retreive every possible key for a given query. This would allow us to retreive systematically all the possible ways we can uniquely identify a record in a query & table. This would give us an abstraction of a record identifier (basically a set of keys) that we could pass along in our application, while we refactor.
The first step was to generate a virtual representation of the DB (table, fields, field types, lengths, keys, foreign keys) with this structure (simplified for readability) :
TTable = class(TCollectionItem)
property Name : string;
property Fields : TFieldCollection;
property Keys : TKeyCollection;
end;
TField = class(TCollectionItem)
property Name : string;
property Table : TTable;
property Type : TFieldType;
property Size : integer;
property Required : Boolean;
end;
TKey = class(TCollectionItem)
property KeyType : TKeyType;
property Name : string;
property Table : TTable;
end;
TKeyType = (ktPrimary, ktUnique, ktForeign);
TKeyCollection = class(TCollection)
end;
TTableCollection = class(TCollection)
end;
TSchema = class(TObject)
property Tables : TTableCollection;
end;
Secondly, I acheived to generate code from this schema a class for each of the table of the application, along with all their possible keys. Looks like this :
CUSTOMERS table
---------------
ID int (autoinc, pk PK_CUSTOMERS)
CODE int (uk UK_CODE)
NAME string
GROUP_ID int (fk on GROUPS.ID)
GROUPS table
------------
ID int (autoinc, pk PK_GROUPS)
NAME string
// Generated code (simplified for readability)
TCustomerPk = class(TKey)
property ID : Integer;
end;
TCustomerCode = class(TKey)
property Code : Integer;
end;
TCustomerKeys = class(TKeys)
property CustomerPk : TCustomerPk ...
property CustomerCode : TCustomerCode ...
end;
TCustomers = class(TTableBase)
property ID : TIntegerField;
property Code : TIntegerField;
property Name : TWideStringField;
property Keys : TCustomerKeys;
end;
TGroupsPK = class(TKey)
property ID : Integer;
end;
TGroupsKeys = class(TKeys)
property GroupsPk : TGroupsPk;
end;
TGroups = class(TTableBase)
property ID : TIntegerField;
property NAME : TStringField;
property Keys : TGroupKeys;
end;
Now that tables are done, I attack Queries. My aim is to know which keys are present in a query.
In order to know which keys this query has :
- Get the list of tables that this query takes data from
- Iterate through the keys of these tables
- Check if all fields of the key is present in the query
Sample (skipping case of foreign keys for simplicity) :
for lQueryField in lQuery.Fields do
begin
if not lTableList.Contains(lQueryField.Table)
lTableList.Add(lQueryField.Table)
end;
for lQueryTable in lTableList do
begin
for lKey in lQueryTable.Keys do
begin
lFound := true;
for lKeyField in lKey.Fields do
begin
if not lQuery.Fields.Contains(lKeyField) then lFound := false;
end;
if lFound then
// Query has key lKey
end;
end;
Using this method, and given a TFDQuery
SELECT CUSTOMERS.ID CUSTOMERID, CUSTOMERS.CODE CUSTOMERCODE, GROUPS.ID GROUPID FROM CUSTOMERS INNER JOIN GROUPS ON GROUPS.ID = CUSTOMERS.GROUP_ID
We can easily generate this code because we know that this query has both the PK_CUSTOMERS
key and PK_GROUPS
key using our test above :
TCustomersQuery = class(TQueryBase)
property CUSTOMERID : TIntegerField;
property GROUPID : TIntegerField;
property CUSTOMERCODE : TIntegerField;
property CustomerPk : TCustomerPk;
property CustomerCodeUk : TCustomerCode;
property GroupPk : TGroupPk;
end;
Now, the challenge I face is the following. We know that each record in the TCustomersQuery
uniquely identify a record in the CUSTOMERS
table. In other words, Each record in the TCustomersQuery
corresponds to a distinct, non-reoccurring record in the CUSTOMERS
table.
But each record in the TCustomersQuery
does not uniquely identify a non-reoccurring record in the GROUPS
table.
In other words, the PK_CUSTOMERS
and UK_CODE
keys are still valid in the context of this query, but the PK_GROUPS
is not.
In other words, again, the uniqueness of the PK_CUSTOMERS
and UK_CODE
keys are conveyed in this query, but not the uniqueness of the PK_GROUPS
key.
I want to find which keys are still valid in the context of an arbitrary query.
FireDAC
automatically fetches the query fields ProviderFlags
when first creating a query in the IDE. It somehow know which fields are part of the primary key. I would like to extend this to know all Primary and Unique keys that apply to this query. How can I know all of the fields that could have a ProviderFlags = pfInKey
value ?
In a nutshell, how do I know which (primary & unique) keys uniquely identify a FireDAC
query record ?