go-irisnative
A Golang driver for InterSystems IRIS that implements database/sql
.
Project status: alpha. API may change. Feedback and PRs welcome.
Installation
# replace the module path with the final repo path when published
go get github.com/caretdev/go-irisnative
Register 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?
host
— IRIS hostname or IP
1972
— superserver port (default)
Namespace
— IRIS namespace (e.g., USER
)
Quick start (database/sql)
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/caretdev/go-irisnative"
)
func main() {
dsn := "iris://_SYSTEM:SYS@localhost:1972/USER"
db, err := sql.Open("iris", dsn)
if err != nil { log.Fatal(err) }
defer db.Close()
// Connection pool tuning
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(30 * time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err = db.ExecContext(ctx, `DROP TABLE IF EXISTS demo_person`)
if err != 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
)`)
if err != nil { log.Fatal("create table:", err) }
// 2) Insert with placeholders
res, err := db.ExecContext(ctx, `INSERT INTO demo_person(id, name) VALUES(?, ?)`, 1, "Alice")
if err != nil { log.Fatal("insert:", err) }
if n, _ := res.RowsAffected(); n > 0 { fmt.Println("inserted:", n) }
// 3) Query rows
rows, err := db.QueryContext(ctx, `SELECT id, name, created_at FROM demo_person ORDER BY id`)
if err != nil { log.Fatal("query:", err) }
defer rows.Close()
for rows.Next() {
var (
id int
name string
createdAt time.Time
)
if err := 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))
}
if err := rows.Err(); err != nil { log.Fatal(err) }
// 4) Prepared statement
stmt, err := db.PrepareContext(ctx, `UPDATE demo_person SET name=? WHERE id=?`)
if err != nil { log.Fatal("prepare:", err) }
defer stmt.Close()
if _, err := stmt.ExecContext(ctx, "Alice Updated", 1); err != nil { log.Fatal("update:", err) }
// 5) Transaction example
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err != 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)
}
if err := tx.Commit(); err != nil { log.Fatal("commit:", err) }
}
Query single value helper
var count int
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM demo_person`).Scan(&count); err != nil {
log.Fatal(err)
}
fmt.Println("count=", count)
Using with sqlx
sqlx
adds nice helpers over database/sql
like struct scanning and named queries.
go get github.com/jmoiron/sqlx
package main
import (
"context"
"fmt"
"log"
"time"
_ "github.com/caretdev/go-irisnative" // driver
"github.com/jmoiron/sqlx"
)
type Person struct {
ID int `db:"id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
}
func create(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
)`)
if err != nil {
panic(err)
}
}
func drop(ctx context.Context, db *sqlx.DB) {
_, err := db.ExecContext(ctx, `DROP TABLE IF EXISTS demo_person`)
if err != nil {
panic(err)
}
}
func main() {
ctx := context.Background()
dsn := "iris://_SYSTEM:SYS@localhost:1972/USER"
db := sqlx.MustConnect("iris", dsn)
defer db.Close()
create(ctx, db)
defer drop(ctx, db)
// Struct-based insert with NamedExec
p := Person{ID: 3, Name: "Carol"}
_, err := db.NamedExecContext(ctx,
`INSERT INTO demo_person(id, name) VALUES(:id, :name)`, p,
)
if err != nil {
log.Fatal("named insert:", err)
}
// Select into slice of structs
var people []Person
if err := 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 struct
var one Person
if err := 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)
if err != nil {
log.Fatal(err)
}
q = db.Rebind(q) // ensure driver-specific bindvars
rows, err := db.QueryxContext(ctx, q, args...)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Println(id, name)
}
}
Placeholders & rebind
- The driver uses
?
positional placeholders.
- With
sqlx
, always call db.Rebind(q)
after sqlx.In(...)
to adapt placeholders.
Context, timeouts & cancellations
All examples use Context
. Set sensible timeouts to avoid runaway queries:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
Error handling tips
- Check
rows.Err()
after iteration.
- Prefer
ExecContext
/QueryContext
to ensure timeouts are respected.
- Wrap errors with operation context (e.g.,
fmt.Errorf("create table: %w", err)
).
Testing locally
- 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.
Compatibility
- Go: 1.21+
- InterSystems IRIS: 2025.1+
License
MIT
Contributing
- Run
go vet
and go test ./...
before submitting PRs.
- Add tests for new behaviors.
- Document any DSN parameters you introduce.