mysql

package module
v0.0.0-...-f53743d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

README

go-ruby-mysql/mysql

mysql — go-ruby-mysql

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the Ruby mysql2 gem's Mysql2 API. Upstream, mysql2 is a C extension linking libmysqlclient; this module binds github.com/go-sql-driver/mysql instead — a pure-Go implementation of the MySQL client/server protocol — over database/sql, and exposes the gem's Mysql2::Client / Mysql2::Result / Mysql2::Statement / Mysql2::Error surface on top. The whole stack links statically with CGO_ENABLED=0 on every 64-bit target the go-* ecosystem supports.

It completes the database set alongside go-ruby-sqlite3 (the SQLite engine) and go-ruby-pg (the PostgreSQL wire protocol), and is the MySQL backend for go-embedded-ruby — a standalone, reusable module.

What it is — and isn't. The connection, the SQL, and the MySQL↔Ruby type coercions are fully deterministic and need no interpreter, so they live here as pure Go. Turning a returned row into live Ruby objects is the host's job; this library hands back a small, explicit value model (int64, float64, Decimal, Date, time.Time, string, []byte, nil, bool), plus an Error carrying the exact #error_number and #sql_state.

Features

Faithful port of the gem's Mysql2 surface:

  • ClientNewClient (Mysql2::Client.new with host:, port:, username:, password:, database:, socket:, encoding:, flags:, and the connect/read/write timeouts), Query, Prepare, Escape, Ping, Close (idempotent), AffectedRows, LastID, ServerInfo.
  • ResultMysql2::Result as Enumerable rows: Fields, Count, Rows (as: :array), Hashes (as: :hash), Each / EachHash / EachArray, and the symbolize_keys flag.
  • StatementPrepareStatement with positional ? binds, Execute / ExecuteOpts, Close.
  • Casting — the gem's cast table (cast, cast_booleans, symbolize_keys, as: :array/:hash), turning each column into its Ruby type.
  • ErrorsMysql2::Error with #error_number and #sql_state, mapped from the server error or a generic client-side failure (HY000).

CGO-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) — the whole stack, including the pure-Go MySQL protocol driver, builds and runs pure-Go.

Install

go get github.com/go-ruby-mysql/mysql

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-mysql/mysql"
)

func main() {
	c, _ := mysql.NewClient(mysql.Options{
		Host: "127.0.0.1", Port: 3306,
		Username: "root", Password: "secret", Database: "app",
	}) // Mysql2::Client.new(host:, port:, username:, password:, database:)
	defer c.Close()

	res, _ := c.Query("SELECT id, name FROM hosts")
	for _, h := range res.Hashes() {
		fmt.Println(h["id"], h["name"]) // int64, string
	}

	// Prepared statement, like Mysql2::Statement.
	st, _ := c.Prepare("SELECT name FROM hosts WHERE id > ?")
	defer st.Close()
	r, _ := st.Execute(0)
	_ = r.EachHash(func(row mysql.HashRow) error {
		fmt.Println(row["name"])
		return nil
	})

	c.Query("INSERT INTO hosts (name) VALUES ('web')")
	fmt.Println(c.AffectedRows(), c.LastID())
}

Type mapping

Result values crossing the boundary use mysql2's MySQL→Ruby cast table:

MySQL column type Go (this package) Ruby (host binding)
TINYINT..BIGINT, YEAR int64 / uint64 Integer
FLOAT, DOUBLE float64 Float
DECIMAL / NEWDECIMAL Decimal (a string) BigDecimal
DATE Date Date
DATETIME, TIMESTAMP time.Time (naive UTC) Time
TIME string String
CHAR/VARCHAR/TEXT/… string String (UTF-8)
BLOB/BINARY/BIT/… []byte String (ASCII-8BIT)
NULL nil nil

The cast is governed by the same options mysql2 exposes, carried on QueryOptions: Cast (cast: false returns every value as a String), CastBooleans (a TINYINTbool), SymbolizeKeys, and As ("hash" / "array").

Errors

Errors are *mysql.Error, carrying the server error number and SQLSTATE:

_, err := c.Query("INSERT INTO t (id) VALUES (1)") // duplicate key
var e *mysql.Error
if errors.As(err, &e) {
	fmt.Println(e.ErrorNumber()) // 1062
	fmt.Println(e.SQLState())    // 23000
}

A client-side failure (a dial error, a cancellation) surfaces as an Error with number 0 and the general-error SQLSTATE HY000.

Backend & architectures

