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

17 KiB
Raw Blame History

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 재설계