Following up the DBFINFO program the next logical step is to write a program to create a dBase file. I was able to use the skeleton of DBFINFO so some of it will look familiar. The program I wrote (DBFMAKE) will create a dBase III DBF file and optionally a DBT file if there is a memo field defined.
One of the things you will notice is the file contains a lot of $00 bytes whereas the same structure created on a PC will have pseudo-random bytes in it. The PC programs generate garbage into the files so the difference is expected. When you disect a file created on the PC this garbage appears to be parts of memory dumped into the file. Maybe it’s a quick way to write large numbers of bytes at once, I don’t know. At any rate the difference doesn’t matter. The files I have created on the Atari 8 bit and transferred to the PC were usable and caused no issues. One thing I noticed is that after adding records the DBT file no longer had $00 bytes, but instead had the pseudo-random garbage again. PC by-product. At any rate the garbage appears in non-positional (non-data) bytes.
Additionally I don’t foresee the Atari ever utilizing the multi-user bytes, code page language bytes, or a handful of others that make no sense for the A8 platform – so those are all defined as $00 as well.
This program, DBFMAKE, will ask for the current date. The date is needed because three of the first four bytes in a DBF file represent the date of last modification. For now it’s manually input. In the future I may try to have it seek out a Z: handle and read the date from it since it should be an RTC (real time clock), then resort to input if the date can’t be determined automatically.
It will then ask for a filename. This is input WITHOUT an extension. The program will append DBF and DBT as needed.
After that it will loop asking for fields, up to the maximum of 128. It requires a name (up to 10 characters) and a field type (Character, Date, Logical, Memo, Number). If the field type is Character it will ask for the length (up to 254 characters max). If the field type is Number it will ask for the size in characters (up to 19), and the decimal precision (up to 15). Pressing RETURN on a field name will terminate input at which point the file(s) are created and the structure is displayed back as confirmation.
The constraints on the database structure follow those from the dBase III specification, and are as follows:
- The signature byte will be either $03 or $83. These are dBase III identifiers for database without and with memo fields respectively. I could have used a new identifier to signify dBase III on Atari 8 bit as the dBase flavor (like FoxPro, etc), but a) it’s premature for that; b) programs on the PC wouldn’t recognize it. So, I imitate.
- Max # Fields: 128
- Max Character Field Size: 254
- Max Number Field Size: 19
- Max Number Field Decimal Size: 15
- Max # Records: 1,000,000,000
- Max # Records Per File: 65,535
- Max Record Size: 4,000
- Max Memo Size: 5,000
DBF Structure
This is a brief explanation of the the dBase III DBF file structure.
- 1 b : DBT (x03=NO, x83=YES)
- 1 b : Year (less than 80 is 20xx; greater than 80 and less than 100 is 19xx; greater than 100 is 20xx)
- 1 b : Month
- 1 b : Day
- 4 b : NDR (number data records LSB first; i.e.: 03 00 00 00)
- 2 b : HSZ (header size LSB first; i.e.: A2 00)
- 2 b : DSZ (data record size LSB first; i.e.: 34 00) (includes deletion flag)
- 20 b : filler
- 32 b : field definition (repeat up to max as needed)
- 11 b : field name
- 1 b : field type
- 4 b : field memory address
- 1 b : field length
- 1 b : number of decimals
- 2 b : muti user
- 1 b : work area ID
- 11 b : reserved
DBT Structure
This is a brief explanation of the dBase III DBT file structure.
- Bytes 1 through 4 : The index of the next available memo location within the DBT file. Encoded in LSB/MSB form.
- Remaining bytes (through 512): Memo field data.
The last memo field in the DBT will not be the full 512 bytes, it will terminate at the conclusion of the memo field data.
Complete Source
; Program: DBFMAKE.ACT
; Author.: Wade Ripkowski
; Date...: 2015.08
; Desc...: Create dBase III DBF file.
; Notes..: Requires 9 pages of symbol
; table space:
; Run BIGST.ACT, then
; SET $495=9, then
; compile.
; License: Creative Commons
; Attribution-NonCommercial-
; NoDerivatives
; 4.0 International
; Inc Action Runtime
INCLUDE "D2:SYS.ACT"
; Inc Action Toolkit parts
INCLUDE "D5:PRINTF.ACT"
INCLUDE "D5:CHARTEST.ACT"
; Include library
INCLUDE "D3:DEFINES.ACT"
INCLUDE "D3:LIBIO.ACT"
INCLUDE "D3:LIBMISC.ACT"
INCLUDE "D3:LIBDOS.ACT"
; Start DBF Make
MODULE
; Size of a field record
DEFINE SZFLD = "13"
DEFINE OFFNM = "4"
DEFINE MAXREC = "128"
DEFINE SZMEM = "1664"
; Header record type
TYPE DBHead=[BYTE bMe,
bYr,
bMo,
bDy
CARD cNDR,
cHSZ,
cDSZ]
; Field definition type
TYPE DBFld=[BYTE bTp
CARD cFL
BYTE bFD,
bNm]
; Global vars
BYTE bgErr=[0]
CARD cErrO
; Enough storage for 128 fields
BYTE ARRAY baFlds(SZMEM)
; Custom Quit routine
PROC DIQuit()
; Restore error vector
Error=cErrO
RETURN
; Custom Error routine
PROC DIErr(BYTE bE)
; Set global error number
bgErr=bE
; Print custom error message
PrintF("%EERROR: ")
if bE=170 then
PrintE("File not found!")
elseif bE=130 then
PrintE("Invalid device!")
elseif bE=255 then
PrintE("DBF file invalid!")
fi
if bE>240 then
DIQuit()
fi
RETURN
; Func..: GType()
; Return: BYTE=ATASCII key (upper)
; Desc..: Waits for CDLNM key
BYTE FUNC GType()
BYTE bRCH=764,bR,bE
; Set exit flag 0
bE=0
DO
; Loop until keypress
while bRCH=KNONE DO OD
; Get key (keycode) and reset
bR=bRCH
bRCH=KNONE
; Convert keycode to ATASCII
if bR=18 or bR=82 then
bR='C
bE=1
elseif bR=58 or bR=122 then
bR='D
bE=1
elseif bR=0 or bR=64 then
bR='L
bE=1
elseif bR=35 or bR=99 then
bR='N
bE=1
elseif bR=37 or bR=101 then
bR='M
bE=1
fi
until bE=1
OD
RETURN(bR)
; Main routine
PROC Main()
BYTE bFn,bEx,bMF,bLp,bFlp,bCX,bCY
BYTE bSD=[0]
; DB header record
DBHead dHd
; Pointer to field record
DBFld POINTER pFld
; Pointer to name field
BYTE POINTER pNm
; Filename and field name input
CHAR ARRAY cDBF(16),cDBT(16),cI(12),cF(11),cDt(7),cBf(5)
CARD cLp
; Save and reassign error vector
cErrO=Error
Error=DIErr
; Clear memory
Zero(baFlds,SZMEM)
; Setup screen
Poke(82,0)
Put(CCLS)
Position(0,0)
PrintE("-[DBF Make]-----------[ ]-")
; Check for SpartaDOS
bSD=IsSD()
; Get date
Position(0,2)
Print("Todays Date (YYMMDD)?")
InputS(cDt)
; Parse date into components; YY,MM,DD
SCopyS(cBf,cDt,1,2)
dHd.bYr=ValB(cBf)
SCopyS(cBf,cDt,3,4)
dHd.bMo=ValB(cBf)
SCopyS(cBf,cDt,5,6)
dHd.bDy=ValB(cBf)
; Get filename
Print("Filename (NO ext)?")
InputS(cI)
; Create DBF and DBT names
SCopy(cDBF,cI)
SCopy(cDBT,cI)
SAssign(cDBF,".DBF",cDBF(0)+1,cDBF(0)+5)
SAssign(cDBT,".DBT",cDBT(0)+1,cDBT(0)+5)
; Save cursor position
bCY=Peek(84)
bCX=Peek(85)
; Open file for write
Open(4,cDBF,8,0)
; If no error, proceed
if bgErr=0 then
; Update title with DBF name
Position(23,0)
Print(cDBF)
; Restore cursor position
Position(bCX,bCY)
; Set field #, rec size, exit flag, memo flag
bFn=0
dHd.cDSZ=0
bEx=FALSE
bMF=FALSE
DO
; 0 based counter computes.
; Set Fld pointer to storage loc
pFld=baFlds+(bFn*SZFLD)
; Set Name pointer to offset
pNm=pFld+OFFNM
; Inc counter (start at 1)
bFn==+1
PrintF("%EField #%D (RETURN=Done)%E",bFn)
; Get field name
Print("Name (10 char)? ")
InputS(cF)
; If field name len > 0
if cF(0) > 0 then
; Copy input to pointer memory
SCopy(pNm,cF)
; Get field type
Print("Type (C/D/L/M/N)?")
pFld.bTp=GType()
; Make upper and display
pFld.bTp=ToUpper(pFld.bTp)
Put(pFld.bTp)
; Character Field
if pFld.bTp = 'C then
; Get length
PrintF("%ELength (1 to 254)?")
pFld.cFL=InputB()
; Check bounds
if pFld.cFL > 254 then
pFld.cFL = 254
elseif pFld.cFL < 1 then
pFld.cFL = 1
fi
; Date Field
elseif pFld.bTp = 'D then
; Set length, newline
pFld.cFL=8
PutE()
; Logical Field
elseif pFld.bTp = 'L then
; Set length, newline
pFld.cFL=1
PutE()
; Memo Field
elseif pFld.bTp = 'M then
; Set length, set flag
pFld.cFL=10
bMF=TRUE
PutE()
; Numeric Field
elseif pFld.bTp = 'N then
; Get field length
PrintF("%ELength (1 to 19)?")
pFld.cFL=InputB()
; Check bounds
if pFld.cFL > 19 then
pFld.cFL=19
elseif pFld.cFL 15 then
pFld.bFD=15
fi
fi
; Add size to tally
dHd.cDSZ==+pFld.cFL
; Exit
else
; Set exit flag
bEx=TRUE
; Minus the one we added up top
bFn==-1
fi
UNTIL cF(0)=0 OR bFn=MAXREC OR bEx=TRUE
OD
PutE()
PrintE("Writing database file(s)...")
; Write header
; Put Memo byte
if bMF=TRUE then
PutD(4,$83)
else
PutD(4,$03)
fi
; Put Year, Month, Day
PutD(4,dHd.bYr)
PutD(4,dHd.bmo)
PutD(4,dHd.bDy)
; Put num data recs (4 bytes of 0)
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
; Compute header size, then put
; 32 * num_fields + 32 (1st bytes) +
; 2 (term bytes)
dHd.cHSZ=(32 * bFn) + 34
PrintF("Header Size: %U%E",dHd.cHSZ)
PutCD(4,dHd.cHSZ)
; Add delete flag to rec size, then put
dHd.cDSZ==+1
PrintF("Data Size : %U%E",dHd.cDSZ)
PutCD(4,dHd.cDSZ)
; Add 20 bytes of filler
for bLp=1 to 20
DO
PutD(4,$00)
OD
; Display header
PutE()
PrintE("#-- Name------ Type Len Dec")
; Process each input field
for bLp=1 to bFn
DO
; Compute from 1 offset (-1)
; Set Fld pointer to storage loc
pFld=baFlds+((bLp-1)*SZFLD)
; Set Name pointer to offset
pNm=pFld+OFFNM
; Display field number, name
PrintF("%3D %-10S ",bLp,pNm)
; Display field type
if pFld.bTp=$43 then
Print("Char")
elseif pFld.bTp=$44 then
Print("Date")
elseif pFld.bTp=$4C then
Print("Log ")
elseif pFld.bTp=$4D then
Print("Memo")
elseif pFld.bTp=$4E then
Print("Num ")
fi
; Display field length, decimals
PrintF(" %3D %3D%E",pFld.cFL,pFld.bFD)
; Write field name and terminator
PrintD(4,pNm)
; Pad name to 11 (0 based, term inc)
for bFLp=pNm(0) to 10
DO
PutD(4,$00)
OD
; Write field type
PutD(4,pFld.bTp)
; Write fake mem address
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
; Write field length
PutD(4,pFld.cFL)
; Write decimals
PutD(4,pFld.bFD)
; Write two fillers for MU DB3
PutD(4,$00)
PutD(4,$00)
; Write work area ID, always 1
PutD(4,$01)
; Write 11 bytes filler
for bFLp=1 to 11
DO
PutD(4,$00)
OD
OD
; Write terminators
PutD(4,$0D)
PutD(4,$00)
PutD(4,$1A)
; Close DBF file
Close(4)
; If memo file, create it
if bMF = TRUE then
; Open file for write
Open(5,cDBT,8,0)
; If no error, proceed
if bgErr=0 then
; Write first memo loc at 1
; 4 bytes, rest is 0.
PutD(5, $01)
; Write 511 bytes of 0
for cLp=2 to 512
DO
PutD(5,$00)
OD
; Close DBT file
Close(5)
fi
fi
; Display done
PutE()
PrintE("Database created.")
fi
; Restore Error handler and clean up
DIQuit()
; If SpartaDOS, exit through DOSVEC
if bSD=1 then
SDx()
fi
RETURN
Breakdown
This section pulls in the Action! run time library, the Action! toolkit libraries needed, and my library files:
; Inc Action Runtime
INCLUDE "D2:SYS.ACT"
; Inc Action Toolkit parts
INCLUDE "D5:PRINTF.ACT"
INCLUDE "D5:CHARTEST.ACT"
; Include library
INCLUDE "D3:DEFINES.ACT"
INCLUDE "D3:LIBIO.ACT"
INCLUDE "D3:LIBMISC.ACT"
INCLUDE "D3:LIBDOS.ACT"
This section declares the global variables and sets up some compiler definitions. Of note is variable baFlds which is setup as large array of 1664 bytes. This is enough space to store 128 field definitions which are 13 bytes each. The field definitions are are stored as records of type DBFld:
; Start DBF Make
MODULE
; Size of a field record
DEFINE SZFLD = "13"
DEFINE OFFNM = "4"
DEFINE MAXREC = "128"
DEFINE SZMEM = "1664"
; Header record type
TYPE DBHead=[BYTE bMe,
bYr,
bMo,
bDy
CARD cNDR,
cHSZ,
cDSZ]
; Field definition type
TYPE DBFld=[BYTE bTp
CARD cFL
BYTE bFD,
bNm]
; Global vars
BYTE bgErr=[0]
CARD cErrO
; Enough storage for 128 fields
BYTE ARRAY baFlds(SZMEM)
This section is the custom quit and error routines, just as I described in DBFINFO:
; Custom Quit routine
PROC DIQuit()
; Restore error vector
Error=cErrO
RETURN
; Custom Error routine
PROC DIErr(BYTE bE)
; Set global error number
bgErr=bE
; Print custom error message
PrintF("%EERROR: ")
if bE=170 then
PrintE("File not found!")
elseif bE=130 then
PrintE("Invalid device!")
elseif bE=255 then
PrintE("DBF file invalid!")
fi
if bE>240 then
DIQuit()
fi
RETURN
This function gets input from an allowed set of characters without needing to press RETURN, then passes the result back as the ATASCII byte of the key pressed (in upper case). The code is well documented so I won’t break it down any further, if you have questions ask:
; Func..: GType()
; Return: BYTE=ATASCII key (upper)
; Desc..: Waits for CDLNM key
BYTE FUNC GType()
BYTE bRCH=764,bR,bE
; Set exit flag 0
bE=0
DO
; Loop until keypress
while bRCH=KNONE DO OD
; Get key (keycode) and reset
bR=bRCH
bRCH=KNONE
; Convert keycode to ATASCII
if bR=18 or bR=82 then
bR='C
bE=1
elseif bR=58 or bR=122 then
bR='D
bE=1
elseif bR=0 or bR=64 then
bR='L
bE=1
elseif bR=35 or bR=99 then
bR='N
bE=1
elseif bR=37 or bR=101 then
bR='M
bE=1
fi
until bE=1
OD
RETURN(bR)
This is the start of the main program routine. It declares the variables used:
; Main routine
PROC Main()
BYTE bFn,bEx,bMF,bLp,bFlp,bCX,bCY
BYTE bSD=[0]
; DB header record
DBHead dHd
; Pointer to field record
DBFld POINTER pFld
; Pointer to name field
BYTE POINTER pNm
; Filename and field name input
CHAR ARRAY cDBF(16),cDBT(16),cI(12),cF(11),cDt(7),cBf(5)
CARD cLp
This saves the existing error vector and assigns the custom one:
; Save and reassign error vector
cErrO=Error
Error=DIErr
This fills the memory set aside for the field storage with $00 bytes. Just making sure its clear before using it:
; Clear memory
Zero(baFlds,SZMEM)
This sections sets up the screen then calls the routine to check for SpartaDOS:
; Setup screen
Poke(82,0)
Put(CCLS)
Position(0,0)
PrintE("-[DBF Make]-----------[ ]-")
; Check for SpartaDOS
bSD=IsSD()
For now just get the date from the user. Later I may change this to detect if an RTC (real time clock) is installed and read from that. After input, it breaks the input down into year, month, and day components. It doesn’t do any data sanitization, but it should. My bad:
; Get date
Position(0,2)
Print("Todays Date (YYMMDD)?")
InputS(cDt)
; Parse date into components; YY,MM,DD
SCopyS(cBf,cDt,1,2)
dHd.bYr=ValB(cBf)
SCopyS(cBf,cDt,3,4)
dHd.bMo=ValB(cBf)
SCopyS(cBf,cDt,5,6)
dHd.bDy=ValB(cBf)
This section gets a filename from the user (it should NOT have an extension). It then defines the DBF and DBT filename variables by copying the input into each and appending the appropriate extension:
; Get filename
Print("Filename (NO ext)?")
InputS(cI)
; Create DBF and DBT names
SCopy(cDBF,cI)
SCopy(cDBT,cI)
SAssign(cDBF,".DBF",cDBF(0)+1,cDBF(0)+5)
SAssign(cDBT,".DBT",cDBT(0)+1,cDBT(0)+5)
This just saves the cursors position. After a successful file open the filename will be populated into the screen header and the cursor needs to be restored to this spot:
; Save cursor position
bCY=Peek(84)
bCX=Peek(85)
This tries to open the DBF file for writing. If it succeeds the program continues:
; Open file for write
Open(4,cDBF,8,0)
; If no error, proceed
if bgErr=0 then
The open succeeded so update the title bar with the filename and restore the cursor position:
; Update title with DBF name
Position(23,0)
Print(cDBF)
; Restore cursor position
Position(bCX,bCY)
Set flags used in the field definition loop. Number of fields (bFn) to 0, dBase data record size (dHd.cDSZ) to 0, exit flag (bEx) to FALSE, and memo flag (bMF) to FALSE. Then start the field definition loop:
; Set field #, rec size, exit flag, memo flag
bFn=0
dHd.cDSZ=0
bEx=FALSE
bMF=FALSE
DO
This computes a pointer to the current field definition numbers (bFn) location in our reserved storage space (baFlds). It is the size of a field definition record multiplied by the field definition number:
; 0 based counter computes.
; Set Fld pointer to storage loc
pFld=baFlds+(bFn*SZFLD)
This computes a pointer to the fields name string within the previously calculated location for the current field definition in the reserved storage space (baFlds). It is an offset from the base pointer for the record. The offset is the combined size of the other variables in the field definition record:
; Set Name pointer to offset
pNm=pFld+OFFNM
This section increases the field counter (1 based for visuals). Then it displays the field name prompt, and then gets a field name from the user. If the length of the input field is greater than 0 then the program continues from this point:
; Inc counter (start at 1)
bFn==+1
PrintF("%EField #%D (RETURN=Done)%E",bFn)
; Get field name
Print("Name (10 char)? ")
InputS(cF)
; If field name len > 0
if cF(0) > 0 then
This copies the user input for the field name (cF) into the field name storage location for this record in memory (pNm):
; Copy input to pointer memory
SCopy(pNm,cF)
This section gets the field type from the user. It accepts C, D, L, M, and N (upper or lower) and does not require RETURN to make the input. The result is assigned to this field records type variable after being uppercased and displayed:
; Get field type
Print("Type (C/D/L/M/N)?")
pFld.bTp=GType()
; Make upper and display
pFld.bTp=ToUpper(pFld.bTp)
Put(pFld.bTp)
This checks if the records field type is C (Character). If it is, it gets the length from the user. The input is then sanitized against acceptable values. If its greater than 254, its set to 254. If its less than 1 its set to 1:
; Character Field
if pFld.bTp = 'C then
; Get length
PrintF("%ELength (1 to 254)?")
pFld.cFL=InputB()
; Check bounds
if pFld.cFL > 254 then
pFld.cFL = 254
elseif pFld.cFL < 1 then
pFld.cFL = 1
fi
This checks if the records field type is D (Date). If it is, it sets the length to 8, then forces a new line:
; Date Field
elseif pFld.bTp = 'D then
; Set length, newline
pFld.cFL=8
PutE()
This checks if the records field type is L (Logical). If it is, it sets the length to 1, then forces a new line:
; Logical Field
elseif pFld.bTp = 'L then
; Set length, newline
pFld.cFL=1
PutE()
This checks if the records field type is M (Memo). If it is, it sets the length to 10, sets the memo flag to TRUE, then forces a new line:
; Memo Field
elseif pFld.bTp = 'M then
; Set length, set flag
pFld.cFL=10
bMF=TRUE
PutE()
This checks if the records field type is N (Number). If it is, it gets the field length from the user. The field length is then sanitized against acceptable values between 1 and 19. Then it moves the cursor back up one line since the RETURN pushed it down. Then it gets the number of decimals from the user. The number of decimals is then sanitized against acceptable values:
; Numeric Field
elseif pFld.bTp = 'N then
; Get field length
PrintF("%ELength (1 to 19)?")
pFld.cFL=InputB()
; Check bounds
if pFld.cFL > 19 then
pFld.cFL=19
elseif pFld.cFL 15 then
pFld.bFD=15
fi
fi
This adds the records field length to a running tally for the complete data record size:
; Add size to tally
dHd.cDSZ==+pFld.cFL
If the user pressed RETURN at the field name prompt the program continues from here. It sets the exit flag to TRUE which allows the DO loop to stop gracefully. Then it subtracts 1 from the field number that was added before input because nothing was input:
; Exit
else
; Set exit flag
bEx=TRUE
; Minus the one we added up top
bFn==-1
fi
This is the end of the field input loop. It continues until one of the conditions is true. The length of a fields input is 0, the maximum number of field definitions is reached, or the exit flag is set TRUE.
UNTIL cF(0)=0 OR bFn=MAXREC OR bEx=TRUE
OD
Tell the user whats happening now:
PutE()
PrintE("Writing database file(s)...")
This is the start of where the DBF file is created. It first writes the header information that is common amongst DBF files. The first byte is the memo flag. For dBase III it is $83 if there is a memo field in the record, or $03 if there is not:
; Write header
; Put Memo byte
if bMF=TRUE then
PutD(4,$83)
else
PutD(4,$03)
fi
Now it writes the year, month, and day to the DBF file as bytes 2,3,4:
; Put Year, Month, Day
PutD(4,dHd.bYr)
PutD(4,dHd.bmo)
PutD(4,dHd.bDy)
Since we are creating a new file, the number of data records will always be 0. Fill bytes 5,6,7,8 with $00.
; Put num data recs (4 bytes of 0)
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
This computes the complete header size (header plus field definitions). The common header is 32 bytes and each field definition is 32 bytes, and there are 2 terminator bytes. The formula is 32 + (number_of_fields * 32) + 2. Then display the computed header size and write the CARD value to the DBF file:
; Compute header size, then put
; 32 * num_fields + 32 (1st bytes) +
; 2 (term bytes)
dHd.cHSZ=(32 * bFn) + 34
PrintF("Header Size: %U%E",dHd.cHSZ)
PutCD(4,dHd.cHSZ)
This adds 1 byte for the deletion flag to the tallied data record size. It then displays the computed data record size and writes the CARD value to the DBF file:
; Add delete flag to rec size, then put
dHd.cDSZ==+1
PrintF("Data Size : %U%E",dHd.cDSZ)
PutCD(4,dHd.cDSZ)
This puts 20 bytes of filler into the DBF file. These are bytes that do not have a significant meaning:
; Add 20 bytes of filler
for bLp=1 to 20
DO
PutD(4,$00)
OD
This displays a header for the field definitions the program is about to output. Very similar to that of DBFINFO, if not exact:
; Display header
PutE()
PrintE("#-- Name------ Type Len Dec")
This section loops through each field definition entered. It displays the information and writes it to the DBF file:
; Process each input field
for bLp=1 to bFn
DO
This computes the field defintions record location in the reserved storage space just like was done during entry. And also computes the field name location within that record location:
; Compute from 1 offset (-1)
; Set Fld pointer to storage loc
pFld=baFlds+((bLp-1)*SZFLD)
; Set Name pointer to offset
pNm=pFld+OFFNM
This displays the field number and name:
; Display field number, name
PrintF("%3D %-10S ",bLp,pNm)
This displays the field type in english terms based on the fields defined type:
; Display field type
if pFld.bTp=$43 then
Print("Char")
elseif pFld.bTp=$44 then
Print("Date")
elseif pFld.bTp=$4C then
Print("Log ")
elseif pFld.bTp=$4D then
Print("Memo")
elseif pFld.bTp=$4E then
Print("Num ")
fi
This displays the field length and decimal precision:
; Display field length, decimals
PrintF(" %3D %3D%E",pFld.cFL,pFld.bFD)
This writes the field name of this field definition to the DBF file. It then pads the field name to the 11th position with $00 bytes. 11 positions is significant for 0 terminated strings of 10 in size:
; Write field name and terminator
PrintD(4,pNm)
; Pad name to 11 (0 based, term inc)
for bFLp=pNm(0) to 10
DO
PutD(4,$00)
OD
This writes the field type byte to the DBF file for this field definition:
; Write field type
PutD(4,pFld.bTp)
The fields memory address won’t be used on the Atari and is rarely used on the PC. Fill it with $00:
; Write fake mem address
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
PutD(4,$00)
Write the field length and decimal precision to the DBF file for this field definition:
; Write field length
PutD(4,pFld.cFL)
; Write decimals
PutD(4,pFld.bFD)
The next two bytes are for multi user dBase III. The values don’t matter at creation so they are filled with $00:
; Write two fillers for MU DB3
PutD(4,$00)
PutD(4,$00)
The next byte is ALWAYS 1, always, even though its not used. It is the work area ID:
; Write work area ID, always 1
PutD(4,$01)
Now write the rest of the field definition as $00 to complete the 32 byte definition in the DBF file:
; Write 11 bytes filler
for bFLp=1 to 11
DO
PutD(4,$00)
OD
OD
This writes the header terminator ($0D) and the first data record (or zeroth) terminators to the DBF file. Then the DBF file is closed:
; Write terminators
PutD(4,$0D)
PutD(4,$00)
PutD(4,$1A)
; Close DBF file
Close(4)
If the memo flag was set to true, meaning there is at least one memo field defined, open the DBT file for writing. If it opened successfully continue:
; If memo file, create it
if bMF = TRUE then
; Open file for write
Open(5,cDBT,8,0)
; If no error, proceed
if bgErr=0 then
Because this is a newly created file, the first byte will always be 1, which is the index of the first memo field location within the DBT file. The rest is filled with garbage on the PC, but I chose to fill it with $00 bytes. Each memo field location within the file is 512 bytes. Then the file is closed:
; Write first memo loc at 1
; 4 bytes, rest is 0.
PutD(5, $01)
; Write 511 bytes of 0
for cLp=2 to 512
DO
PutD(5,$00)
OD
; Close DBT file
Close(5)
fi
fi
Tell the user the database creation is complete, then call the custom quit routine to restore the error vector. Then if SpartaDOS was detected, jump to it through DOSVEC using the library routine SDx():
; Display done
PutE()
PrintE("Database created.")
fi
; Restore Error handler and clean up
DIQuit()
; If SpartaDOS, exit through DOSVEC
if bSD=1 then
SDx()
fi
RETURN
Results
Here I created the same structure I created on the PC to test DBFINFO. 4 fields, one of each type, except MEMO. Shown using DBFINFO on the Atari 8 bit and a counterpart on the PC:
And the file size of the created file on both the Atari 8 bit and the PC:
And a comparison of the structures from the file created on the Atari 8 bit and one created on the PC:
In this shot I’ve just completed creating a database file using one of each field type including memo:
These screenshots shows my DBFINFO on the Atari 8 bit displaying information about the database structure as well as the PC counterpart displaying the same for the file created on the Atari:
These shots show the sizes of the created files (the DBF and DBT) on both the Atari 8 bit and the PC:
You an download the ATR image here. Rename it from .key to .ATR. Sorry for the inconvenience, the hosting provider doesn’t allow .ATR files.
Here is a link to a well documented dBase file structure.