I am trying to make a function to add / delete / modify fields of Paradox Tables using BDE.dbiDoRestructure (see my other question BDE dbidorestructure returns empty table), but while I get the Table restructured properly and the grid shows the correct number of data-rows, all its data cells are empty.
How to use BDE dbiDoRestructure to add fields in Delphi 10.4?
667 views Asked by jim AtThere are 2 answers
On
This is some code which adds one or more fields to a TTable. On completion, the values in the original fields of the table are correctly displayed in a DBGrid. Other necessary routines are set out below.
To use the code, please create a new project and add a TTable, TDataSource and a TDBGrid connected up in the usual way and also a TButton to its main form.
procedure AddFields(Table : TTable; FieldsToAdd : TChangeRecs);
{ this code is based on the Delphi example code in the BDE32 help file,
extensively revised
}
var
Props: CURProps;
hDb: hDBIDb;
TableDesc: CRTblDesc;,
pOldFields,
pNewFields,
pCurField: pFLDDesc;
pOp, pCurOp: pCROpType;
ItrFld: Word;
i,
j : Integer;
POldFieldDescArray,
PNewFieldDescArray : PFieldDescArray;
OldFieldDescArraySize,
NewFieldDescArraySize : Integer;
FieldsToAddCount : Integer;
NewFieldsCount : Integer;
begin
// Initialize the pointers...
pOldFields := nil;
pNewFields := Nil;
pOp := nil;
CheckTableType(Table, Props);
try
FieldsToAddCount := Length(FieldsToAdd);
OldFieldDescArraySize := Props.iFields * sizeof(FLDDesc);
NewFieldDescArraySize := OldFieldDescArraySize + (FieldsToAddCount * sizeof(FLDDesc));
pOldFields := AllocMem(OldFieldDescArraySize);
pNewFields := AllocMem(NewFieldDescArraySize);
// Allocate memory for the operation descriptor...
NewFieldsCount := Props.iFields + FieldsToAddCount;
pOp := AllocMem((NewFieldsCount) * sizeof(CROpType));
// Null out the operations (= crNOOP)...
FillChar(pOp^, NewFieldsCount * sizeof(CROpType), #0);
for i := Props.iFields to Props.iFields + FieldsToAddCount do begin
pCurOp := pOp;
Inc(pCurOp, i);
pCurOp^ := crAdd;
end;
// Fill field descriptor with the existing field information...
Check(DbiGetFieldDescs(Table.Handle, pOldFields));
POldFieldDescArray := PFieldDescArray(pointer(pOldFields));
PNewFieldDescArray := PFieldDescArray(pointer(pNewFields));
// copy existing fields into pNewFields
for i := 0 to Table.FieldCount - 1 do begin
pNewFieldDescArray^[i] := pOldFieldDescArray^[i];
end;
// and add the new fields
for i := 0 to FieldsToAddCount - 1 do begin
pCurField := pNewFields;
Inc(pCurField, Table.FieldCount + i); // +1 to account for old fields
pCurField^.iFldNum := Table.FieldCount + i;
pCurField^.szName := FieldsToAdd[i].szName;
pCurField^.iFldType := FieldsToAdd[i].iType; //FieldTypeToBDEFieldInt(TFieldType(FieldsToAdd[i].iType));
pCurField^.iUnits1 := FieldsToAdd[i].iLength;
// Note: Other fields' ChangeRec properties not set
end;
FillChar(TableDesc, sizeof(TableDesc), #0);
hDb := Table.DBHandle;
StrPCopy(TableDesc.szTblName, Table.TableName);
StrCopy(TableDesc.szTblType, Props.szTableType);
// Set the new field count for the table
TableDesc.iFldCount := Props.iFields + FieldsToAddCount;
TableDesc.pecrFldOp := pOp;
TableDesc.pFldDesc := pNewFields;
Table.Close;
Check(DbiDoRestructure(hDb, 1, @TableDesc, nil, nil, nil, False));
// Clear the table's previous FieldDefs and Fields
Table.FieldDefs.Clear;
Table.Fields.Clear;
finally
if (pOldFields <> nil) then
FreeMem(pOldFields);
if (pNewFields <> nil) then
FreeMem(pNewFields);
if (pOp <> nil) then
FreeMem(pOp);
end;
end;
Note that I've written this code without relying on the PointerMath directive
available in modern versions of Delphi and I've tested it on Delphi 10.4.2 and
Delphi 7. The code is deliberately more long-winded than it strictly needs to be - it avoids Move operations for example - as my main concern was to ensure that it was as easy to trace in the debugger as possible. It is for that reason also that I've used two separate sets of field descriptors, pOldFields^ and pNewFields rather than one, as the BDE32 Help examples and various code examples derived from it do.
The problem reported by the OP in the q and an earlier one of his, namely that the field values of the original fields display blank in the DBGrid is because the field values are actually Null, so there is nothing to display. A necessary requirement to avoid this is that a) the field operation array (pointed to by pOp^) is large enough to have one row for each field in the table, including the one(s) being added and that the field operation code is set to crNoOp for the existing fields and crAdd for the new ones. Another requirement is that the pointers to the field descriptors are correctly set, which is why my pointer code is so long-winded.
The reason for the declarations of POldFieldDescArray and PNewFieldDescArray, and the fact that they are
declared as pointers to an Array[0..1000] of FLDDesc is purely to assist observation of the individual field descriptors (pFldDesc^) in the debugger.
My answer https://stackoverflow.com/a/66762667/2663863 to the OP's previous q dealt specifically with dropping a single field from the table. However, it is readily adaptable to deleting several fields at once using techniques similar to those in the above AddField.
function FieldTypeToBDEFieldInt(FieldType: TFieldType): Word;
{ This code may have originated with a Nathaniel Woolls, author of TFieldUpdate v1.1 }
begin
Result := fldUNKNOWN;
case FieldType of
ftUnknown : result := fldUNKNOWN;
ftString : result := fldZSTRING;
ftSmallint : result := fldPDXSHORT;
ftInteger : result := fldInt32;
ftWord : result := fldUINT16;
ftBoolean : result := fldBOOL;
ftFloat : result := fldFLOAT;
ftCurrency : result := fldPDXMONEY;
ftBCD : result := fldBCD;
ftDate : result := fldDATE;
ftTime : result := fldTIME;
ftDateTime : result := fldPDXDATETIME;
ftBytes : result := fldBYTES;
ftVarBytes : result := fldVARBYTES;
ftAutoInc : result := fldPDXAUTOINC;
ftBlob : result := fldPDXBINARYBLOB; //fldBLOB;
ftMemo : result := fldPDXMEMO;
ftGraphic : result := fldPDXGRAPHIC;
ftFmtMemo : result := fldPDXFMTMEMO;
ftParadoxOle : result := fldPDXOLEBLOB;
ftTypedBinary : result := fldPDXBINARYBLOB;
ftCursor : result := fldCURSOR;
ftFixedChar : result := fldPDXCHAR;
ftWideString : result := fldZSTRING;
ftLargeInt : result := fldINT32;
ftADT : result := fldADT;
ftArray : result := fldARRAY;
ftReference : result := fldREF;
ftVariant : result := fldUNKNOWN;
end;
end;
type
TFieldArray = Array of TField;
TFieldDescArray = Array[0..1000] of FLDDesc;
PFieldDescArray = ^TFieldDescArray;
TChangeRec = packed record
szName: DBINAME;
iType: Word;
iSubType: Word;
iLength: Word;
iPrecision: Byte;
end;
PChangeRec = ^TChangeRec;
TChangeRecs = Array of TChangeRec;
procedure TForm1.CreateTable(T : TTable);
var
AField : TField;
begin
AField := TIntegerField.Create(T);
AField.FieldName := 'Field1';
AField.DataSet := T;
AField := TStringField.Create(T);
AField.FieldName := 'Field2';
AField.DataSet := T;
AField.Size := 20;
T.Exclusive := True;
T.CreateTable;
T.Open;
T.InsertRecord([1, 'r1f2']);
T.InsertRecord([2, 'r2f2']);
T.InsertRecord([3, 'r3f2']);
end;
procedure TForm1.TestAddFields;
var
FieldsToAdd : TChangeRecs;
begin
CreateTable(Table1);
if not Table1.Active then
Table1.Open;
try
// Define fields to be added
SetLength(FieldsToAdd,2);
FieldsToAdd[0].szName := 'Added1';
FieldsToAdd[0].iType := FieldTypeToBDEFieldInt(ftString);
FieldsToAdd[0].iLength := 8;
FieldsToAdd[1].szName := 'Added2';
FieldsToAdd[1].iType := FieldTypeToBDEFieldInt(ftInteger);
AddFields(Table1, FieldsToAdd);
finally
FieldsToAdd := Nil;
end;
if not Table1.Active then
Table1.Open;
end;
procedure TForm1.btnRestructureClick(Sender: TObject);
begin
TestAddFields;
end;
Here is the unit I built (Delphi 10.4, Win 10/64) to test and rebuild a BDE TTable (Paradox, DB, FOXpro). It has the ability to open / create, check and reconstruct the table (fields and indexes) and visualize the progress. You can use / improve it freely.