A Golang driver for InterSystems IRIS that implements database/sql.
Project status: alpha. API may change. Feedback and PRs welcome.
This project does not contain or distribute any proprietary source code from InterSystems. All observed behaviors or configuration techniques are derived from publicly available information, empirical testing, and independent analysis intended solely to improve interoperability within the IRIS ecosystem.
# replace the module path with the final repo path when published
go get github.com/caretdev/go-irisnativeRegister the driver by importing it for side‑effects:
import (
"database/sql"
_ "github.com/caretdev/go-irisnative"// registers driver as "iris"
)The driver accepts a URL-style DSN (recommended) or key=value pairs.
URL style
iris://user:password@host:1972/NAMESPACE?max_rows=200&query_timeout=30
host— IRIS hostname or IP1972— superserver port (default)Namespace— IRIS namespace (e.g.,USER)
DSN parameters
max_rows— Maximum number of rows to fetch in a single request (default: 0 = no limit)query_timeout— Query timeout in seconds (default: 0 = no timeout)
package main
import (
"context""database/sql""fmt""log""time"
_ "github.com/caretdev/go-irisnative"
)
funcmain() {
dsn:="iris://_SYSTEM:SYS@localhost:1972/USER"db, err:=sql.Open("iris", dsn)
iferr!=nil { log.Fatal(err) }
deferdb.Close()
// Connection pool tuningdb.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(30*time.Minute)
ctx, cancel:=context.WithTimeout(context.Background(), 10*time.Second)
defercancel()
_, err=db.ExecContext(ctx, `DROP TABLE IF EXISTS demo_person`)
iferr!=nil { log.Fatal("drop table:", err) }
// 1) Create a table (id INT PRIMARY KEY, name VARCHAR(80))_, err=db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS demo_person ( id INT PRIMARY KEY, name VARCHAR(80) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )`)
iferr!=nil { log.Fatal("create table:", err) }
// 2) Insert with placeholdersres, err:=db.ExecContext(ctx, `INSERT INTO demo_person(id, name) VALUES(?, ?)`, 1, "Alice")
iferr!=nil { log.Fatal("insert:", err) }
ifn, _:=res.RowsAffected(); n>0 { fmt.Println("inserted:", n) }
// 3) Query rowsrows, err:=db.QueryContext(ctx, `SELECT id, name, created_at FROM demo_person ORDER BY id`)
iferr!=nil { log.Fatal("query:", err) }
deferrows.Close()
forrows.Next() {
var (
idintnamestringcreatedAt time.Time
)
iferr:=rows.Scan(&id, &name, &createdAt); err!=nil { log.Fatal(err) }
fmt.Printf("row: id=%d name=%s created_at=%s\n", id, name, createdAt.Format(time.RFC3339))
}
iferr:=rows.Err(); err!=nil { log.Fatal(err) }
// 4) Prepared statementstmt, err:=db.PrepareContext(ctx, `UPDATE demo_person SET name=? WHERE id=?`)
iferr!=nil { log.Fatal("prepare:", err) }
deferstmt.Close()
if_, err:=stmt.ExecContext(ctx, "Alice Updated", 1); err!=nil { log.Fatal("update:", err) }
// 5) Transaction exampletx, err:=db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
iferr!=nil { log.Fatal("begin tx:", err) }
if_, err:=tx.ExecContext(ctx, `INSERT INTO demo_person(id, name) VALUES(?, ?)`, 2, "Bob"); err!=nil {
tx.Rollback()
log.Fatal("tx insert:", err)
}
iferr:=tx.Commit(); err!=nil { log.Fatal("commit:", err) }
}varcountintiferr:=db.QueryRowContext(ctx, `SELECT COUNT(*) FROM demo_person`).Scan(&count); err!=nil {
log.Fatal(err)
}
fmt.Println("count=", count)sqlx adds nice helpers over database/sql like struct scanning and named queries.
go get github.com/jmoiron/sqlxpackage main
import (
"context""fmt""log""time"
_ "github.com/caretdev/go-irisnative"// driver"github.com/jmoiron/sqlx"
)
typePersonstruct {
IDint`db:"id"`Namestring`db:"name"`CreatedAt time.Time`db:"created_at"`
}
funccreate(ctx context.Context, db*sqlx.DB) {
drop(ctx, db)
_, err:=db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS demo_person ( id INT PRIMARY KEY, name VARCHAR(80) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )`)
iferr!=nil {
panic(err)
}
}
funcdrop(ctx context.Context, db*sqlx.DB) {
_, err:=db.ExecContext(ctx, `DROP TABLE IF EXISTS demo_person`)
iferr!=nil {
panic(err)
}
}
funcmain() {
ctx:=context.Background()
dsn:="iris://_SYSTEM:SYS@localhost:1972/USER"db:=sqlx.MustConnect("iris", dsn)
deferdb.Close()
create(ctx, db)
deferdrop(ctx, db)
// Struct-based insert with NamedExecp:=Person{ID: 3, Name: "Carol"}
_, err:=db.NamedExecContext(ctx,
`INSERT INTO demo_person(id, name) VALUES(:id, :name)`, p,
)
iferr!=nil {
log.Fatal("named insert:", err)
}
// Select into slice of structsvarpeople []Personiferr:=db.SelectContext(ctx, &people, `SELECT id, name, created_at FROM demo_person ORDER BY id`); err!=nil {
log.Fatal(err)
}
fmt.Printf("people: %#v\n", people)
// Get a single structvaronePersoniferr:=db.GetContext(ctx, &one, `SELECT id, name, created_at FROM demo_person WHERE id=?`, people[0].ID); err!=nil {
log.Fatal(err)
}
fmt.Printf("one: %+v\n", one)
// Named query with IN (sqlx.In)ids:= []int{1, 2, 3}
q, args, err:=sqlx.In(`SELECT id, name FROM demo_person WHERE id IN (?)`, ids)
iferr!=nil {
log.Fatal(err)
}
q=db.Rebind(q) // ensure driver-specific bindvarsrows, err:=db.QueryxContext(ctx, q, args...)
iferr!=nil {
log.Fatal(err)
}
deferrows.Close()
forrows.Next() {
varidintvarnamestringiferr:=rows.Scan(&id, &name); err!=nil {
log.Fatal(err)
}
fmt.Println(id, name)
}
}- The driver uses
?positional placeholders. - With
sqlx, always calldb.Rebind(q)aftersqlx.In(...)to adapt placeholders.
All examples use Context. Set sensible timeouts to avoid runaway queries:
ctx, cancel:=context.WithTimeout(context.Background(), 5*time.Second)
defercancel()- Check
rows.Err()after iteration. - Prefer
ExecContext/QueryContextto ensure timeouts are respected. - Wrap errors with operation context (e.g.,
fmt.Errorf("create table: %w", err)).
- Start IRIS and ensure SQL is enabled for your namespace (e.g.,
USER). - Create a SQL user with privileges to connect and create tables.
- Verify connectivity using the DSN shown above.
- Go: 1.21+
- InterSystems IRIS: 2025.1+
MIT
- Run
go vetandgo test ./...before submitting PRs. - Add tests for new behaviors.
- Document any DSN parameters you introduce.