- 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>
205 lines
4.6 KiB
Plaintext
205 lines
4.6 KiB
Plaintext
// 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
|