// Five Example: SQLite Database with Go's database/sql // // Harbour's xBase syntax + Go's SQL ecosystem = modern database apps. // Traditional Harbour: limited to DBF/NTX/CDX // Five: any database Go supports (SQLite, PostgreSQL, MySQL, etc.) PROCEDURE Main() LOCAL aRows, aSummary, aSearch, i ? "=== Five + SQLite Demo ===" ? GoDbOpen(":memory:") GoDbExec("CREATE TABLE customers (" + ; " id INTEGER PRIMARY KEY AUTOINCREMENT," + ; " name TEXT NOT NULL," + ; " city TEXT," + ; " balance REAL DEFAULT 0" + ; ")") ? "Inserting records..." GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Charles Kwon', 'Seoul', 15000.50)") GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('John Smith', 'New York', 8200.00)") GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Maria Garcia', 'Madrid', 12300.75)") GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Yuki Tanaka', 'Tokyo', 9800.25)") GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Hans Mueller', 'Berlin', 6500.00)") ? "5 records inserted." ? aRows := GoDbQuery("SELECT * FROM customers ORDER BY balance DESC") ? "All customers (sorted by balance):" ? 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 ? aSummary := GoDbQuery("SELECT COUNT(*) as cnt, SUM(balance) as total, AVG(balance) as avg FROM customers") ? "Summary:" ? " Count: ", aSummary[1]["cnt"] ? " Total: ", aSummary[1]["total"] ? " Average:", aSummary[1]["avg"] ? aSearch := GoDbQueryP("SELECT name, city FROM customers WHERE balance > ?", 10000) ? "Customers with balance > 10000:" FOR i := 1 TO Len(aSearch) ? " ", aSearch[i]["name"], "-", aSearch[i]["city"] NEXT GoDbClose() ? ? "Done." RETURN #pragma BEGINDUMP import ( "database/sql" "five/hbrt" "fmt" _ "modernc.org/sqlite" ) var db *sql.DB func init() { hbrt.HB_FUNC("GODBOPEN", goDbOpen) hbrt.HB_FUNC("GODBCLOSE", goDbClose) hbrt.HB_FUNC("GODBEXEC", goDbExec) hbrt.HB_FUNC("GODBQUERY", goDbQuery) hbrt.HB_FUNC("GODBQUERYP", goDbQueryP) } func goDbOpen(ctx *hbrt.HBContext) { dsn := ctx.ParC(1) if dsn == "" { dsn = ":memory:" } var err error db, err = sql.Open("sqlite", dsn) if err != nil { ctx.RetL(false) return } ctx.RetL(true) } func goDbClose(ctx *hbrt.HBContext) { if db != nil { db.Close() db = nil } ctx.RetNil() } func goDbExec(ctx *hbrt.HBContext) { sqlStr := ctx.ParC(1) if db == nil || sqlStr == "" { ctx.RetL(false) return } _, err := db.Exec(sqlStr) if err != nil { fmt.Printf("SQL Error: %v\n", err) ctx.RetL(false) return } ctx.RetL(true) } func goDbQuery(ctx *hbrt.HBContext) { sqlStr := ctx.ParC(1) if db == nil || sqlStr == "" { ctx.RetArray(nil) return } rows, err := db.Query(sqlStr) if err != nil { fmt.Printf("SQL Error: %v\n", err) ctx.RetArray(nil) return } defer rows.Close() ctx.RetVal(rowsToHarbour(ctx, rows)) } func goDbQueryP(ctx *hbrt.HBContext) { sqlStr := ctx.ParC(1) if db == nil || sqlStr == "" { ctx.RetArray(nil) return } var args []interface{} for i := 2; 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(sqlStr, args...) if err != nil { fmt.Printf("SQL Error: %v\n", err) ctx.RetArray(nil) return } defer rows.Close() ctx.RetVal(rowsToHarbour(ctx, rows)) } func rowsToHarbour(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 hbrt.MakeArrayFrom(result) } #pragma ENDDUMP