The backend is github.com/go-sql-driver/mysql — the MySQL protocol in pure Go, no cgo. Every arch below builds and tests with CGO_ENABLED=0:

arch CGO=0 build notes
amd64 native CI lane
arm64 native CI lane
riscv64 qemu-user CI lane
loong64 qemu-user CI lane
ppc64le qemu-user CI lane
s390x qemu-user CI lane (big-endian)

Tests & coverage

The suite drives the whole NewClient → Query → Result → cast pipeline — every MySQL type, NULLs, the option matrix, and the error taxonomy — through an in-process fake database/sql driver, so it reaches 100% coverage with no live MySQL server. That is what keeps the qemu cross-arch and Windows lanes green. An optional live oracle (TestLiveOracle) round-trips a typed row against a real server when MYSQL2_TEST_DSN is set (a go-sql-driver DSN, e.g. root:root@tcp(127.0.0.1:3306)/test), and skips otherwise — so it never runs in CI.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

# Optional: round-trip against a real MySQL.
MYSQL2_TEST_DSN='root:root@tcp(127.0.0.1:3306)/test' go test -run TestLiveOracle ./...

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-mysql/mysql authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package mysql is a pure-Go (CGO=0) reimplementation of the Ruby mysql2 gem's Mysql2 API. Upstream, mysql2 is a C extension linking libmysqlclient; this package instead binds github.com/go-sql-driver/mysql — a pure-Go, CGO-free implementation of the MySQL client/server protocol — through database/sql, so the whole stack links statically with CGO_ENABLED=0 on every 64-bit target the go-* ecosystem supports (amd64, arm64, riscv64, loong64, ppc64le, s390x).

The API mirrors Mysql2::Client, Mysql2::Result, Mysql2::Statement and Mysql2::Error:

c, _ := mysql.NewClient(mysql.Options{Host: "127.0.0.1", Username: "root", Database: "test"})
defer c.Close()
res, _ := c.Query("SELECT id, name FROM hosts")
for _, h := range res.Hashes() {
	fmt.Println(h["id"], h["name"]) // int64, string
}

Result values crossing the boundary use mysql2's MySQL->Ruby cast table:

MySQL column type        Go (this package)     Ruby (rbgo binding)
TINYINT..BIGINT, YEAR    int64 / uint64        Integer
FLOAT, DOUBLE            float64               Float
DECIMAL / NEWDECIMAL     Decimal (a string)    BigDecimal
DATE                     Date                  Date
DATETIME, TIMESTAMP      time.Time (naive UTC) Time
TIME                     string                String
CHAR/VARCHAR/TEXT/…      string                String (UTF-8)
BLOB/BINARY/BIT/GEOMETRY []byte                String (ASCII-8BIT)
NULL                     nil                   nil

The cast is governed by the same options mysql2 exposes — cast, cast_booleans, symbolize_keys, and as: :array vs :hash — carried on QueryOptions. Errors map to Mysql2::Error via the Error type, exposing #error_number and #sql_state.

The connection is a host seam: the deterministic test suite drives the whole query -> result -> cast pipeline through an in-process fake database/sql driver, so it reaches 100% coverage without a live server, while an optional env-gated oracle round-trips against a real MySQL when MYSQL2_TEST_DSN is set.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a handle to a MySQL server, mirroring Mysql2::Client. It wraps a *sql.DB over the go-sql-driver backend.

func NewClient

func NewClient(opts Options) (*Client, error)

NewClient connects to a MySQL server described by opts and returns a Client, mirroring Mysql2::Client.new. It opens the pool and verifies the connection with a ping (mysql2 connects eagerly), wrapping any failure as a *mysql.Error.

func (*Client) AffectedRows

func (c *Client) AffectedRows() int64

