Files
five/examples/go_sql_direct.prg
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

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