// 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