AffectedRows returns the number of rows changed by the most recent statement (Mysql2::Client#affected_rows).

func (*Client) Close

func (c *Client) Close() error

Close closes the client and its connection pool (Mysql2::Client#close). Closing an already-closed client is a no-op, matching the gem.

func (*Client) Closed

func (c *Client) Closed() bool

Closed reports whether the client has been closed.

func (*Client) Escape

func (c *Client) Escape(s string) string

Escape escapes a string for safe interpolation into a statement (Mysql2::Client#escape).

func (*Client) LastID

func (c *Client) LastID() int64

LastID returns the AUTO_INCREMENT value generated by the most recent INSERT (Mysql2::Client#last_id / #insert_id).

func (*Client) Ping

func (c *Client) Ping() bool

Ping checks the connection is alive (Mysql2::Client#ping), returning false on failure like the gem rather than an error.

func (*Client) Prepare

func (c *Client) Prepare(query string) (*Statement, error)

Prepare compiles query into a *Statement (Mysql2::Client#prepare). Bind parameters positionally on Execute using MySQL's `?` placeholders.

func (*Client) Query

func (c *Client) Query(query string, opts ...QueryOptions) (*Result, error)

Query runs sql and returns a *Result for a row-producing statement, or a nil Result for a statement that changes rows (INSERT / UPDATE / DELETE / DDL), mirroring Mysql2::Client#query (which returns a Result or nil). After a non-row statement AffectedRows and LastID reflect it. An optional QueryOptions overrides the client defaults for this call.

func (*Client) ServerInfo

func (c *Client) ServerInfo() (ServerInfo, error)

ServerInfo queries the server version (Mysql2::Client#server_info) via SELECT VERSION() and returns it parsed.

type Date

type Date struct {
	Year  int
	Month int
	Day   int
}

Date is a calendar date from a DATE column, with no time-of-day or zone — mirroring the Ruby Date mysql2 returns for DATE. The rbgo binding maps it to a Ruby Date; keeping it distinct from time.Time lets the host pick Date vs Time.

func (Date) String

func (d Date) String() string

String renders the date as "YYYY-MM-DD", the canonical MySQL DATE literal.

type Decimal

type Decimal string

Decimal is a fixed-point value from a DECIMAL / NEWDECIMAL column, carried as its exact decimal string so no precision is lost. The rbgo binding turns it into a Ruby BigDecimal (mysql2's cast for DECIMAL). Its String is the raw database text (e.g. "3.14").

func (Decimal) String

func (d Decimal) String() string

String returns the decimal text.

type Error

type Error struct {
	// contains filtered or unexported fields
}

Error is a database error mirroring Mysql2::Error. It carries the MySQL error number and SQLSTATE the rbgo binding exposes as #error_number and #sql_state, plus the human-readable message (#message). A driver error that is not a server error (a dial failure, a context cancellation) surfaces as an Error with number 0 and the generic "HY000" SQLSTATE, matching how mysql2 wraps client-side failures.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface (Mysql2::Error#message).

func (*Error) ErrorNumber

func (e *Error) ErrorNumber() int

ErrorNumber returns the MySQL server error number (Mysql2::Error#error_number / #errno), e.g. 1062 for a duplicate-key violation. It is 0 for a client-side error with no server number.

func (*Error) SQLState

func (e *Error) SQLState() string

SQLState returns the five-character SQLSTATE (Mysql2::Error#sql_state), e.g. "23000" for an integrity-constraint violation, or "HY000" (the general-error state) when the server supplied none.

type HashRow

type HashRow = map[string]Value

HashRow is a single result row keyed by column name — the shape Mysql2::Result yields by default (`as: :hash`).

type Options

type Options struct {
	Host           string
	Port           int
	Username       string
	Password       string
	Database       string
	Socket         string        // unix-socket path; when set, Net becomes "unix"
	Encoding       string        // connection charset (mysql2 :encoding), e.g. "utf8mb4"
	Flags          []string      // capability flags, e.g. "MULTI_STATEMENTS", "FOUND_ROWS"
	ConnectTimeout time.Duration // dial timeout (mysql2 :connect_timeout)
	ReadTimeout    time.Duration // I/O read timeout (mysql2 :read_timeout)
	WriteTimeout   time.Duration // I/O write timeout (mysql2 :write_timeout)
	// QueryDefaults overrides the per-client default query options. When nil the
	// client uses DefaultQueryOptions().
	QueryDefaults *QueryOptions
}

Options configures a Client, mirroring the keyword arguments of Mysql2::Client.new (host:, port:, username:, password:, database:, socket:, encoding:, flags:, connect_timeout:, read_timeout:, write_timeout:, …).

type QueryOptions

type QueryOptions struct {
	// Cast toggles type casting (mysql2 :cast). When false every value is
	// returned as a String. Default true.
	Cast bool
	// CastBooleans casts TINYINT columns to bool (mysql2 :cast_booleans).
	CastBooleans bool
	// SymbolizeKeys requests Symbol hash keys (mysql2 :symbolize_keys); carried
	// for the host to honour, as Go has no Symbol.
	SymbolizeKeys bool
	// As selects the row shape: asHash ("hash", default) or asArray ("array").
	As string
}

QueryOptions carries the per-query options mysql2 accepts on Client#query and merges from Client.new's default_query_options. The zero value is not the gem's default (mysql2 defaults cast:true); build one with DefaultQueryOptions and adjust, or set Client.QueryDefaults.

func DefaultQueryOptions

func DefaultQueryOptions() QueryOptions

DefaultQueryOptions returns the gem's default query options: cast on, no boolean cast, string keys, hash rows.

type Result

type Result struct {
	// contains filtered or unexported fields
}

Result is a buffered result set, mirroring Mysql2::Result. mysql2 returns one from Client#query / Statement#execute for a statement that yields rows. It is Enumerable over its rows; each row is a HashRow (keyed by column name, the default) or a Row (a positional array, the `as: :array` shape), selected by the QueryOptions the query ran with. Fields and Count mirror #fields and #count / #size.

func (*Result) As

func (r *Result) As() string

As returns the row shape the result was built for ("hash" or "array"), mirroring the query's `as:` option.

func (*Result) Count

func (r *Result) Count() int

Count returns the number of rows (Mysql2::Result#count / #size).

func (*Result) Each

func (r *Result) Each(fn func(any) error) error

Each iterates the result in its configured shape (Mysql2::Result#each): it yields a HashRow by default, or a Row when the query used as: :array. fn receives an any it type-switches; iteration stops at the first error.

func (*Result) EachArray

func (r *Result) EachArray(fn func(Row) error) error

EachArray calls fn once per row as a positional array, stopping at and returning the first error fn returns (the block form of #each with as: :array).

func (*Result) EachHash

func (r *Result) EachHash(fn func(HashRow) error) error

EachHash calls fn once per row as a name-keyed map, stopping at and returning the first error fn returns (the block form of #each).

func (*Result) Fields

func (r *Result) Fields() []string

Fields returns the column names in column order (Mysql2::Result#fields).

func (*Result) Hashes

func (r *Result) Hashes() []HashRow

Hashes returns every row as a name-keyed map — the default `as: :hash` shape. A duplicate column name keeps the last column's value, matching how a Ruby Hash overwrites a repeated key.

func (*Result) Rows

func (r *Result) Rows() []Row

Rows returns every row as a positional array in column order — the `as: :array` shape (Mysql2::Result#each with as: :array).

func (*Result) SymbolizeKeys

func (r *Result) SymbolizeKeys() bool

SymbolizeKeys reports whether the query requested symbol keys (Mysql2::Result built with symbolize_keys: true). Go has no Symbol type, so the flag is carried for the rbgo binding to intern the string keys into Ruby Symbols; the maps here are always string-keyed.

type Row

type Row = []Value

Row is a single result row as a slice of column Values in column order — the shape Mysql2::Result yields with `as: :array`.

type ServerInfo

type ServerInfo struct {
	Version       string
	VersionNumber int
}

ServerInfo describes the connected server, mirroring the Hash Mysql2::Client#server_info returns: the version string and a packed numeric id (major*10000 + minor*100 + patch).

type Statement

type Statement struct {
	// contains filtered or unexported fields
}

Statement is a prepared statement, mirroring Mysql2::Statement. Client#prepare compiles the SQL; Execute binds the given parameters and runs it, returning a *Result for a row-producing statement or a nil Result otherwise (after which the owning client's AffectedRows / LastID reflect the change).

func (*Statement) Close

func (s *Statement) Close() error

Close releases the prepared statement (Mysql2::Statement#close). Closing an already-closed statement is a no-op.

func (*Statement) Closed

func (s *Statement) Closed() bool

Closed reports whether the statement has been closed.

func (*Statement) Execute

func (s *Statement) Execute(args ...any) (*Result, error)

Execute binds args to the statement's placeholders and runs it (Mysql2::Statement#execute). For a row-producing statement it returns a *Result using the client's default query options; for a changing statement it returns a nil Result and updates the client's AffectedRows / LastID. Use ExecuteOpts to override the query options.

func (*Statement) ExecuteOpts

func (s *Statement) ExecuteOpts(qo QueryOptions, args ...any) (*Result, error)

ExecuteOpts is Execute with explicit per-execution query options.

func (*Statement) SQL

func (s *Statement) SQL() string

SQL returns the statement's source text (Mysql2::Statement#sql-ish).

type Value

type Value = any

Value is a datum crossing the Go<->MySQL boundary. On a cast result it is one of: nil, int64, uint64, float64, Decimal, Date, time.Time, string, []byte, or bool. On binds this package accepts int / int32 / int64 / uint / float32 / float64 / string / []byte / bool / time.Time / nil.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL