- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
17 KiB
17 KiB
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 예시)
// 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 설계
// 계층 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 대응)
// 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 임베딩 메서드 호출
// 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 대응)
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 의사코드 (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 의사코드 (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 재설계 |