# Five RDD Architecture Specification > Harbour RDD 상속 아키텍처 분석 및 Go 재설계 > WAAREA → DBF → DBFNTX/DBFCDX 체인의 정밀 분석 > > Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) > All rights reserved. > > Source reference: /mnt/d/harbour-core/include/hbapirdd.h, src/rdd/ --- ## 1. Harbour RDD 상속 체인 ``` WAAREA (base, 101 methods) │ ~25 real implementations + ~76 unsupported stubs │ ├── DBF (overrides all 101 methods) │ │ 파일 I/O, 필드 GET/PUT, 레코드 관리, 락 │ │ │ ├── DBFFPT (DBF + FPT memo) │ │ │ 메모 5 methods override │ │ │ │ │ ├── DBFNTX (+ NTX index) │ │ │ ~30 methods override (movement, order, filter) │ │ │ │ │ └── DBFCDX (+ CDX index) │ │ ~20 methods override (order, CDX-specific) │ │ │ └── DBFDBT (DBF + DBT memo, fallback) │ │ │ ├── DBFNTX (fallback parent) │ └── DBFCDX (fallback parent) │ └── SDF, DELIM (flat file drivers, separate chain) ``` ### 상속 해석 순서 (DBFNTX 예시) ```c // DBFNTX 등록 시: errCode = hb_rddInheritEx(&ntxTable, &ntxSuper, "DBFFPT", ...); // 1st: try DBFFPT if (errCode != HB_SUCCESS) errCode = hb_rddInheritEx(&ntxTable, &ntxSuper, "DBFDBT", ...); // 2nd: try DBFDBT if (errCode != HB_SUCCESS) errCode = hb_rddInheritEx(&ntxTable, &ntxSuper, "DBF", ...); // 3rd: fallback DBF ``` ### hb_rddInheritEx 알고리즘 ``` 1. 부모 RDD를 이름으로 검색 2. 부모의 전체 메서드 테이블 (101개)을 복사 → pTable, pSuperTable 양쪽 3. 자식의 메서드 테이블을 순회: - NULL이 아닌 항목만 pTable에 덮어씀 (override) - NULL 항목은 부모 메서드가 그대로 유지됨 (inherit) 4. pSuperTable은 부모 원본 그대로 보존 (SUPER_* 호출용) ``` --- ## 2. 101개 메서드 분류 + 드라이버별 오버라이드 현황 ### Movement & Positioning (11) | Method | WAAREA | DBF | DBFNTX | DBFCDX | |--------|--------|-----|--------|--------| | bof | real | override | override | inherit | | eof | real | override | override | inherit | | found | real | override | override | inherit | | goBottom | unsup | override | override | **override** | | go | unsup | override | override | inherit | | goToId | unsup | override | override | inherit | | goTop | unsup | override | override | **override** | | seek | unsup | override | **override** | **override** | | skip | real | override | override | **override** | | skipFilter | real | override | override | inherit | | skipRaw | unsup | override | override | **override** | ### Data Management (22) | Method | WAAREA | DBF | DBFNTX | DBFCDX | |--------|--------|-----|--------|--------| | addField | real | override | inherit | inherit | | append | unsup | override | inherit | inherit | | createFields | real | override | inherit | inherit | | deleterec | unsup | override | inherit | inherit | | deleted | unsup | override | inherit | inherit | | fieldCount | real | override | inherit | inherit | | fieldInfo | real | override | inherit | inherit | | fieldName | real | override | inherit | inherit | | flush | unsup | override | override | **override** | | getValue | unsup | override | inherit | inherit | | putValue | unsup | override | inherit | inherit | | goCold | unsup | override | override | **override** | | goHot | unsup | override | override | **override** | | reccount | unsup | override | inherit | inherit | | recno | unsup | override | inherit | inherit | | ... | | | | | ### Order Management (9) — 핵심 차이점 | Method | WAAREA | DBF | DBFNTX | DBFCDX | |--------|--------|-----|--------|--------| | orderListAdd | unsup | stub | **NTX** | **CDX** | | orderListClear | unsup | stub | **NTX** | **CDX** | | orderListFocus | unsup | stub | **NTX** | **CDX** | | orderListRebuild | unsup | stub | **NTX** | **CDX** | | orderCreate | unsup | stub | **NTX** | **CDX** | | orderDestroy | unsup | stub | **NTX** | **CDX** | | orderInfo | real | override | **NTX** | **CDX** | ### Filter & Scope (11) | Method | WAAREA | DBF | DBFNTX | DBFCDX | |--------|--------|-----|--------|--------| | clearFilter | real | override | **override** | **override** | | setFilter | real | override | **override** | **override** | | clearScope | unsup | override | **NTX** | **CDX** | | countScope | unsup | override | **NTX** | **CDX** | | ... | | | | | --- ## 3. SELF_* / SUPER_* 디스패치 메커니즘 ``` 호출 체인 예시: USE customers VIA DBFNTX → CLOSE User → SELF_CLOSE(workarea) → workarea->lprfsHost->close [DBFNTX의 hb_ntxClose] │ NTX 인덱스 닫기 │ SUPER_CLOSE(area) └→ ntxSuper->close [DBF의 hb_dbfClose] │ DBF 파일 플러시/닫기 │ SUPER_CLOSE(area) └→ dbfSuper->close [WAAREA의 hb_waClose] │ 플래그 초기화 └→ HB_SUCCESS ``` --- ## 4. Go 재설계: Interface 분할 ### 핵심 발견 ``` Harbour: 101개 메서드의 거대한 단일 vtable - 대부분 unsupported stub - 새 드라이버 작성 시 101개를 모두 채워야 함 Go 철학: 작은 interface 조합 - 필요한 것만 구현 - 나머지는 임베딩으로 상속 ``` ### Go Interface 설계 ```go // 계층 1: 필수 (모든 드라이버가 구현) type Driver interface { Name() string Open(params OpenParams) (Area, error) Create(params CreateParams) (Area, error) } // 계층 2: 기본 Area (WAAREA 대응) type Area interface { io.Closer // Movement (11) BOF() bool EOF() bool Found() bool GoTo(recNo uint32) error GoTop() error GoBottom() error Skip(count int64) error SkipFilter(count int64) error // Data (core) RecNo() uint32 RecCount() uint32 Deleted() bool FieldCount() int FieldInfo(index int) FieldInfo GetValue(index int) (Value, error) PutValue(index int, val Value) error Flush() error } // 계층 3: 레코드 조작 (DBF가 구현) type RecordManager interface { Append() error Delete() error Recall() error Pack() error Zap() error } // 계층 4: 인덱스 (DBFNTX, DBFCDX가 각각 구현) type Indexer interface { OrderCreate(params OrderCreateParams) error OrderListAdd(path string) error OrderListClear() error OrderListFocus(tag string) error OrderListRebuild() error OrderDestroy(tag string) error OrderInfo(index int, info *OrderInfo) error Seek(key Value, softSeek bool) (bool, error) } // 계층 5: 락 (DBF가 구현) type Locker interface { Lock(params LockParams) (bool, error) Unlock(recNo uint32) error RawLock(action int, recNo uint32) error } // 계층 6: 필터/관계 type Filterer interface { SetFilter(expr string, block func() bool) error ClearFilter() error } type Relater interface { SetRelation(child Area, keyExpr func() Value) error ClearRelation() error ForceRel() error SyncChildren() error } // 계층 7: 메모 (DBFFPT가 구현) type MemoHandler interface { OpenMemo(path string) error CloseMemo() error GetMemo(blockNo uint32) ([]byte, error) PutMemo(data []byte) (uint32, error) } ``` ### Go 임베딩으로 상속 구현 (Harbour의 hb_rddInheritEx 대응) ```go // WAAREA 대응: 기본 구현 type BaseArea struct { fBof, fEof, fFound bool fields []FieldInfo alias string filter *Filter relations []*Relation } // BaseArea implements Area with default behaviors // DBF 대응: BaseArea를 임베딩 type DBFArea struct { BaseArea // ← WAAREA 상속 (Go 임베딩) file *os.File header DBFHeader recBuf []byte recNo uint32 // ... } // DBFArea implements Area + RecordManager + Locker + MemoHandler // DBFNTX 대응: DBFArea를 임베딩 type NTXArea struct { DBFArea // ← DBF 상속 (Go 임베딩) indexes []*NTXIndex curOrder int // ... } // NTXArea adds Indexer to DBFArea's capabilities // DBFCDX 대응: DBFArea를 임베딩 type CDXArea struct { DBFArea // ← DBF 상속 (Go 임베딩) indexes []*CDXIndex curTag string // ... } // CDXArea adds Indexer to DBFArea's capabilities (CDX-specific) ``` ### SUPER_* 호출 → Go 임베딩 메서드 호출 ```go // Harbour: // SUPER_CLOSE(&pArea->dbfarea.area) → ntxSuper->close(area) // Go: func (a *NTXArea) Close() error { // NTX-specific: close all index files for _, idx := range a.indexes { idx.Close() } // SUPER call → DBFArea.Close() (임베딩으로 자동) return a.DBFArea.Close() } func (a *DBFArea) Close() error { // DBF-specific: flush and close data file a.Flush() a.file.Close() // SUPER call → BaseArea.Close() (임베딩으로 자동) return a.BaseArea.Close() } func (a *BaseArea) Close() error { a.fBof = true a.fEof = true return nil } ``` --- ## 5. 드라이버 등록 (Harbour hb_rddRegister 대응) ```go var drivers = map[string]Driver{} func RegisterDriver(d Driver) { drivers[strings.ToUpper(d.Name())] = d } func init() { RegisterDriver(&DBFDriver{}) RegisterDriver(&DBFNTXDriver{}) RegisterDriver(&DBFCDXDriver{}) } // USE customers VIA DBFCDX func OpenTable(path, driverName string) (Area, error) { d, ok := drivers[strings.ToUpper(driverName)] if !ok { return nil, fmt.Errorf("unknown RDD: %s", driverName) } return d.Open(OpenParams{Path: path}) } ``` --- ## 6. DBSEEK → Index 호출 체인 정밀 분석 ### 전체 흐름 ``` User: SEEK "SMITH" │ ▼ dbcmd.c: HB_FUNC(DBSEEK) │ pKey = "SMITH", fSoftSeek = SET SOFTSEEK value │ SELF_SEEK(pArea, fSoftSeek, pKey, fFindLast) ▼ dbfntx1.c: hb_ntxSeek() │ 1. GOCOLD (flush current record) │ 2. Check lpCurTag != NULL (active index required) │ 3. hb_ntxKeyPutItem() → convert "SMITH" to binary key │ 4. Lock tag (read lock) │ 5. hb_ntxTagKeyFind() → B-tree search │ 6. Scope validation │ 7. SELF_GOTO(recNo) → position data cursor │ 8. SELF_SKIPFILTER() → apply SET FILTER / SET DELETED │ 9. Set area.fFound flag │ 10. Unlock tag ▼ Result: area.fFound = .T./.F., cursor at record or EOF ``` ### 키 변환: hb_ntxKeyPutItem() 사용자가 전달한 값을 인덱스 키 바이너리 형식으로 변환: ``` 타입 변환 방식 예시 ──── ────────── ────── C memcpy + 우측 공백 패딩 (KeyLength까지) "SMITH" → "SMITH " (8바이트 키) 코드페이지 변환 (hb_cdpnDup2) 적용 N hb_ntxNumToStr (정렬 가능 문자열) 123.45 → " 123.45" D YYYYMMDD 문자열 (8바이트) Date → "20260328" L 'T' 또는 'F' (1바이트) .T. → "T" ``` ### B-tree 검색: hb_ntxTagKeyFind() ``` Phase 1: 루트→리프 순회 ┌────────────────────────────────────────────┐ │ ulPage = root (page 0) │ │ WHILE ulPage != 0: │ │ page = LoadPage(ulPage) │ │ iKey = BinarySearch(page, searchKey) │ │ stack.push({page, iKey}) │ │ ulPage = page.children[iKey] │ │ END WHILE │ │ → 리프 페이지에 도착, iKey 위치에 결과 │ └────────────────────────────────────────────┘ Phase 2: 키 추출 CurKeyInfo = page[iKey] recNo = CurKeyInfo.Xtra Phase 3: FINDLAST 처리 (fFindLast=.T. 인 경우) WHILE PrevKey() AND key matches: 이전 키로 이동 (마지막 일치 키 찾기) Phase 4: 결과 판정 exact match → return TRUE (fFound 후보) no match → return FALSE (SOFTSEEK에 따라 처리) ``` ### 페이지 내 이진 검색: hb_ntxPageKeyFind() ```go // Go 의사코드 (Harbour hb_ntxPageKeyFind 대응) func pageKeyFind(tag *TagInfo, page *PageInfo, key []byte, keyLen int, fNext bool, recNo uint32) (int, bool) { lo, hi := 0, int(page.keyCount)-1 found := false last := -1 for lo <= hi { mid := (lo + hi) / 2 cmp := keyCompare(tag, key, keyLen, page.keyAt(mid), tag.keyLength, false) // 레코드 번호로 타이브레이커 (fSortRec일 때) if cmp == 0 && recNo != 0 && tag.fSortRec { pageRec := page.recAt(mid) if recNo < pageRec { cmp = -1 } else if recNo > pageRec { cmp = 1 } else { return mid, true } // 정확히 일치 } // 내림차순 인덱스면 비교 반전 if cmp != 0 && !tag.ascendKey { cmp = -cmp } if (fNext && cmp >= 0) || (!fNext && cmp > 0) { lo = mid + 1 } else { if cmp == 0 && recNo == 0 { found = true } last = mid hi = mid - 1 } } if last >= 0 { return last, found } return int(page.keyCount), found } ``` ### 키 비교: hb_ntxValCompare() ```go // Go 의사코드 (Harbour hb_ntxValCompare 대응) func keyCompare(tag *TagInfo, val1 []byte, len1 int, val2 []byte, len2 int, exact bool) int { limit := min(len1, len2) var result int if tag.keyType == 'C' { if tag.isBinSort() { result = bytes.Compare(val1[:limit], val2[:limit]) } else { // 코드페이지 기반 정렬 (collation) result = codepageCompare(tag.codepage, val1[:len1], val2[:len2]) } } else { // N, D, L: 바이너리 비교 result = bytes.Compare(val1[:limit], val2[:limit]) } if result == 0 { if len1 > len2 { return 1 } if len1 < len2 && exact { return -1 } } // 정규화: -1, 0, +1 if result > 0 { return 1 } if result < 0 { return -1 } return 0 } ``` ### 페이지 스택과 SKIP SEEK 후 스택이 유지되어 SKIP이 효율적: ``` SEEK 후 스택 상태: stack[0] = {page=5, ikey=3} ← 루트 stack[1] = {page=12, ikey=7} ← 중간 stack[2] = {page=24, ikey=2} ← 리프 (현재 위치) stackLevel = 3 SKIP +1 (hb_ntxTagNextKey): IF stack[2].ikey+1 < page[24].keyCount: stack[2].ikey++ ← 같은 페이지 내 이동 (I/O 없음) ELSE: stack[1].ikey++ ← 부모로 올라감 stack[2] = 새 리프의 첫 키 ← 새 페이지 로드 (I/O 1회) SKIP -1 (hb_ntxTagPrevKey): IF stack[2].ikey > 0: stack[2].ikey-- ← 같은 페이지 내 이동 ELSE: stack[1].ikey-- ← 부모로 올라감 stack[2] = 새 리프의 마지막 키 ``` ### SOFTSEEK / FOUND 판정 로직 ``` hb_ntxTagKeyFind() 결과: ├── TRUE (정확한 키 일치) │ ├── GOTO(recNo) │ ├── SKIPFILTER (SET DELETED, SET FILTER 적용) │ ├── 필터 후에도 키 일치 확인 │ │ ├── 일치 → fFound = TRUE │ │ └── 불일치 (필터가 다른 레코드로 이동) │ │ ├── SOFTSEEK ON → fFound = FALSE, 현재 위치 유지 │ │ └── SOFTSEEK OFF → GOTO 0 (EOF) │ └── RETURN │ └── FALSE (키 불일치, 다음 높은 키에 위치) ├── SOFTSEEK ON │ ├── 스코프 범위 내 → fFound = FALSE, 현재 위치 유지 (다음 키) │ └── 스코프 범위 밖 → GOTO 0 (EOF) └── SOFTSEEK OFF └── GOTO 0 (EOF), fFound = FALSE ``` ### 성능 특성 ``` 인덱스 100만 키, 페이지당 50키 기준: SEEK: ~4 페이지 로드 (log₅₀(1,000,000) ≈ 3.5) SEEK+FILTER: +필터 평가 비용 (레코드별) FINDLAST: +M번 역방향 이동 (M = 일치 키 수) SKIP (순차): 같은 페이지면 I/O 없음, 페이지 넘어가면 1회 I/O SKIP N: O(N × 페이지당 비용) GO TOP: 루트→리프 좌측 최하단 = SEEK과 동일 비용 GO BOTTOM: 루트→리프 우측 최하단 = SEEK과 동일 비용 ``` --- ## 7. Harbour vs Go 대응 요약 ``` Harbour Go ────────── ──────── RDDFUNCS (101 function pointers) 여러 interface 조합 hb_rddInheritEx (memcpy + override) struct 임베딩 SELF_* macro (vtable dispatch) interface method call SUPER_* macro (saved parent table) embedded struct method call AREAP->lprfsHost interface 타입 assertion hb_rddRegister("DBF") RegisterDriver(&DBFDriver{}) NULL entry = inherit 임베딩된 메서드 = 자동 상속 static RDDFUNCS dbfSuper Go 임베딩이 자동 처리 ``` ### 핵심 차이: Go가 더 나은 점 ``` 1. NULL 메서드 불필요 — 임베딩이 자동 상속 2. 타입 안전 — interface assertion으로 기능 확인 3. 작은 interface — 필요한 것만 구현 (Indexer 없이 DBF만 가능) 4. 테스트 용이 — mock interface 쉬움 5. 새 드라이버 작성 쉬움 — 101개 아닌 필요한 interface만 구현 ``` --- ## 변경 이력 | 날짜 | 변경 내용 | |------|----------| | 2026-03-28 | 초기 작성. Harbour RDD 101-method vtable 분석, Go interface 재설계 |