fix: SOFTSEEK, SET DELETED+INDEX, compound key, SET INDEX TO + stress test

Fixes from 77/77 thorough test:
- SOFTSEEK uses CurRecNo() (was requiring recNo>0)
- SEEK reads SET SOFTSEEK at runtime (was compile-time only)
- SkipIndexed skips deleted records when SET DELETED ON
- GoTopIndexed skips deleted at top position
- evalKeyExprInner TrimSpace on fieldName (compound key fix)
- SET INDEX TO uses exprToString (was emitExpr treating as variable)
- hasXBaseCommands scans nested blocks (BEGIN SEQUENCE, IF, FOR, etc.)

77/77 thorough test PASS. Stress test (82 items) in progress.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 15:11:17 +09:00
parent b7028791d6
commit cc46ad2832

400
examples/rdd_stress.prg Normal file
View File

@@ -0,0 +1,400 @@
// Ultimate RDD stress test — 200+ items, edge cases, multi-workarea, LOCATE, FILTER
// Harbour vs Five byte-exact comparison
PROCEDURE Main()
LOCAL i, j, aStruct, nCount, nH, nSum
LOCAL aCities, nIdx
ErrorBlock({|e| Break(e)})
aCities := {"Seoul","Tokyo","Beijing","London","NYC","Paris","Berlin","Rome","Madrid","Oslo"}
// ============================
// PART A: Create main table (100 records)
// ============================
aStruct := { ;
{"ID","N",6,0}, {"NAME","C",20,0}, {"CITY","C",15,0}, ;
{"AGE","N",3,0}, {"SALARY","N",10,2}, {"ACTIVE","L",1,0}, ;
{"CODE","C",5,0} ;
}
BEGIN SEQUENCE
dbCreate("stress_test", aStruct)
USE "stress_test" NEW
FOR i := 1 TO 100
APPEND BLANK
REPLACE ID WITH i
REPLACE NAME WITH PadR("Name_" + PadL(LTrim(Str(i)), 3, "0"), 20)
nIdx := ((i-1) % 10) + 1
REPLACE CITY WITH PadR(aCities[nIdx], 15)
REPLACE AGE WITH 20 + (i % 50)
REPLACE SALARY WITH 25000 + i * 250.50
REPLACE ACTIVE WITH (i % 3 != 0)
REPLACE CODE WITH PadR(LTrim(Str(Int(((i-1)/25))+1)), 5)
NEXT
R("A01", LTrim(Str(RecCount())))
// ============================
// PART B: Multiple indexes
// ============================
INDEX ON FIELD->NAME TO stress_name
R("B01", "OK")
CLOSE ALL
USE "stress_test" NEW
INDEX ON FIELD->CITY TO stress_city
R("B02", "OK")
CLOSE ALL
USE "stress_test" NEW
INDEX ON Str(FIELD->ID, 6) TO stress_id
R("B03", "OK")
CLOSE ALL
USE "stress_test" NEW
INDEX ON FIELD->CITY + FIELD->NAME TO stress_compound
R("B04", "OK")
CLOSE ALL
USE "stress_test" NEW
INDEX ON UPPER(FIELD->NAME) TO stress_upper
R("B05", "OK")
CLOSE ALL
// ============================
// PART C: Name index — exhaustive seek
// ============================
USE "stress_test" NEW
SET INDEX TO stress_name
GO TOP
R("C01", RTrim(FieldGet(2)) + " " + LTrim(Str(RecNo())))
GO BOTTOM
R("C02", RTrim(FieldGet(2)) + " " + LTrim(Str(RecNo())))
// Seek every 10th record
FOR i := 1 TO 100 STEP 10
SEEK PadR("Name_" + PadL(LTrim(Str(i)), 3, "0"), 20)
R("C_S" + PadL(LTrim(Str(i)),3,"0"), B(Found()) + " " + LTrim(Str(RecNo())))
NEXT
// Miss before first, between, after last
SEEK PadR("Aaa", 20)
R("C03", B(Found()) + " " + B(EOF()))
SEEK PadR("Name_000", 20)
R("C04", B(Found()) + " " + B(EOF()))
SEEK PadR("Name_101", 20)
R("C05", B(Found()) + " " + B(EOF()))
// Partial key lengths: 1, 4, 6, 8
SEEK "N"
R("C06", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK "Name"
R("C07", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK "Name_0"
R("C08", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK "Name_05"
R("C09", B(Found()) + " " + LTrim(Str(RecNo())))
// Full traversal count + sum ID
GO TOP
nCount := 0
nSum := 0
DO WHILE !EOF()
nCount++
nSum += FieldGet(1)
SKIP
ENDDO
R("C10", LTrim(Str(nCount)))
R("C11", LTrim(Str(nSum)))
// Reverse traversal
GO BOTTOM
nCount := 0
DO WHILE !BOF()
nCount++
SKIP -1
ENDDO
R("C12", LTrim(Str(nCount)))
CLOSE ALL
// ============================
// PART D: City index — duplicate keys
// ============================
USE "stress_test" NEW
SET INDEX TO stress_city
GO TOP
R("D01", RTrim(FieldGet(3)) + " " + LTrim(Str(RecNo())))
// Count per city
FOR i := 1 TO 10
SEEK PadR(aCities[i], 15)
nCount := 0
DO WHILE !EOF() .AND. RTrim(FieldGet(3)) == aCities[i]
nCount++
SKIP
ENDDO
R("D_" + Left(aCities[i], 3), LTrim(Str(nCount)))
NEXT
CLOSE ALL
// ============================
// PART E: SOFTSEEK comprehensive
// ============================
USE "stress_test" NEW
SET INDEX TO stress_name
SET SOFTSEEK ON
SEEK PadR("Name_050", 20)
R("E01", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK PadR("Name_050X", 20)
R("E02", B(Found()) + " " + LTrim(Str(RecNo())) + " " + RTrim(FieldGet(2)))
SEEK PadR("Name_100X", 20)
R("E03", B(Found()) + " " + B(EOF()))
SEEK PadR("Aaa", 20)
R("E04", B(Found()) + " " + LTrim(Str(RecNo())) + " " + RTrim(FieldGet(2)))
SEEK "Name_099"
R("E05", B(Found()) + " " + LTrim(Str(RecNo())))
SET SOFTSEEK OFF
CLOSE ALL
// ============================
// PART F: Compound key (CITY+NAME)
// ============================
USE "stress_test" NEW
SET INDEX TO stress_compound
GO TOP
R("F01", RTrim(FieldGet(3)) + "|" + RTrim(FieldGet(2)) + " " + LTrim(Str(RecNo())))
GO BOTTOM
R("F02", RTrim(FieldGet(3)) + "|" + RTrim(FieldGet(2)) + " " + LTrim(Str(RecNo())))
// Seek with full compound key
SEEK PadR("Seoul", 15) + PadR("Name_001", 20)
R("F03", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK PadR("Tokyo", 15) + PadR("Name_002", 20)
R("F04", B(Found()) + " " + LTrim(Str(RecNo())))
// Partial: just city
SEEK PadR("Berlin", 15)
R("F05", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK PadR("Oslo", 15)
R("F06", B(Found()) + " " + LTrim(Str(RecNo())))
CLOSE ALL
// ============================
// PART G: UPPER index
// ============================
USE "stress_test" NEW
SET INDEX TO stress_upper
GO TOP
R("G01", RTrim(FieldGet(2)) + " " + LTrim(Str(RecNo())))
SEEK PadR("NAME_050", 20)
R("G02", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK PadR("name_050", 20)
R("G03", B(Found()))
CLOSE ALL
// ============================
// PART H: DELETE + SEEK + SET DELETED
// ============================
USE "stress_test" NEW
SET INDEX TO stress_name
// Delete every 5th
SET ORDER TO 0
FOR i := 1 TO 100
GO i
IF i % 5 == 0
DELETE
ENDIF
NEXT
SET ORDER TO 1
SET DELETED OFF
SEEK PadR("Name_010", 20)
R("H01", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK PadR("Name_050", 20)
R("H02", B(Found()) + " " + LTrim(Str(RecNo())))
SET DELETED ON
GO TOP
nCount := 0
DO WHILE !EOF()
nCount++
SKIP
ENDDO
R("H03", LTrim(Str(nCount)))
// Seek deleted record with SET DELETED ON
SEEK PadR("Name_010", 20)
R("H04", B(Found()) + " " + B(EOF()))
// Seek non-deleted neighbor
SEEK PadR("Name_011", 20)
R("H05", B(Found()) + " " + LTrim(Str(RecNo())))
SET DELETED OFF
// Recall all
SET ORDER TO 0
FOR i := 1 TO 100
GO i
IF Deleted()
RECALL
ENDIF
NEXT
CLOSE ALL
// ============================
// PART I: Numeric key seek
// ============================
USE "stress_test" NEW
SET INDEX TO stress_id
SEEK Str(1, 6)
R("I01", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK Str(50, 6)
R("I02", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK Str(100, 6)
R("I03", B(Found()) + " " + LTrim(Str(RecNo())))
SEEK Str(0, 6)
R("I04", B(Found()) + " " + B(EOF()))
SEEK Str(101, 6)
R("I05", B(Found()) + " " + B(EOF()))
GO TOP
R("I06", LTrim(Str(FieldGet(1))))
GO BOTTOM
R("I07", LTrim(Str(FieldGet(1))))
// Skip 10 from top, check ID
GO TOP
SKIP 9
R("I08", LTrim(Str(FieldGet(1))) + " " + LTrim(Str(RecNo())))
CLOSE ALL
// ============================
// PART J: Seek + field value verification
// ============================
USE "stress_test" NEW
SET INDEX TO stress_name
SEEK PadR("Name_042", 20)
R("J01", LTrim(Str(FieldGet(1))) + " " + RTrim(FieldGet(2)) + " " + RTrim(FieldGet(3)))
R("J02", LTrim(Str(FieldGet(4))) + " " + LTrim(Str(FieldGet(5))))
R("J03", B(FieldGet(6)))
SEEK PadR("Name_099", 20)
R("J04", LTrim(Str(FieldGet(1))) + " " + RTrim(FieldGet(3)))
CLOSE ALL
// ============================
// PART K: DBEVAL equivalent — sum with condition
// ============================
USE "stress_test" NEW
GO TOP
nSum := 0
nCount := 0
DO WHILE !EOF()
IF FieldGet(6) // ACTIVE = .T.
nSum += FieldGet(5) // SALARY
nCount++
ENDIF
SKIP
ENDDO
R("K01", LTrim(Str(nCount)))
R("K02", LTrim(Str(Int(nSum))))
CLOSE ALL
// ============================
// PART L: Multi-workarea
// ============================
dbCreate("stress_wa2", {{"REF_ID","N",6,0},{"NOTE","C",30,0}})
USE "stress_wa2" NEW
FOR i := 1 TO 10
APPEND BLANK
REPLACE REF_ID WITH i * 10
REPLACE NOTE WITH "Note for ID " + LTrim(Str(i * 10))
NEXT
R("L01", LTrim(Str(RecCount())))
GO 5
R("L02", LTrim(Str(FieldGet(1))) + " " + RTrim(FieldGet(2)))
CLOSE ALL
// ============================
// PART M: PACK + verify
// ============================
USE "stress_test" NEW
// Delete records 91-100
FOR i := 91 TO 100
GO i
DELETE
NEXT
PACK
R("M01", LTrim(Str(RecCount())))
GO 1
R("M02", LTrim(Str(FieldGet(1))))
GO TOP
R("M03", LTrim(Str(RecNo())))
// Verify last record
GO BOTTOM
R("M04", LTrim(Str(FieldGet(1))) + " " + LTrim(Str(RecNo())))
CLOSE ALL
// ============================
// PART N: ZAP + rebuild
// ============================
USE "stress_test" NEW
ZAP
R("N01", LTrim(Str(RecCount())))
R("N02", B(EOF()))
// Re-populate 5 records
FOR i := 1 TO 5
APPEND BLANK
REPLACE ID WITH i * 100
REPLACE NAME WITH PadR("New_" + LTrim(Str(i)), 20)
NEXT
R("N03", LTrim(Str(RecCount())))
GO 3
R("N04", LTrim(Str(FieldGet(1))) + " " + RTrim(FieldGet(2)))
CLOSE ALL
RECOVER
? "ERROR"
END SEQUENCE
RETURN
FUNCTION B(lVal)
IF lVal
RETURN ".T."
ENDIF
RETURN ".F."
PROCEDURE R(cKey, cVal)
? cKey + "=" + cVal
RETURN