Files
five/docs/rdd-architecture-spec.md
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

593 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 재설계 |