- 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>
214 lines
5.4 KiB
Plaintext
214 lines
5.4 KiB
Plaintext
// Five Example: Direct Go SQL — the simplest possible way
|
|
//
|
|
// #pragma BEGINDUMP registers Go functions via HB_FUNC.
|
|
// PRG calls them like regular Harbour functions.
|
|
// Go objects flow as Harbour values — : for methods.
|
|
//
|
|
// Pattern: IMPORT declares Go packages
|
|
// HB_FUNC bridges Go → Harbour
|
|
// PRG code stays clean xBase style
|
|
|
|
IMPORT "database/sql"
|
|
IMPORT _ "modernc.org/sqlite"
|
|
|
|
PROCEDURE Main()
|
|
LOCAL db, aRows, aSum, i
|
|
|
|
? "=== Five SQL Demo ==="
|
|
?
|
|
|
|
db := SqlOpen("sqlite", ":memory:")
|
|
IF db == NIL
|
|
? "Failed to open database"
|
|
RETURN
|
|
ENDIF
|
|
|
|
SqlExec(db, "CREATE TABLE customers (" + ;
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT," + ;
|
|
" name TEXT NOT NULL," + ;
|
|
" city TEXT," + ;
|
|
" balance REAL DEFAULT 0)")
|
|
|
|
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Charles Kwon', 'Seoul', 15000.50)")
|
|
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('John Smith', 'New York', 8200.00)")
|
|
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Maria Garcia', 'Madrid', 12300.75)")
|
|
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Yuki Tanaka', 'Tokyo', 9800.25)")
|
|
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Hans Mueller', 'Berlin', 6500.00)")
|
|
? "5 records inserted."
|
|
?
|
|
|
|
aRows := SqlQuery(db, "SELECT * FROM customers ORDER BY balance DESC")
|
|
? PadR("ID", 4), PadR("Name", 20), PadR("City", 15), "Balance"
|
|
? Replicate("-", 55)
|
|
FOR i := 1 TO Len(aRows)
|
|
? PadR(aRows[i]["id"], 4), ;
|
|
PadR(aRows[i]["name"], 20), ;
|
|
PadR(aRows[i]["city"], 15), ;
|
|
aRows[i]["balance"]
|
|
NEXT
|
|
?
|
|
|
|
aSum := SqlQuery(db, "SELECT COUNT(*) as cnt, SUM(balance) as total, AVG(balance) as avg FROM customers")
|
|
? "Count:", aSum[1]["cnt"], " Total:", aSum[1]["total"], " Avg:", aSum[1]["avg"]
|
|
?
|
|
|
|
aRows := SqlQueryP(db, "SELECT name, city FROM customers WHERE balance > ?", 10000)
|
|
? "Balance > 10000:"
|
|
FOR i := 1 TO Len(aRows)
|
|
? " ", aRows[i]["name"], "-", aRows[i]["city"]
|
|
NEXT
|
|
|
|
SqlClose(db)
|
|
? "Done."
|
|
|
|
RETURN
|
|
|
|
#pragma BEGINDUMP
|
|
|
|
import (
|
|
"database/sql"
|
|
"five/hbrt"
|
|
"fmt"
|
|
)
|
|
|
|
func init() {
|
|
hbrt.HB_FUNC("SQLOPEN", sqlOpen)
|
|
hbrt.HB_FUNC("SQLCLOSE", sqlClose)
|
|
hbrt.HB_FUNC("SQLEXEC", sqlExec)
|
|
hbrt.HB_FUNC("SQLQUERY", sqlQuery)
|
|
hbrt.HB_FUNC("SQLQUERYP", sqlQueryP)
|
|
}
|
|
|
|
// SqlOpen(cDriver, cDSN) → oDb or NIL
|
|
func sqlOpen(ctx *hbrt.HBContext) {
|
|
driver := ctx.ParC(1)
|
|
dsn := ctx.ParC(2)
|
|
db, err := sql.Open(driver, dsn)
|
|
if err != nil {
|
|
ctx.RetNil()
|
|
return
|
|
}
|
|
if err = db.Ping(); err != nil {
|
|
ctx.RetNil()
|
|
return
|
|
}
|
|
ctx.RetVal(hbrt.WrapGo(db))
|
|
}
|
|
|
|
// SqlClose(oDb)
|
|
func sqlClose(ctx *hbrt.HBContext) {
|
|
if db := getDB(ctx, 1); db != nil {
|
|
db.Close()
|
|
}
|
|
ctx.RetNil()
|
|
}
|
|
|
|
// SqlExec(oDb, cSQL) → lSuccess
|
|
func sqlExec(ctx *hbrt.HBContext) {
|
|
db := getDB(ctx, 1)
|
|
if db == nil {
|
|
ctx.RetL(false)
|
|
return
|
|
}
|
|
_, err := db.Exec(ctx.ParC(2))
|
|
if err != nil {
|
|
fmt.Printf("SQL Error: %v\n", err)
|
|
ctx.RetL(false)
|
|
return
|
|
}
|
|
ctx.RetL(true)
|
|
}
|
|
|
|
// SqlQuery(oDb, cSQL) → aRows (array of hashes)
|
|
func sqlQuery(ctx *hbrt.HBContext) {
|
|
db := getDB(ctx, 1)
|
|
if db == nil {
|
|
ctx.RetArray(nil)
|
|
return
|
|
}
|
|
rows, err := db.Query(ctx.ParC(2))
|
|
if err != nil {
|
|
fmt.Printf("SQL Error: %v\n", err)
|
|
ctx.RetArray(nil)
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
ctx.RetArray(scanRows(ctx, rows))
|
|
}
|
|
|
|
// SqlQueryP(oDb, cSQL, xParam1, ...) → aRows with parameters
|
|
func sqlQueryP(ctx *hbrt.HBContext) {
|
|
db := getDB(ctx, 1)
|
|
if db == nil {
|
|
ctx.RetArray(nil)
|
|
return
|
|
}
|
|
var args []interface{}
|
|
for i := 3; i <= ctx.PCount(); i++ {
|
|
v := ctx.Param(i)
|
|
switch {
|
|
case v.IsString():
|
|
args = append(args, v.AsString())
|
|
case v.IsNumeric():
|
|
args = append(args, v.AsNumDouble())
|
|
case v.IsLogical():
|
|
args = append(args, v.AsBool())
|
|
default:
|
|
args = append(args, nil)
|
|
}
|
|
}
|
|
rows, err := db.Query(ctx.ParC(2), args...)
|
|
if err != nil {
|
|
fmt.Printf("SQL Error: %v\n", err)
|
|
ctx.RetArray(nil)
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
ctx.RetArray(scanRows(ctx, rows))
|
|
}
|
|
|
|
// --- internal helpers ---
|
|
|
|
func getDB(ctx *hbrt.HBContext, n int) *sql.DB {
|
|
obj := hbrt.UnwrapGo(ctx.Param(n))
|
|
db, _ := obj.(*sql.DB)
|
|
return db
|
|
}
|
|
|
|
func scanRows(ctx *hbrt.HBContext, rows *sql.Rows) []hbrt.Value {
|
|
cols, _ := rows.Columns()
|
|
var result []hbrt.Value
|
|
for rows.Next() {
|
|
values := make([]interface{}, len(cols))
|
|
ptrs := make([]interface{}, len(cols))
|
|
for i := range values {
|
|
ptrs[i] = &values[i]
|
|
}
|
|
rows.Scan(ptrs...)
|
|
hash := ctx.HashNew()
|
|
for i, col := range cols {
|
|
key := hbrt.MakeString(col)
|
|
var val hbrt.Value
|
|
switch v := values[i].(type) {
|
|
case int64:
|
|
val = hbrt.MakeInt(int(v))
|
|
case float64:
|
|
val = hbrt.MakeDouble(v, 0, 0)
|
|
case string:
|
|
val = hbrt.MakeString(v)
|
|
case []byte:
|
|
val = hbrt.MakeString(string(v))
|
|
case bool:
|
|
val = hbrt.MakeBool(v)
|
|
default:
|
|
val = hbrt.MakeNil()
|
|
}
|
|
ctx.HashAdd(hash, key, val)
|
|
}
|
|
result = append(result, hash)
|
|
}
|
|
return result
|
|
}
|
|
|
|
#pragma ENDDUMP
|