Pure Go database driver for CUBRID — database/sql interface + GORM dialector, no CGO required.
| cubrid-go | CCI (C interface) | |
|---|---|---|
| CGO Required | No — pure Go | Yes |
| Cross-compilation | GOOS=linux GOARCH=arm64 go build |
Requires C toolchain |
| database/sql | Native interface | Wrapper needed |
| GORM Support | Built-in dialector | Not available |
| Connection Pool | Go standard (database/sql) |
Manual management |
| Deployment | Single binary | Shared library dependency |
cubrid-go speaks the CUBRID CAS protocol directly over TCP. No shared libraries, no CGO, no external dependencies — just go get and start coding.
go get github.com/cubrid-labs/cubrid-goRequirements: Go 1.21+
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/cubrid-labs/cubrid-go"
)
func main() {
db, err := sql.Open("cubrid", "cubrid://dba:@localhost:33000/demodb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Verify connection
if err := db.Ping(); err != nil {
log.Fatal(err)
}
fmt.Println("Connected to CUBRID!")
// Query
rows, err := db.Query("SELECT name, nation_code FROM athlete WHERE nation_code = ?", "KOR")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name, nation string
rows.Scan(&name, &nation)
fmt.Printf("%s (%s)\n", name, nation)
}
}package main
import (
"fmt"
"log"
"gorm.io/gorm"
cubrid "github.com/cubrid-labs/cubrid-go/dialector"
)
type Athlete struct {
Code int `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"size:40;not null"`
NationCode string `gorm:"size:3"`
Gender string `gorm:"size:1"`
Event string `gorm:"size:40"`
}
func main() {
db, err := gorm.Open(cubrid.Open("cubrid://dba:@localhost:33000/demodb"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
// Auto-migrate creates the table if it doesn't exist
db.AutoMigrate(&Athlete{})
// Create
db.Create(&Athlete{Name: "Hong Gildong", NationCode: "KOR", Gender: "M", Event: "Marathon"})
// Read
var athletes []Athlete
db.Where("nation_code = ?", "KOR").Find(&athletes)
for _, a := range athletes {
fmt.Printf("%s - %s\n", a.Name, a.Event)
}
// Update
db.Model(&Athlete{}).Where("name = ?", "Hong Gildong").Update("event", "Sprint")
// Delete
db.Delete(&Athlete{}, "nation_code = ?", "ZZZ")
}cubrid://[user[:password]]@host[:port]/database[?autocommit=true&timeout=30s]
| Parameter | Default | Description |
|---|---|---|
host |
localhost |
CUBRID broker host |
port |
33000 |
CUBRID broker port |
database |
(required) | Target database name |
user |
"" |
Database user |
password |
"" |
Database password |
autocommit |
true |
Enable/disable auto-commit |
timeout |
30s |
Connection timeout (Go duration) |
Examples:
// Local development
"cubrid://dba:@localhost:33000/demodb"
// Remote with credentials
"cubrid://admin:[email protected]:33000/production"
// Manual transaction mode
"cubrid://dba:@localhost:33000/demodb?autocommit=false"
// Custom timeout
"cubrid://dba:@localhost:33000/demodb?timeout=60s"cubrid-go uses Go's standard database/sql pool, so the usual pool tuning APIs work as expected.
package main
import (
"context"
"database/sql"
"log"
"time"
_ "github.com/cubrid-labs/cubrid-go"
)
func main() {
db, err := sql.Open("cubrid", "cubrid://dba:@localhost:33000/demodb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Configure the shared connection pool.
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(1 * time.Minute)
// Verify that the broker is reachable before serving traffic.
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
log.Fatal(err)
}
}SetMaxOpenConns(25): caps the total number of open connections. Start with a conservative number and scale up only if your broker and workload need it.SetMaxIdleConns(5): keeps a small number of warm idle connections ready for bursts without holding on to the full pool.SetConnMaxLifetime(5 * time.Minute): rotates long-lived connections so stale sessions do not accumulate forever.SetConnMaxIdleTime(1 * time.Minute): closes connections that have been idle for too long, which is useful for spiky traffic.
For a typical web service, a good baseline is:
MaxOpenConns:10to25MaxIdleConns:2to5ConnMaxLifetime:5mto30mConnMaxIdleTime:1mto5m
Measure under production-like load and adjust from there. If requests begin queueing on the Go side, raise MaxOpenConns carefully. If the database is saturated, lower it.
- The pool limit should stay below the available CUBRID broker worker capacity. Setting
MaxOpenConnshigher than the broker can actually serve only shifts contention downstream. PingContext()is a good readiness or startup check because it fails fast when the broker is unavailable or the DSN is invalid.- If your deployment has multiple application instances, size the pool per instance, not just per service. For example,
25open connections across4replicas can become100broker sessions.
| Feature | Status | Notes |
|---|---|---|
| Pure TCP (no shared library) | ✅ | No CGO, no external deps |
database/sql driver |
✅ | Full interface |
Parameterized queries (?) |
✅ | Client-side interpolation |
Transactions (Begin/Commit/Rollback) |
✅ | |
| GORM dialector | ✅ | Models, migrations, CRUD |
| GORM AutoMigrate | ✅ | Create tables, add columns |
| Server-side cursor / lazy fetch | ✅ | Batches of 100 rows |
| Result streaming (FETCH) | ✅ | Memory-efficient |
| Last insert ID | ✅ | Via SELECT LAST_INSERT_ID() |
| Connection pool | ✅ | Go standard database/sql |
| Connection health check (Ping) | ✅ | Via GET_DB_VERSION |
| LOB (BLOB/CLOB) | Raw bytes only | |
| Timezone-aware types | UTC only | |
| ON CONFLICT / UPSERT | ❌ | Use raw SQL workaround |
| Go Type | CUBRID Literal | Example |
|---|---|---|
nil |
NULL |
|
bool |
0 / 1 |
CUBRID has no BOOLEAN |
int64 |
Integer | 42 |
float64 |
Float | 3.14 |
string |
'escaped' |
Quotes doubled |
[]byte |
X'cafe' |
Hex-encoded |
time.Time |
DATETIME'...' |
Millisecond precision |
| CUBRID Type | Go Type |
|---|---|
SMALLINT, INTEGER, BIGINT |
int64 |
FLOAT, DOUBLE, MONETARY |
float64 |
NUMERIC |
string |
CHAR, VARCHAR, STRING |
string |
DATE, TIME, DATETIME, TIMESTAMP |
time.Time (UTC) |
BIT, BIT VARYING, BLOB, CLOB |
[]byte |
SET, MULTISET, SEQUENCE |
string |
| GORM Field Type | CUBRID SQL Type |
|---|---|
Bool |
SMALLINT |
Int (≤16 bits) |
SMALLINT |
Int (≤32 bits) |
INTEGER |
Int (>32 bits) |
BIGINT |
Float (≤32 bits) |
FLOAT |
Float (>32 bits) |
DOUBLE |
String |
VARCHAR(n) (default 256) |
String (very large) |
STRING |
Time |
DATETIME |
Bytes |
BLOB |
| Auto-increment (≤32) | INTEGER AUTO_INCREMENT |
| Auto-increment (>32) | BIGINT AUTO_INCREMENT |
import "github.com/cubrid-labs/cubrid-go"
// Base error
var cubridErr *cubrid.CubridError
// Constraint violation (unique, foreign key)
var integrityErr *cubrid.IntegrityError
// SQL syntax error, missing table/column
var progErr *cubrid.ProgrammingError
// Network/connection failure
var opErr *cubrid.OperationalError
// Use errors.As for type checking
if errors.As(err, &integrityErr) {
fmt.Printf("Constraint violation: code=%d msg=%s\n", integrityErr.Code, integrityErr.Message)
}flowchart TD
A[Application] --> B[cubrid-go database/sql driver]
B --> C[CAS Protocol net.Conn]
C --> D[CUBRID Server]
flowchart TD
R[cubrid-go/] --> C1[constants.go\nCAS protocol constants function codes, data types]
R --> C2[packet.go\nPacketWriter / PacketReader big-endian binary codec]
R --> C3[protocol.go\nHigh-level packet builders and parsers]
R --> C4[errors.go\nCubridError, IntegrityError, ProgrammingError, OperationalError]
R --> C5[types.go\nFormatValue Go to CUBRID type conversion]
R --> C6[conn.go\nTCP connection + two-step broker handshake]
R --> C7[stmt.go\ndatabase/sql Stmt PrepareAndExecute FC=41]
R --> C8[rows.go\ndatabase/sql Rows lazy server-side cursor FC=8]
R --> C9[tx.go\ndatabase/sql Tx COMMIT / ROLLBACK FC=1]
R --> C10[driver.go\nDriver registration + DSN parser]
R --> D[dialector/]
D --> D1[cubrid.go\nGORM Dialector + Migrator]
cubrid-go speaks the CUBRID CAS (Client Application Server) protocol directly over TCP. The two-step connection sequence is:
- Broker handshake — connect to
host:port, send a 10-byte magic string, receive a redirected CAS port. - Open database — connect to the CAS port, send credentials (628 bytes), receive session info.
All subsequent requests use the PREPARE_AND_EXECUTE (function code 41) combined packet, and large result sets are streamed back via FETCH (function code 8).
| Document | Description |
|---|---|
| API Reference | Complete database/sql driver API, DSN format, type mapping, error types, protocol details |
| GORM Guide | GORM dialector setup, models, migrations, CRUD, transactions, querying, schema inspection |
| Troubleshooting | Connection, query, transaction, GORM, type, and performance issues with solutions |
import (
"database/sql"
_ "github.com/cubrid-labs/cubrid-go"
)
db, err := sql.Open("cubrid", "cubrid://dba:@localhost:33000/demodb")import (
"gorm.io/gorm"
cubrid "github.com/cubrid-labs/cubrid-go/dialector"
)
db, err := gorm.Open(cubrid.Open("cubrid://dba:@localhost:33000/demodb"), &gorm.Config{})No. cubrid-go is a pure Go implementation that speaks the CUBRID CAS protocol directly over TCP. No shared libraries or CGO needed.
Go 1.21 or later.
Yes. See the Connection Pooling section for a production-oriented example with PingContext() and recommended starting values.
Yes. Use db.Begin() to start a transaction, then tx.Commit() or tx.Rollback().
Yes. The GORM dialector supports AutoMigrate for creating and updating table schemas.
| Package | Description |
|---|---|
| cubrid-go | database/sql driver + GORM dialector |
| gorm-cubrid | GORM dialect for CUBRID |
See ROADMAP.md for this project's direction and next milestones.
For the ecosystem-wide view, see the CUBRID Labs Ecosystem Roadmap and Project Board.
MIT