# Five DBF Engine Specification > Harbour DBF 소스 코드 정밀 분석 결과 > 바이트 레벨 포맷 호환을 위한 구현 사양 > > Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) > All rights reserved. > > Source reference: /mnt/d/harbour-core/src/rdd/dbf1.c, include/hbdbf.h --- ## 1. DBF 헤더 (32 bytes) ``` Offset Size Field Description ────── ──── ───── ────────── 0 1 bVersion DB type: 0x03=std, 0x83=DBT, 0xF5=FPT, 0x30/31/32=VFP 1 1 bYear Last update year (year - 1900) 2 1 bMonth Last update month (1-12) 3 1 bDay Last update day (1-31) 4 4 ulRecCount Record count (LE uint32) 8 2 uiHeaderLen Total header length including terminators (LE uint16) 10 2 uiRecordLen Record length including deletion flag (LE uint16) 12 2 bReserved1 Reserved (0x00) 14 1 bTransaction 1=in transaction 15 1 bEncrypted 1=encrypted 16 12 bReserved2 Multi-user/LAN (12 bytes) 28 1 bHasTags 0x01=production index, 0x02=memo (VFP) 29 1 bCodePage Code page identifier 30 2 bReserved3 Reserved (0x00) ``` ### Version byte values ``` 0x03 Standard DBF III 0x04 DBF IV (reserved) 0x30 VFP (Visual FoxPro) 0x31 VFP + autoincrement 0x32 VFP + varchar/varbinary 0x83 DBF III + DBT memo 0xF5 DBF + FPT memo 0x8B DBF IV + DBT memo 0x06 Encrypted DBF 0x86 Encrypted + DBT 0xE6 Encrypted + SMT 0xF6 Encrypted + SMT ``` --- ## 2. 필드 디스크립터 (32 bytes per field) ``` Offset Size Field Description ────── ──── ───── ────────── 0 11 bName Field name (null-terminated, space-padded) 11 1 bType Type char: C, N, L, D, M, I, B, T, @, +, =, ^, Y, Z, Q, V, P, W, G 12 4 bReserved1 Reserved (VFP: offset in record) 16 1 bLen Field length (max 255) 17 1 bDec Decimal places 18 1 bFieldFlags 0x01=system, 0x02=nullable, 0x04=binary 19 4 bCounter Autoincrement counter (LE) 23 1 bStep Autoincrement step 24 7 bReserved2 Reserved 31 1 bHasTag Has index tag (VFP) ``` ### 헤더 종결자 ``` - 필드 디스크립터 이후 0x0D (carriage return) 1 byte 종결자 - VFP: 추가 263 bytes 백필러 가능 - headerLen = 32(header) + fieldCount*32 + 1(0x0D) [+ backlink] ``` --- ## 3. 필드 타입별 바이트 포맷 ### C (Character) ``` 저장: 원시 바이트, 우측 공백 패딩 읽기: 후행 공백 보존 (SET EXACT에 따라) 최대: 65535 bytes (bLen + bDec*256) ``` ### N (Numeric) ``` 저장: ASCII 문자열, 우측 정렬, 좌측 공백 패딩 형식: " -123.45" (폭 = bLen, 소수점 = bDec) 음수: '-' 기호 포함 빈 값: 모두 공백 (" ") ``` ### L (Logical) ``` 저장: 1 byte 참: 'T', 't', 'Y', 'y' (모두 true로 인식) 거짓: 'F', 'f', 'N', 'n' (모두 false로 인식) 미정: ' ' (space) = NIL ``` ### D (Date) ``` 표준 (bLen=8): "YYYYMMDD" ASCII (빈 날짜 = " ") 짧은 형식 (bLen=3): LE uint24 packed date VFP (bLen=4): LE uint32 Julian day ``` ### M (Memo) ``` bLen=4: LE uint32 block number bLen=10: ASCII block number (우측 공백) block number 0 = 빈 메모 ``` ### I (Integer — VFP) ``` bLen=1: signed int8 bLen=2: signed int16 LE bLen=3: signed int24 LE bLen=4: signed int32 LE bLen=8: signed int64 LE ``` ### B (Double — VFP) ``` bLen=8: IEEE 754 double (8 bytes LE) ``` ### @ (Timestamp — VFP) ``` bLen=8: 4 bytes date (LE int32) + 4 bytes time (LE int32) date = Julian day number time = milliseconds since midnight ``` ### = (Modtime) ``` 동일 형식: @ (Timestamp) 자동 업데이트됨 ``` ### + (Autoincrement) ``` bLen=1/2/3/4/8: 부호 있는 정수 LE 자동 증가 ``` ### Y (Currency) ``` bLen=8: LE int64, 암묵적 4자리 소수점 (value / 10000.0) ``` ### ^ (RowVersion) ``` bLen=8: LE uint64, 자동 버전 증가 ``` --- ## 4. 레코드 레이아웃 ``` Byte 0: Deletion flag (' '=active, '*'=deleted) Byte 1..N: Field data (각 필드가 연속 배치) 레코드 오프셋 = headerLen + (recNo - 1) * recordLen EOF 마커 위치 = headerLen + recordLen * recordCount EOF 값 = 0x1A ``` ### 레코드 번호 ``` 1-based (첫 레코드 = 1) 0 = BOF (유효하지 않은 레코드) recordCount + 1 = 유령 레코드 (APPEND용) ``` --- ## 5. 락 스키마 (6종) ### 위치 계산 공식 ```go // 방향에 따른 레코드 락 위치: switch direction { case +1: // forward lockPos = basePos + recNo case -1: // backward (VFP with tags) lockPos = basePos - recNo case 2: // at record (VFP no tags) lockPos = basePos + (recNo-1)*recordLen + headerLen } ``` ### 스키마별 상수 ``` 스키마 베이스 위치 방향 파일락 크기 레코드락 크기 ───────── ────────────────────── ──── ──────────── ────────── DB_DBFLOCK_CLIPPER 1,000,000,000 +1 294,967,295 1 byte DB_DBFLOCK_CLIPPER2 4,000,000,000 +1 294,967,295 1 byte DB_DBFLOCK_COMIX 1,000,000,000 +1 1 1 byte DB_DBFLOCK_VFP (hasTags) 0x7FFFFFFE -1 0x07FFFFFF 1 byte DB_DBFLOCK_VFP (noTags) 0x40000000 +2 0x3FFFFFFF recordLen DB_DBFLOCK_HB32 4,000,000,000 +1 294,967,295 1 byte DB_DBFLOCK_HB64 0x7F00000000000000 +1 0x00000000FFFFFFFE 1 byte ``` ### 파일 락 (전체 테이블 잠금) ```go // 파일 락 위치 = 베이스 위치 // 파일 락 크기 = 스키마별 상수 (위 표 참조) fileLockPos = lockBasePos fileLockSize = scheme.fileLockSize ``` ### 레코드 락 (개별 레코드 잠금) ```go // 레코드 락 위치 = 방향에 따라 계산 // 레코드 락 크기 = 1 byte (VFP noTags는 recordLen) recLockPos = calculateRecLockPos(scheme, recNo) recLockSize = scheme.recLockSize ``` ### 헤더 락 (APPEND 시) ```go // APPEND BLANK 시 다른 프로세스의 동시 APPEND 방지 headerLockPos = lockBasePos // 파일 락과 같은 위치 headerLockSize = 1 ``` --- ## 6. FPT 메모 파일 ### FPT 헤더 (512 bytes) ``` Offset Size Field Description ────── ──── ───── ────────── 0 4 nextBlock Next free block (BE uint32) 4 2 reserved1 Reserved 6 2 blockSize Block size in bytes (BE uint16) 8 504 reserved2 Reserved (VFP: contains GC info at offset 536) ``` ### 메모 블록 구조 ``` Offset Size Field Description ────── ──── ───── ────────── 0 4 type Block type (BE uint32): 0=picture, 1=memo, 2=object 4 4 size Data size in bytes (BE uint32) 8 N data Actual memo data ``` ### 블록 오프셋 계산 ```go blockOffset = int64(blockNumber) * int64(blockSize) ``` ### 기본 블록 크기 ``` DBT (dBASE): 512 bytes FPT (FoxPro): 64 bytes (최소) SMT (SIx): 32 bytes ``` --- ## 7. OPEN 처리 순서 ``` 1. 파일 열기 (fOpen or fCreate) 2. 헤더 32 bytes 읽기 3. uiHeaderLen 검증 (>= 66, headerLen % 32 == 0 or 1) 4. 필드 디스크립터 읽기 (fieldCount = (headerLen - 32 - 1) / 32) 5. 각 필드의 offset 계산: pFieldOffset[0] = 1 (deletion flag 이후) pFieldOffset[i+1] = pFieldOffset[i] + field[i].bLen 6. recordLen 검증: pFieldOffset[fieldCount] == recordLen 7. 레코드 버퍼 할당 (recordLen bytes) 8. shared 모드면 recCount 재계산: recCount = (fileSize - headerLen) / recordLen 9. 인덱스 파일이 있으면 자동 열기 (bHasTags 체크) 10. 메모 파일이 있으면 열기 (version byte 체크) ``` --- ## 8. APPEND BLANK 처리 ``` 1. 파일 락/헤더 락 획득 2. shared 모드면 recCount 재계산 3. recCount++ 4. 새 레코드 오프셋 = headerLen + (recCount - 1) * recordLen 5. 레코드 버퍼를 공백(' ')으로 초기화 6. 파일에 쓰기 7. EOF 마커(0x1A) 쓰기 8. 헤더의 recCount 갱신 9. 현재 위치를 새 레코드로 설정 10. 락 해제 ``` --- ## 9. PACK 처리 ``` 1. 배타적 잠금 필수 (fShared이면 에러) 2. 열린 인덱스 모두 닫기 3. ulRecOut = 0 (출력 카운터) 4. FOR recNo = 1 TO recCount: 레코드 읽기 IF NOT deleted: ulRecOut++ 레코드를 ulRecOut 위치에 쓰기 5. recCount = ulRecOut 6. 파일 크기 조정 (truncate) 7. EOF 마커 쓰기 8. 헤더 갱신 9. 인덱스 재빌드 (REINDEX) ``` --- ## 10. 헤더 갱신 로직 ``` 갱신 시점: - APPEND BLANK 이후 - PACK 이후 - CLOSE 시 (fUpdateHeader 플래그가 설정되어 있으면) - FLUSH 시 갱신 내용: - bYear/bMonth/bDay: 현재 날짜 - ulRecCount: 현재 레코드 수 - 32 bytes를 파일 오프셋 0에 쓰기 ``` --- ## 11. Go 구현 체크리스트 ``` [ ] DBF 헤더 읽기/쓰기 (32 bytes, LE) [ ] 필드 디스크립터 읽기/쓰기 (32 bytes × N) [ ] 필드 타입별 GET (C, N, L, D, M, I, B, @, +, =, ^, Y) [ ] 필드 타입별 PUT (역방향) [ ] 레코드 오프셋 계산 (headerLen + (recNo-1) * recordLen) [ ] 삭제 플래그 관리 (pRecord[0] = ' ' or '*') [ ] EOF 마커 (0x1A) 읽기/쓰기 [ ] OPEN: 헤더 → 필드 → 오프셋 배열 → 버퍼 할당 [ ] CLOSE: 플러시 → 헤더 갱신 → 파일 닫기 [ ] APPEND BLANK: 락 → recCount++ → 빈 레코드 쓰기 → EOF → 헤더 갱신 [ ] DELETE/RECALL: pRecord[0] 변경 [ ] PACK: 배타적 → 순차 재작성 → truncate → 재인덱스 [ ] 6종 락 스키마 전부 구현 [ ] shared 모드에서 recCount 재계산 [ ] FPT 메모: 헤더 → 블록 읽기/쓰기 [ ] Harbour/Clipper로 생성한 DBF ↔ Five로 읽기 호환 테스트 [ ] Five로 생성한 DBF ↔ Harbour/Clipper로 읽기 호환 테스트 ``` --- ## 변경 이력 | 날짜 | 변경 내용 | |------|----------| | 2026-03-28 | 초기 작성. Harbour dbf1.c/hbdbf.h 정밀 분석 |