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?
host— IRIS hostname or IP1972— superserver port (default)Namespace— IRIS namespace (e.g.,USER)
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) }
}var count int
if err := 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"
)
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)
}
}- 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)
defer cancel()- 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.