Skip to content

Commit

Permalink
published first draft of the v2 rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
tux21b committed Aug 11, 2013
1 parent a1bebff commit c9fd6df
Show file tree
Hide file tree
Showing 8 changed files with 891 additions and 991 deletions.
88 changes: 48 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,81 @@
gocql
=====

The gocql package provides a database/sql driver for CQL, the Cassandra
query language.

This package requires a recent version of Cassandra (≥ 1.2) that supports
CQL 3.0 and the new native protocol. The native protocol is still considered
beta and must be enabled manually in Cassandra 1.2 by setting
"start_native_transport" to true in conf/cassandra.yaml.

**Note:** gocql requires the tip version of Go, as some changes in the
`database/sql` have not made it into 1.0.x yet. There is
[a fork](https://github.com/titanous/gocql) that backports these changes
to Go 1.0.3.
Package gocql implements a fast and robust Cassandra driver for the
Go programming language.

Installation
------------

go get github.com/tux21b/gocql


Features
--------

* Modern Cassandra client for Cassandra 2.0
* Built-In support for UUIDs (version 1 and 4)


Example
-------

```go
package main

import (
"database/sql"
"fmt"
_ "github.com/tux21b/gocql"
"github.com/tux21b/gocql"
"log"
)

func main() {
db, err := sql.Open("gocql", "localhost:9042 keyspace=system")
if err != nil {
fmt.Println("Open error:", err)
// connect to your cluster
db := gocql.NewSession(gocql.Config{
Nodes: []string{
"192.168.1.1",
"192.168.1.2",
"192.168.1.3",
},
Keyspace: "example", // (optional)
Consistency: gocql.ConQuorum, // (optional)
})
defer db.Close()

// simple query
var title, text string
if err := db.Query("SELECT title, text FROM posts WHERE title = ?",
"Lorem Ipsum").Scan(&title, &text); err != nil {
log.Fatal(err)
}
fmt.Println(title, text)

rows, err := db.Query("SELECT keyspace_name FROM schema_keyspaces")
if err != nil {
fmt.Println("Query error:", err)
// iterator example
var titles []string
iter := db.Query("SELECT title FROM posts").Iter()
for iter.Scan(&title) {
titles = append(titles, title)
}
if err := iter.Close(); err != nil {
log.Fatal(err)
}
fmt.Println(titles)

for rows.Next() {
var keyspace string
err = rows.Scan(&keyspace)
if err != nil {
fmt.Println("Scan error:", err)
}
fmt.Println(keyspace)
// insertion example (with custom consistency level)
if err := db.Query("INSERT INTO posts (title, text) VALUES (?, ?)",
"New Title", "foobar").Consistency(gocql.ConAny).Exec(); err != nil {
log.Fatal(err)
}

if err = rows.Err(); err != nil {
fmt.Println("Iteration error:", err)
return
// prepared queries
query := gocql.NewQuery("SELECT text FROM posts WHERE title = ?")
if err := db.Do(query, "New Title").Scan(&text); err != nil {
log.Fatal(err)
}
fmt.Println(text)
}
```

Please see `gocql_test.go` for some more advanced examples.

Features
--------

* Modern Cassandra client that is based on Cassandra's new native protocol
* Compatible with Go's `database/sql` package
* Built-In support for UUIDs (version 1 and 4)
* Optional frame compression (using snappy)

License
-------

Expand Down
247 changes: 247 additions & 0 deletions binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package gocql

import (
"errors"
"net"
)

const (
protoRequest byte = 0x02
protoResponse byte = 0x82

opError byte = 0x00
opStartup byte = 0x01
opReady byte = 0x02
opAuthenticate byte = 0x03
opOptions byte = 0x05
opSupported byte = 0x06
opQuery byte = 0x07
opResult byte = 0x08
opPrepare byte = 0x09
opExecute byte = 0x0A
opRegister byte = 0x0B
opEvent byte = 0x0C
opBatch byte = 0x0D
opAuthChallenge byte = 0x0E
opAuthResponse byte = 0x0F
opAuthSuccess byte = 0x10

resultKindVoid = 1
resultKindRows = 2
resultKindKeyspace = 3
resultKindPrepared = 4
resultKindSchemaChanged = 5

flagQueryValues uint8 = 1

headerSize = 8
)

var ErrInvalid = errors.New("invalid response")

type buffer []byte

func (b *buffer) writeInt(v int32) {
p := b.grow(4)
(*b)[p] = byte(v >> 24)
(*b)[p+1] = byte(v >> 16)
(*b)[p+2] = byte(v >> 8)
(*b)[p+3] = byte(v)
}

func (b *buffer) writeShort(v uint16) {
p := b.grow(2)
(*b)[p] = byte(v >> 8)
(*b)[p+1] = byte(v)
}

func (b *buffer) writeString(v string) {
b.writeShort(uint16(len(v)))
p := b.grow(len(v))
copy((*b)[p:], v)
}

func (b *buffer) writeLongString(v string) {
b.writeInt(int32(len(v)))
p := b.grow(len(v))
copy((*b)[p:], v)
}

func (b *buffer) writeUUID() {
}

func (b *buffer) writeStringList(v []string) {
b.writeShort(uint16(len(v)))
for i := range v {
b.writeString(v[i])
}
}

func (b *buffer) writeByte(v byte) {
p := b.grow(1)
(*b)[p] = v
}

func (b *buffer) writeBytes(v []byte) {
if v == nil {
b.writeInt(-1)
return
}
b.writeInt(int32(len(v)))
p := b.grow(len(v))
copy((*b)[p:], v)
}

func (b *buffer) writeShortBytes(v []byte) {
b.writeShort(uint16(len(v)))
p := b.grow(len(v))
copy((*b)[p:], v)
}

func (b *buffer) writeInet(ip net.IP, port int) {
p := b.grow(1 + len(ip))
(*b)[p] = byte(len(ip))
copy((*b)[p+1:], ip)
b.writeInt(int32(port))
}

func (b *buffer) writeConsistency() {
}

func (b *buffer) writeStringMap(v map[string]string) {
b.writeShort(uint16(len(v)))
for key, value := range v {
b.writeString(key)
b.writeString(value)
}
}

func (b *buffer) writeStringMultimap(v map[string][]string) {
b.writeShort(uint16(len(v)))
for key, values := range v {
b.writeString(key)
b.writeStringList(values)
}
}

func (b *buffer) setHeader(version, flags, stream, opcode uint8) {
(*b)[0] = version
(*b)[1] = flags
(*b)[2] = stream
(*b)[3] = opcode
}

func (b *buffer) setLength(length int) {
(*b)[4] = byte(length >> 24)
(*b)[5] = byte(length >> 16)
(*b)[6] = byte(length >> 8)
(*b)[7] = byte(length)
}

func (b *buffer) Length() int {
return int((*b)[4])<<24 | int((*b)[5])<<16 | int((*b)[6])<<8 | int((*b)[7])
}

func (b *buffer) grow(n int) int {
if len(*b)+n >= cap(*b) {
buf := make(buffer, len(*b), len(*b)*2+n)
copy(buf, *b)
*b = buf
}
p := len(*b)
*b = (*b)[:p+n]
return p
}

func (b *buffer) skipHeader() {
*b = (*b)[headerSize:]
}

func (b *buffer) readInt() int {
if len(*b) < 4 {
panic(ErrInvalid)
}
v := int((*b)[0])<<24 | int((*b)[1])<<16 | int((*b)[2])<<8 | int((*b)[3])
*b = (*b)[4:]
return v
}

func (b *buffer) readShort() uint16 {
if len(*b) < 2 {
panic(ErrInvalid)
}
v := uint16((*b)[0])<<8 | uint16((*b)[1])
*b = (*b)[2:]
return v
}

func (b *buffer) readString() string {
n := int(b.readShort())
if len(*b) < n {
panic(ErrInvalid)
}
v := string((*b)[:n])
*b = (*b)[n:]
return v
}

func (b *buffer) readBytes() []byte {
n := b.readInt()
if n < 0 {
return nil
}
if len(*b) < n {
panic(ErrInvalid)
}
v := (*b)[:n]
*b = (*b)[n:]
return v
}

func (b *buffer) readShortBytes() []byte {
n := int(b.readShort())
if len(*b) < n {
panic(ErrInvalid)
}
v := (*b)[:n]
*b = (*b)[n:]
return v
}

func (b *buffer) readTypeInfo() *TypeInfo {
x := b.readShort()
typ := &TypeInfo{Type: Type(x)}
switch typ.Type {
case TypeCustom:
typ.Custom = b.readString()
case TypeMap:
typ.Key = b.readTypeInfo()
fallthrough
case TypeList, TypeSet:
typ.Value = b.readTypeInfo()
}
return typ
}

func (b *buffer) readMetaData() []columnInfo {
flags := b.readInt()
numColumns := b.readInt()
globalKeyspace := ""
globalTable := ""
if flags&1 != 0 {
globalKeyspace = b.readString()
globalTable = b.readString()
}
info := make([]columnInfo, numColumns)
for i := 0; i < numColumns; i++ {
info[i].Keyspace = globalKeyspace
info[i].Table = globalTable
if flags&1 == 0 {
info[i].Keyspace = b.readString()
info[i].Table = b.readString()
}
info[i].Name = b.readString()
info[i].TypeInfo = b.readTypeInfo()
}
return info
}
Loading

0 comments on commit c9fd6df

Please sign in to comment.