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.