Documentation
¶
Overview ¶
Package cassandra is a database/sql driver for Apache Cassandra. It wraps github.com/apache/cassandra-gocql-driver/v2 and registers itself as "cassandra".
Connecting ¶
Open a database with a DSN, which is a URL whose scheme is cassandra:
db, err := sql.Open("cassandra", "cassandra://user:password@10.0.0.1,10.0.0.2:9042/keyspace?consistency=localQuorum")
The driver reads no other scheme and no older form of the DSN. See ParseDSN for every key. To set a gocql option that the DSN cannot express, build a gocql.ClusterConfig and pass it to NewConnector.
All the connections of one sql.DB share one gocql session, which holds its own pool of connections to each node. The pool settings of sql.DB, such as SetMaxOpenConns, limit how many statements run at the same time. They do not limit the connections to Cassandra. The numConns key does that.
Arguments ¶
A statement takes its values at ? markers. Every Go value that gocql can marshal is accepted, including slices, maps, gocql.UUID, gocql.Duration, *big.Int, net.IP, structs for user defined types, and gocql.UnsetValue. The canonical types of a column bind too: a dbimp.Date, a dbimp.LocalTime, a dbimp.Interval, an *apd.Decimal, a netip.Addr and a dbimp.Vector. Named arguments from sql.Named are refused, because gocql cannot bind a value by name.
Query options ¶
An Option, such as WithConsistency or WithPageSize, changes how one statement runs. Pass it among the arguments, or attach it to a context with WithOptions:
rows, err := db.QueryContext(ctx, "SELECT id FROM users WHERE org = ?", org, cassandra.WithPageSize(500))
Scanning ¶
Scan a column into any type that gocql can decode it into, such as *[]string, *map[string]int or *gocql.UUID, or into any type that database/sql can convert to. A column has the Go type that fits its CQL type, by the kinds of dbimp: a decimal is an *apd.Decimal, a varint is a *big.Int, a date is a dbimp.Date, a time is a dbimp.LocalTime, a duration is a dbimp.Interval, an inet is a netip.Addr, and a uuid is the standard uuid.UUID, which the driver takes as an argument too. A NULL scanned into a plain *string or *int64 is an error, as it is with other drivers. Scan into a sql.Null[T], a pointer to a pointer, or *any to read a column that can be NULL. Cassandra stores an empty collection as NULL, so a NULL scanned into a slice or a map gives nil and no error.
Transactions, batches and lightweight transactions ¶
CQL has no transactions, and BeginTx returns an error that wraps dbimp.ErrNotSupported. A batch is a CQL statement. Pass the whole BEGIN BATCH ... APPLY BATCH text to ExecContext. To read the [applied] column of a lightweight transaction, run it with QueryContext and scan the first column into a bool. When it is false, the row also holds the values that are there already, one column each.
ExecContext returns driver.ResultNoRows, because Cassandra reports no row count and no insert ID.
Example ¶
package main
import (
"context"
"database/sql"
"log"
gocql "github.com/apache/cassandra-gocql-driver/v2"
)
func main() {
db, err := sql.Open("cassandra", "cassandra://cassandra:cassandra@127.0.0.1:9042/app?consistency=localQuorum")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ctx := context.Background()
rows, err := db.QueryContext(ctx, "SELECT id, name, tags FROM users WHERE org = ?", "xo")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var (
id gocql.UUID
name sql.Null[string]
tags []string
)
if err := rows.Scan(&id, &name, &tags); err != nil {
log.Fatal(err)
}
log.Println(id, name.V, tags)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
Output:
Example (Batch) ¶
package main
import (
"context"
"database/sql"
"log"
)
func main() {
db, err := sql.Open("cassandra", "cassandra://127.0.0.1/app")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// A batch is one CQL statement. A logged batch is atomic, but it is not
// isolated and it cannot roll back.
_, err = db.ExecContext(context.Background(), `BEGIN BATCH
INSERT INTO users (id, name) VALUES (?, ?);
INSERT INTO users_by_name (name, id) VALUES (?, ?);
APPLY BATCH`, 1, "ken", "ken", 1)
if err != nil {
log.Fatal(err)
}
}
Output:
Example (LightweightTransaction) ¶
package main
import (
"context"
"database/sql"
"log"
)
func main() {
db, err := sql.Open("cassandra", "cassandra://127.0.0.1/app")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// The first column of the result is [applied]. Run the statement as a
// query to read it. When it is false, the row also holds the values that
// are there already, so the number of columns changes.
rows, err := db.QueryContext(context.Background(),
"INSERT INTO users (id, name) VALUES (?, ?) IF NOT EXISTS", 1, "ken")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
log.Fatal(err)
}
var applied bool
dest := make([]any, len(cols))
dest[0] = &applied
for i := 1; i < len(dest); i++ {
dest[i] = new(any)
}
for rows.Next() {
if err := rows.Scan(dest...); err != nil {
log.Fatal(err)
}
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
log.Println("applied:", applied)
}
Output:
Index ¶
- Constants
- func FormatDSN(cfg *gocql.ClusterConfig) (string, error)
- func ParseDSN(dsn string) (*gocql.ClusterConfig, error)
- func WithOptions(ctx context.Context, opts ...Option) context.Context
- type Connector
- type Driver
- type Error
- type Option
- func WithConsistency(c gocql.Consistency) Option
- func WithDatabase(name string) Option
- func WithIdempotent(idempotent bool) Option
- func WithPageSize(n int) Option
- func WithParameter(name string, value any) Option
- func WithReadonly(readonly bool) Option
- func WithSerialConsistency(c gocql.Consistency) Option
- func WithTimeout(d time.Duration) Option
- func WithTimestamp(t time.Time) Option
Examples ¶
Constants ¶
const Name = "cassandra"
Name is the name that the driver registers with database/sql, and the scheme of its DSN.
Variables ¶
This section is empty.
Functions ¶
func FormatDSN ¶
func FormatDSN(cfg *gocql.ClusterConfig) (string, error)
FormatDSN writes cfg as a cassandra:// URL that ParseDSN reads back. It writes only the settings that differ from the defaults of gocql.NewCluster, and only the settings that the DSN can express. It returns an error for a value that the DSN cannot express, such as a consistency with no DSN name or an authenticator other than gocql.PasswordAuthenticator.
func ParseDSN ¶
func ParseDSN(dsn string) (*gocql.ClusterConfig, error)
ParseDSN parses a DSN and returns the configuration of a gocql cluster. A DSN is a URL with the scheme cassandra, parsed with net/url, and no other scheme or form is read (dbimp D27):
cassandra://user:password@host1:9042/keyspace?consistency=localQuorum&host=host2
The user information sets the username and the password, the host part holds one host, and the path names the keyspace. Each host key adds one more host, in order: cassandra://h1:9042/ks?host=h2:9042&host=[::1]:9042 (D34).
The query takes these keys, and each has the default of gocql.NewCluster:
consistency any, one, two, three, quorum, all, localQuorum, eachQuorum or localOne keyspace the keyspace timeout the timeout of a request, such as 10s connectTimeout the timeout of a new connection, such as 10s numConns the number of connections to each host ignorePeerAddr true or false disableInitialHostLookup true or false writeCoalesceWaitTime a duration, such as 200µs username, password the credentials enableHostVerification true or false certPath, keyPath, caPath the paths of the TLS files host one more host
A key that is not in the list is an error that wraps dbimp.ErrUnknownKey. A key that appears twice wraps dbimp.ErrRepeatedKey, except host. A scheme other than cassandra wraps dbimp.ErrScheme, and any other fault wraps dbimp.ErrInvalidValue. An error never holds the DSN, because the DSN can hold a password. A DSN with no host connects to 127.0.0.1.
func WithOptions ¶
WithOptions returns a copy of ctx that carries opts. Every statement that runs with the returned context uses them. When ctx already carries options, opts come after them, so a later option wins.
Example ¶
package main
import (
"context"
"database/sql"
"log"
"time"
gocql "github.com/apache/cassandra-gocql-driver/v2"
"github.com/xo/cassandra"
)
func main() {
db, err := sql.Open("cassandra", "cassandra://127.0.0.1/app")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Every statement that runs with ctx reads at LOCAL_ONE.
ctx := cassandra.WithOptions(context.Background(), cassandra.WithConsistency(gocql.LocalOne))
// An argument overrides the context for one statement.
_, err = db.ExecContext(ctx, "UPDATE users SET name = ? WHERE id = ?", "ken", 1,
cassandra.WithConsistency(gocql.Quorum), cassandra.WithTimestamp(time.Now()))
if err != nil {
log.Fatal(err)
}
}
Output:
Types ¶
type Connector ¶
type Connector struct {
// contains filtered or unexported fields
}
Connector opens connections to one Cassandra cluster. Every connection shares one gocql session, which the Connector creates on the first Connect. Close the Connector to close the session. sql.DB.Close closes it.
Use a Connector to set a gocql option that the DSN cannot express, such as a host selection policy, a retry policy, a logger or an observer:
cfg := gocql.NewCluster("10.0.0.1", "10.0.0.2")
cfg.Logger = logger
db := sql.OpenDB(cassandra.NewConnector(cfg))
func NewConnector ¶
func NewConnector(cfg *gocql.ClusterConfig) *Connector
NewConnector returns a Connector for cfg. It keeps a copy of cfg, so a change to cfg after the call does not reach the Connector. It does not connect.
Example ¶
package main
import (
"context"
"database/sql"
"log"
gocql "github.com/apache/cassandra-gocql-driver/v2"
"github.com/xo/cassandra"
)
func main() {
cfg := gocql.NewCluster("10.0.0.1", "10.0.0.2")
cfg.Keyspace = "app"
cfg.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(gocql.RoundRobinHostPolicy())
db := sql.OpenDB(cassandra.NewConnector(cfg))
defer db.Close()
if err := db.PingContext(context.Background()); err != nil {
log.Fatal(err)
}
}
Output:
func (*Connector) Close ¶
Close closes the shared session. A connection that is open stops working. Close can be called more than once.
func (*Connector) Connect ¶
Connect returns a connection over the shared session. The first call creates the session. When that fails, Connect returns the error, and the next call tries again.
gocql creates a session with no context, so the connectTimeout key of the DSN limits how long the first call takes.
type Driver ¶
type Driver struct{}
Driver is the cassandra driver. It holds no configuration.
type Error ¶
type Error string
Error is an error that the driver reports.
const ( // ErrConnectorClosed is returned by Connect after Close. ErrConnectorClosed Error = "connector is closed" )
Error values. The other errors of the driver wrap the errors of dbimp: a DSN wraps dbimp.ErrScheme, dbimp.ErrUnknownKey, dbimp.ErrRepeatedKey or dbimp.ErrInvalidValue, and a feature that Cassandra has no form of, such as a transaction or a named argument, wraps dbimp.ErrNotSupported.
type Option ¶
Option changes how one statement runs (dbimp D109). Pass an Option as an argument to a query, or attach it to a context with WithOptions. An Option in the argument list is not sent to Cassandra as a value.
The DSN sets the defaults for the whole session. An Option from the context overrides the DSN, and an Option from the arguments overrides the context. When two options set the same thing, the later one wins.
func WithConsistency ¶
func WithConsistency(c gocql.Consistency) Option
WithConsistency sets the consistency level of the statement, as the key consistency of the DSN does for the session.
func WithDatabase ¶
WithDatabase sets the keyspace of the statement. gocql sends it with the statement, which needs protocol version 5, and a server that speaks an older version refuses the statement.
func WithIdempotent ¶
WithIdempotent tells gocql whether the statement is safe to run more than once. gocql retries a statement only when it is idempotent.
func WithPageSize ¶
WithPageSize sets how many rows gocql reads in one page. gocql reads the next page when the rows of the current page are used up.
func WithParameter ¶
WithParameter sets a key of the request by its name. The native protocol has no body of keys for a statement, so any name fails the statement with an error that wraps dbimp.ErrNotSupported. Use the option of the driver for each setting that gocql lets one statement change.
func WithReadonly ¶
WithReadonly asks the server to refuse a write. Cassandra has no read-only statement, so WithReadonly(true) fails the statement with an error that wraps dbimp.ErrNotSupported. Create a role that can only read, and connect as that role.
func WithSerialConsistency ¶
func WithSerialConsistency(c gocql.Consistency) Option
WithSerialConsistency sets the serial consistency level of a lightweight transaction.
func WithTimeout ¶
WithTimeout ends the statement after d, which includes the time that it takes to read every page of its rows. Zero sets no timeout of its own, and the timeout key of the DSN then applies to each request.
func WithTimestamp ¶
WithTimestamp sets the write time of the statement. Cassandra stores the time in microseconds.