|
|
@@ -6,7 +6,10 @@ package gocql
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
+ "io"
|
|
|
"net"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
@@ -15,603 +18,1300 @@ const (
|
|
|
protoVersion1 = 0x01
|
|
|
protoVersion2 = 0x02
|
|
|
protoVersion3 = 0x03
|
|
|
+)
|
|
|
+
|
|
|
+type protoVersion byte
|
|
|
+
|
|
|
+func (p protoVersion) request() bool {
|
|
|
+ return p&protoDirectionMask == 0x00
|
|
|
+}
|
|
|
+
|
|
|
+func (p protoVersion) response() bool {
|
|
|
+ return p&protoDirectionMask == 0x80
|
|
|
+}
|
|
|
+
|
|
|
+func (p protoVersion) version() byte {
|
|
|
+ return byte(p) & protoVersionMask
|
|
|
+}
|
|
|
+
|
|
|
+func (p protoVersion) String() string {
|
|
|
+ dir := "REQ"
|
|
|
+ if p.response() {
|
|
|
+ dir = "RESP"
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Sprintf("[version=%d direction=%s]", p.version(), dir)
|
|
|
+}
|
|
|
|
|
|
- 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
|
|
|
+type frameOp byte
|
|
|
|
|
|
+const (
|
|
|
+ // header ops
|
|
|
+ opError frameOp = 0x00
|
|
|
+ opStartup = 0x01
|
|
|
+ opReady = 0x02
|
|
|
+ opAuthenticate = 0x03
|
|
|
+ opOptions = 0x05
|
|
|
+ opSupported = 0x06
|
|
|
+ opQuery = 0x07
|
|
|
+ opResult = 0x08
|
|
|
+ opPrepare = 0x09
|
|
|
+ opExecute = 0x0A
|
|
|
+ opRegister = 0x0B
|
|
|
+ opEvent = 0x0C
|
|
|
+ opBatch = 0x0D
|
|
|
+ opAuthChallenge = 0x0E
|
|
|
+ opAuthResponse = 0x0F
|
|
|
+ opAuthSuccess = 0x10
|
|
|
+)
|
|
|
+
|
|
|
+func (f frameOp) String() string {
|
|
|
+ switch f {
|
|
|
+ case opError:
|
|
|
+ return "ERROR"
|
|
|
+ case opStartup:
|
|
|
+ return "STARTUP"
|
|
|
+ case opReady:
|
|
|
+ return "READY"
|
|
|
+ case opAuthenticate:
|
|
|
+ return "AUTHENTICATE"
|
|
|
+ case opOptions:
|
|
|
+ return "OPTIONS"
|
|
|
+ case opSupported:
|
|
|
+ return "SUPPORTED"
|
|
|
+ case opQuery:
|
|
|
+ return "QUERY"
|
|
|
+ case opResult:
|
|
|
+ return "RESULT"
|
|
|
+ case opPrepare:
|
|
|
+ return "PREPARE"
|
|
|
+ case opExecute:
|
|
|
+ return "EXECUTE"
|
|
|
+ case opRegister:
|
|
|
+ return "REGISTER"
|
|
|
+ case opEvent:
|
|
|
+ return "EVENT"
|
|
|
+ case opBatch:
|
|
|
+ return "BATCH"
|
|
|
+ case opAuthChallenge:
|
|
|
+ return "AUTH_CHALLENGE"
|
|
|
+ case opAuthResponse:
|
|
|
+ return "AUTH_RESPONSE"
|
|
|
+ case opAuthSuccess:
|
|
|
+ return "AUTH_SUCCESS"
|
|
|
+ default:
|
|
|
+ return fmt.Sprintf("UNKNOWN_OP_%d", f)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const (
|
|
|
+ // result kind
|
|
|
resultKindVoid = 1
|
|
|
resultKindRows = 2
|
|
|
resultKindKeyspace = 3
|
|
|
resultKindPrepared = 4
|
|
|
resultKindSchemaChanged = 5
|
|
|
|
|
|
- flagQueryValues uint8 = 1
|
|
|
- flagCompress uint8 = 1
|
|
|
- flagTrace uint8 = 2
|
|
|
- flagPageSize uint8 = 4
|
|
|
- flagPageState uint8 = 8
|
|
|
- flagHasMore uint8 = 2
|
|
|
+ // rows flags
|
|
|
+ flagGlobalTableSpec int = 0x01
|
|
|
+ flagHasMorePages = 0x02
|
|
|
+ flagNoMetaData = 0x04
|
|
|
+
|
|
|
+ // query flags
|
|
|
+ flagValues byte = 0x01
|
|
|
+ flagSkipMetaData = 0x02
|
|
|
+ flagPageSize = 0x04
|
|
|
+ flagWithPagingState = 0x08
|
|
|
+ flagWithSerialConsistency = 0x10
|
|
|
+ flagDefaultTimestamp = 0x20
|
|
|
+ flagWithNameValues = 0x40
|
|
|
+
|
|
|
+ // header flags
|
|
|
+ flagCompress byte = 0x01
|
|
|
+ flagTracing = 0x02
|
|
|
+)
|
|
|
+
|
|
|
+type Consistency uint16
|
|
|
+
|
|
|
+const (
|
|
|
+ Any Consistency = 0x00
|
|
|
+ One = 0x01
|
|
|
+ Two = 0x02
|
|
|
+ Three = 0x03
|
|
|
+ Quorum = 0x04
|
|
|
+ All = 0x05
|
|
|
+ LocalQuorum = 0x06
|
|
|
+ EachQuorum = 0x07
|
|
|
+ Serial = 0x08
|
|
|
+ LocalSerial = 0x09
|
|
|
+ LocalOne = 0x0A
|
|
|
+)
|
|
|
+
|
|
|
+func (c Consistency) String() string {
|
|
|
+ switch c {
|
|
|
+ case Any:
|
|
|
+ return "ANY"
|
|
|
+ case One:
|
|
|
+ return "ONE"
|
|
|
+ case Two:
|
|
|
+ return "TWO"
|
|
|
+ case Three:
|
|
|
+ return "THREE"
|
|
|
+ case Quorum:
|
|
|
+ return "QUORUM"
|
|
|
+ case All:
|
|
|
+ return "ALL"
|
|
|
+ case LocalQuorum:
|
|
|
+ return "LOCAL_QUORUM"
|
|
|
+ case EachQuorum:
|
|
|
+ return "EACH_QUORUM"
|
|
|
+ case Serial:
|
|
|
+ return "SERIAL"
|
|
|
+ case LocalSerial:
|
|
|
+ return "LOCAL_SERIAL"
|
|
|
+ case LocalOne:
|
|
|
+ return "LOCAL_ONE"
|
|
|
+ default:
|
|
|
+ return fmt.Sprintf("UNKNOWN_CONS_0x%x", uint16(c))
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+const (
|
|
|
apacheCassandraTypePrefix = "org.apache.cassandra.db.marshal."
|
|
|
)
|
|
|
|
|
|
-var headerProtoSize = [...]int{
|
|
|
- protoVersion1: 8,
|
|
|
- protoVersion2: 8,
|
|
|
- protoVersion3: 9,
|
|
|
+func writeInt(p []byte, n int32) {
|
|
|
+ p[0] = byte(n >> 24)
|
|
|
+ p[1] = byte(n >> 16)
|
|
|
+ p[2] = byte(n >> 8)
|
|
|
+ p[3] = byte(n)
|
|
|
}
|
|
|
|
|
|
-// TODO: replace with a struct which has a header and a body buffer,
|
|
|
-// header just has methods like, set/get the options in its backing array
|
|
|
-// then in a writeTo we write the header then the body.
|
|
|
-type frame []byte
|
|
|
+func readInt(p []byte) int32 {
|
|
|
+ return int32(p[0])<<24 | int32(p[1])<<16 | int32(p[2])<<8 | int32(p[3])
|
|
|
+}
|
|
|
|
|
|
-func newFrame(version uint8) frame {
|
|
|
- // TODO: pool these at the session level incase anyone is using different
|
|
|
- // clusters with different versions in the same application.
|
|
|
- return make(frame, headerProtoSize[version], defaultFrameSize)
|
|
|
+func writeShort(p []byte, n uint16) {
|
|
|
+ p[0] = byte(n >> 8)
|
|
|
+ p[1] = byte(n)
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeInt(v int32) {
|
|
|
- p := f.grow(4)
|
|
|
- (*f)[p] = byte(v >> 24)
|
|
|
- (*f)[p+1] = byte(v >> 16)
|
|
|
- (*f)[p+2] = byte(v >> 8)
|
|
|
- (*f)[p+3] = byte(v)
|
|
|
+func readShort(p []byte) uint16 {
|
|
|
+ return uint16(p[0])<<8 | uint16(p[1])
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeShort(v uint16) {
|
|
|
- p := f.grow(2)
|
|
|
- (*f)[p] = byte(v >> 8)
|
|
|
- (*f)[p+1] = byte(v)
|
|
|
+type frameHeader struct {
|
|
|
+ version protoVersion
|
|
|
+ flags byte
|
|
|
+ stream int
|
|
|
+ op frameOp
|
|
|
+ length int
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeString(v string) {
|
|
|
- f.writeShort(uint16(len(v)))
|
|
|
- p := f.grow(len(v))
|
|
|
- copy((*f)[p:], v)
|
|
|
+func (f frameHeader) String() string {
|
|
|
+ return fmt.Sprintf("[header version=%s flags=0x%x stream=%d op=%s length=%d]", f.version, f.flags, f.stream, f.op, f.length)
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeLongString(v string) {
|
|
|
- f.writeInt(int32(len(v)))
|
|
|
- p := f.grow(len(v))
|
|
|
- copy((*f)[p:], v)
|
|
|
+func (f frameHeader) Header() frameHeader {
|
|
|
+ return f
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeUUID() {
|
|
|
+const defaultBufSize = 128
|
|
|
+
|
|
|
+var framerPool = sync.Pool{
|
|
|
+ New: func() interface{} {
|
|
|
+ return &framer{
|
|
|
+ wbuf: make([]byte, defaultBufSize),
|
|
|
+ readBuffer: make([]byte, defaultBufSize),
|
|
|
+ }
|
|
|
+ },
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeStringList(v []string) {
|
|
|
- f.writeShort(uint16(len(v)))
|
|
|
- for i := range v {
|
|
|
- f.writeString(v[i])
|
|
|
+// a framer is responsible for reading, writing and parsing frames on a single stream
|
|
|
+type framer struct {
|
|
|
+ r io.Reader
|
|
|
+ w io.Writer
|
|
|
+
|
|
|
+ proto byte
|
|
|
+ // flags are for outgoing flags, enabling compression and tracing etc
|
|
|
+ flags byte
|
|
|
+ compres Compressor
|
|
|
+ headSize int
|
|
|
+ // if this frame was read then the header will be here
|
|
|
+ header *frameHeader
|
|
|
+
|
|
|
+ // if tracing flag is set this is not nil
|
|
|
+ traceID []byte
|
|
|
+
|
|
|
+ // holds a ref to the whole byte slice for rbuf so that it can be reset to
|
|
|
+ // 0 after a read.
|
|
|
+ readBuffer []byte
|
|
|
+
|
|
|
+ rbuf []byte
|
|
|
+ wbuf []byte
|
|
|
+}
|
|
|
+
|
|
|
+func newFramer(r io.Reader, w io.Writer, compressor Compressor, version byte) *framer {
|
|
|
+ f := framerPool.Get().(*framer)
|
|
|
+ var flags byte
|
|
|
+ if compressor != nil {
|
|
|
+ flags |= flagCompress
|
|
|
}
|
|
|
+
|
|
|
+ version &= protoVersionMask
|
|
|
+
|
|
|
+ headSize := 8
|
|
|
+ if version > protoVersion2 {
|
|
|
+ headSize = 9
|
|
|
+ }
|
|
|
+
|
|
|
+ f.compres = compressor
|
|
|
+ f.proto = version
|
|
|
+ f.flags = flags
|
|
|
+ f.headSize = headSize
|
|
|
+
|
|
|
+ f.r = r
|
|
|
+ f.rbuf = f.readBuffer[:0]
|
|
|
+
|
|
|
+ f.w = w
|
|
|
+ f.wbuf = f.wbuf[:0]
|
|
|
+
|
|
|
+ f.header = nil
|
|
|
+ f.traceID = nil
|
|
|
+
|
|
|
+ return f
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeByte(v byte) {
|
|
|
- p := f.grow(1)
|
|
|
- (*f)[p] = v
|
|
|
+type frame interface {
|
|
|
+ Header() frameHeader
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeBytes(v []byte) {
|
|
|
- if v == nil {
|
|
|
- f.writeInt(-1)
|
|
|
+func readHeader(r io.Reader, p []byte) (head frameHeader, err error) {
|
|
|
+ _, err = io.ReadFull(r, p)
|
|
|
+ if err != nil {
|
|
|
return
|
|
|
}
|
|
|
- f.writeInt(int32(len(v)))
|
|
|
- p := f.grow(len(v))
|
|
|
- copy((*f)[p:], v)
|
|
|
-}
|
|
|
|
|
|
-func (f *frame) writeShortBytes(v []byte) {
|
|
|
- f.writeShort(uint16(len(v)))
|
|
|
- p := f.grow(len(v))
|
|
|
- copy((*f)[p:], v)
|
|
|
+ version := p[0] & protoVersionMask
|
|
|
+ head.version = protoVersion(p[0])
|
|
|
+
|
|
|
+ head.flags = p[1]
|
|
|
+ if version > protoVersion2 {
|
|
|
+ head.stream = int(readShort(p[2:]))
|
|
|
+ head.op = frameOp(p[4])
|
|
|
+ head.length = int(readInt(p[5:]))
|
|
|
+ } else {
|
|
|
+ head.stream = int(p[2])
|
|
|
+ head.op = frameOp(p[3])
|
|
|
+ head.length = int(readInt(p[4:]))
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeInet(ip net.IP, port int) {
|
|
|
- p := f.grow(1 + len(ip))
|
|
|
- (*f)[p] = byte(len(ip))
|
|
|
- copy((*f)[p+1:], ip)
|
|
|
- f.writeInt(int32(port))
|
|
|
+// explicitly enables tracing for the framers outgoing requests
|
|
|
+func (f *framer) trace() {
|
|
|
+ f.flags |= flagTracing
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeStringMap(v map[string]string) {
|
|
|
- f.writeShort(uint16(len(v)))
|
|
|
- for key, value := range v {
|
|
|
- f.writeString(key)
|
|
|
- f.writeString(value)
|
|
|
+// reads a frame form the wire into the framers buffer
|
|
|
+func (f *framer) readFrame(head *frameHeader) error {
|
|
|
+ if cap(f.readBuffer) >= head.length {
|
|
|
+ f.rbuf = f.readBuffer[:head.length]
|
|
|
+ } else {
|
|
|
+ f.readBuffer = make([]byte, head.length)
|
|
|
+ f.rbuf = f.readBuffer
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-func (f *frame) writeStringMultimap(v map[string][]string) {
|
|
|
- f.writeShort(uint16(len(v)))
|
|
|
- for key, values := range v {
|
|
|
- f.writeString(key)
|
|
|
- f.writeStringList(values)
|
|
|
+ // assume the underlying reader takes care of timeouts and retries
|
|
|
+ _, err := io.ReadFull(f.r, f.rbuf)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-func (f *frame) setHeader(version, flags uint8, stream int, opcode uint8) {
|
|
|
- (*f)[0] = version
|
|
|
- (*f)[1] = flags
|
|
|
- p := 2
|
|
|
- if version&maskVersion > protoVersion2 {
|
|
|
- (*f)[2] = byte(stream >> 8)
|
|
|
- (*f)[3] = byte(stream)
|
|
|
- p += 2
|
|
|
- } else {
|
|
|
- (*f)[2] = byte(stream & 0xFF)
|
|
|
- p++
|
|
|
+ if head.flags&flagCompress == flagCompress {
|
|
|
+ if f.compres == nil {
|
|
|
+ return NewErrProtocol("no compressor available with compressed frame body")
|
|
|
+ }
|
|
|
+
|
|
|
+ f.rbuf, err = f.compres.Decode(f.rbuf)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- (*f)[p] = opcode
|
|
|
+ f.header = head
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-func (f *frame) setStream(stream int, version uint8) {
|
|
|
- if version > protoVersion2 {
|
|
|
- (*f)[2] = byte(stream >> 8)
|
|
|
- (*f)[3] = byte(stream)
|
|
|
- } else {
|
|
|
- (*f)[2] = byte(stream)
|
|
|
+func (f *framer) parseFrame() (frame, error) {
|
|
|
+ if f.header.version.request() {
|
|
|
+ return nil, NewErrProtocol("got a request frame from server: %v", f.header.version)
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-func (f *frame) Stream(version uint8) (n int) {
|
|
|
- if version > protoVersion2 {
|
|
|
- n = int((*f)[2])<<8 | int((*f)[3])
|
|
|
- } else {
|
|
|
- n = int((*f)[2])
|
|
|
+ if f.header.flags&flagTracing == flagTracing {
|
|
|
+ f.readTrace()
|
|
|
}
|
|
|
- return
|
|
|
+
|
|
|
+ var (
|
|
|
+ frame frame
|
|
|
+ err error
|
|
|
+ )
|
|
|
+
|
|
|
+ // asumes that the frame body has been read into rbuf
|
|
|
+ switch f.header.op {
|
|
|
+ case opError:
|
|
|
+ frame = f.parseErrorFrame()
|
|
|
+ case opReady:
|
|
|
+ frame = f.parseReadyFrame()
|
|
|
+ case opResult:
|
|
|
+ frame, err = f.parseResultFrame()
|
|
|
+ case opSupported:
|
|
|
+ frame = f.parseSupportedFrame()
|
|
|
+ case opAuthenticate:
|
|
|
+ frame = f.parseAuthenticateFrame()
|
|
|
+ case opAuthChallenge:
|
|
|
+ frame = f.parseAuthChallengeFrame()
|
|
|
+ case opAuthSuccess:
|
|
|
+ frame = f.parseAuthSuccessFrame()
|
|
|
+ default:
|
|
|
+ return nil, NewErrProtocol("unknown op in frame header: %s", f.header.op)
|
|
|
+ }
|
|
|
+
|
|
|
+ return frame, err
|
|
|
}
|
|
|
|
|
|
-func (f *frame) setLength(length int, version uint8) {
|
|
|
- p := 4
|
|
|
- if version > protoVersion2 {
|
|
|
- p = 5
|
|
|
+func (f *framer) parseErrorFrame() frame {
|
|
|
+ code := f.readInt()
|
|
|
+ msg := f.readString()
|
|
|
+
|
|
|
+ errD := errorFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ code: code,
|
|
|
+ message: msg,
|
|
|
}
|
|
|
|
|
|
- (*f)[p] = byte(length >> 24)
|
|
|
- (*f)[p+1] = byte(length >> 16)
|
|
|
- (*f)[p+2] = byte(length >> 8)
|
|
|
- (*f)[p+3] = byte(length)
|
|
|
+ switch code {
|
|
|
+ case errUnavailable:
|
|
|
+ cl := f.readConsistency()
|
|
|
+ required := f.readInt()
|
|
|
+ alive := f.readInt()
|
|
|
+ return &RequestErrUnavailable{
|
|
|
+ errorFrame: errD,
|
|
|
+ Consistency: cl,
|
|
|
+ Required: required,
|
|
|
+ Alive: alive,
|
|
|
+ }
|
|
|
+ case errWriteTimeout:
|
|
|
+ cl := f.readConsistency()
|
|
|
+ received := f.readInt()
|
|
|
+ blockfor := f.readInt()
|
|
|
+ writeType := f.readString()
|
|
|
+ return &RequestErrWriteTimeout{
|
|
|
+ errorFrame: errD,
|
|
|
+ Consistency: cl,
|
|
|
+ Received: received,
|
|
|
+ BlockFor: blockfor,
|
|
|
+ WriteType: writeType,
|
|
|
+ }
|
|
|
+ case errReadTimeout:
|
|
|
+ cl := f.readConsistency()
|
|
|
+ received := f.readInt()
|
|
|
+ blockfor := f.readInt()
|
|
|
+ dataPresent := f.readByte()
|
|
|
+ return &RequestErrReadTimeout{
|
|
|
+ errorFrame: errD,
|
|
|
+ Consistency: cl,
|
|
|
+ Received: received,
|
|
|
+ BlockFor: blockfor,
|
|
|
+ DataPresent: dataPresent,
|
|
|
+ }
|
|
|
+ case errAlreadyExists:
|
|
|
+ ks := f.readString()
|
|
|
+ table := f.readString()
|
|
|
+ return &RequestErrAlreadyExists{
|
|
|
+ errorFrame: errD,
|
|
|
+ Keyspace: ks,
|
|
|
+ Table: table,
|
|
|
+ }
|
|
|
+ case errUnprepared:
|
|
|
+ stmtId := f.readShortBytes()
|
|
|
+ return &RequestErrUnprepared{
|
|
|
+ errorFrame: errD,
|
|
|
+ StatementId: stmtId,
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return &errD
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-func (f *frame) Op(version uint8) byte {
|
|
|
- if version > protoVersion2 {
|
|
|
- return (*f)[4]
|
|
|
+func (f *framer) writeHeader(flags byte, op frameOp, stream int) {
|
|
|
+ f.wbuf = f.wbuf[:0]
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ f.proto,
|
|
|
+ flags,
|
|
|
+ )
|
|
|
+
|
|
|
+ if f.proto > protoVersion2 {
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ byte(stream>>8),
|
|
|
+ byte(stream),
|
|
|
+ )
|
|
|
} else {
|
|
|
- return (*f)[3]
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ byte(stream),
|
|
|
+ )
|
|
|
}
|
|
|
+
|
|
|
+ // pad out length
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ byte(op),
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
-func (f *frame) Length(version uint8) int {
|
|
|
+func (f *framer) setLength(length int) {
|
|
|
p := 4
|
|
|
- if version > protoVersion2 {
|
|
|
+ if f.proto > protoVersion2 {
|
|
|
p = 5
|
|
|
}
|
|
|
|
|
|
- return int((*f)[p])<<24 | int((*f)[p+1])<<16 | int((*f)[p+2])<<8 | int((*f)[p+3])
|
|
|
+ f.wbuf[p+0] = byte(length >> 24)
|
|
|
+ f.wbuf[p+1] = byte(length >> 16)
|
|
|
+ f.wbuf[p+2] = byte(length >> 8)
|
|
|
+ f.wbuf[p+3] = byte(length)
|
|
|
}
|
|
|
|
|
|
-func (f *frame) grow(n int) int {
|
|
|
- if len(*f)+n >= cap(*f) {
|
|
|
- buf := make(frame, len(*f), len(*f)*2+n)
|
|
|
- copy(buf, *f)
|
|
|
- *f = buf
|
|
|
+func (f *framer) finishWrite() error {
|
|
|
+ if f.wbuf[1]&flagCompress == flagCompress {
|
|
|
+ if f.compres == nil {
|
|
|
+ panic("compress flag set with no compressor")
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: only compress frames which are big enough
|
|
|
+ compressed, err := f.compres.Encode(f.wbuf[f.headSize:])
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ f.wbuf = append(f.wbuf[:f.headSize], compressed...)
|
|
|
}
|
|
|
- p := len(*f)
|
|
|
- *f = (*f)[:p+n]
|
|
|
- return p
|
|
|
+ length := len(f.wbuf) - f.headSize
|
|
|
+ f.setLength(length)
|
|
|
+
|
|
|
+ _, err := f.w.Write(f.wbuf)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-func (f *frame) skipHeader(version uint8) {
|
|
|
- *f = (*f)[headerProtoSize[version]:]
|
|
|
+func (f *framer) readTrace() {
|
|
|
+ f.traceID = f.readUUID().Bytes()
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readInt() int {
|
|
|
- if len(*f) < 4 {
|
|
|
- panic(NewErrProtocol("Trying to read an int while <4 bytes in the buffer"))
|
|
|
- }
|
|
|
- v := uint32((*f)[0])<<24 | uint32((*f)[1])<<16 | uint32((*f)[2])<<8 | uint32((*f)[3])
|
|
|
- *f = (*f)[4:]
|
|
|
- return int(int32(v))
|
|
|
+type readyFrame struct {
|
|
|
+ frameHeader
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readShort() uint16 {
|
|
|
- if len(*f) < 2 {
|
|
|
- panic(NewErrProtocol("Trying to read a short while <2 bytes in the buffer"))
|
|
|
+func (f *framer) parseReadyFrame() frame {
|
|
|
+ return &readyFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
}
|
|
|
- v := uint16((*f)[0])<<8 | uint16((*f)[1])
|
|
|
- *f = (*f)[2:]
|
|
|
- return v
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readString() string {
|
|
|
- n := int(f.readShort())
|
|
|
- if len(*f) < n {
|
|
|
- panic(NewErrProtocol("Trying to read a string of %d bytes from a buffer with %d bytes in it", n, len(*f)))
|
|
|
- }
|
|
|
- v := string((*f)[:n])
|
|
|
- *f = (*f)[n:]
|
|
|
- return v
|
|
|
+type supportedFrame struct {
|
|
|
+ frameHeader
|
|
|
+
|
|
|
+ supported map[string][]string
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readLongString() string {
|
|
|
- n := f.readInt()
|
|
|
- if len(*f) < n {
|
|
|
- panic(NewErrProtocol("Trying to read a string of %d bytes from a buffer with %d bytes in it", n, len(*f)))
|
|
|
+// TODO: if we move the body buffer onto the frameHeader then we only need a single
|
|
|
+// framer, and can move the methods onto the header.
|
|
|
+func (f *framer) parseSupportedFrame() frame {
|
|
|
+ return &supportedFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+
|
|
|
+ supported: f.readStringMultiMap(),
|
|
|
}
|
|
|
- v := string((*f)[:n])
|
|
|
- *f = (*f)[n:]
|
|
|
- return v
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readBytes() []byte {
|
|
|
- n := f.readInt()
|
|
|
- if n < 0 {
|
|
|
- return nil
|
|
|
- }
|
|
|
- if len(*f) < n {
|
|
|
- panic(NewErrProtocol("Trying to read %d bytes from a buffer with %d bytes in it", n, len(*f)))
|
|
|
- }
|
|
|
- v := (*f)[:n]
|
|
|
- *f = (*f)[n:]
|
|
|
- return v
|
|
|
+type writeStartupFrame struct {
|
|
|
+ opts map[string]string
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readShortBytes() []byte {
|
|
|
- n := int(f.readShort())
|
|
|
- if len(*f) < n {
|
|
|
- panic(NewErrProtocol("Trying to read %d bytes from a buffer with %d bytes in it", n, len(*f)))
|
|
|
- }
|
|
|
- v := (*f)[:n]
|
|
|
- *f = (*f)[n:]
|
|
|
- return v
|
|
|
+func (w *writeStartupFrame) writeFrame(framer *framer, streamID int) error {
|
|
|
+ return framer.writeStartupFrame(streamID, w.opts)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writeStartupFrame(streamID int, options map[string]string) error {
|
|
|
+ f.writeHeader(f.flags&^flagCompress, opStartup, streamID)
|
|
|
+ f.writeStringMap(options)
|
|
|
+
|
|
|
+ return f.finishWrite()
|
|
|
+}
|
|
|
+
|
|
|
+type writePrepareFrame struct {
|
|
|
+ statement string
|
|
|
+}
|
|
|
+
|
|
|
+func (w *writePrepareFrame) writeFrame(framer *framer, streamID int) error {
|
|
|
+ return framer.writePrepareFrame(streamID, w.statement)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writePrepareFrame(stream int, statement string) error {
|
|
|
+ f.writeHeader(f.flags, opPrepare, stream)
|
|
|
+ f.writeLongString(statement)
|
|
|
+ return f.finishWrite()
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readTypeInfo(version uint8) *TypeInfo {
|
|
|
- x := f.readShort()
|
|
|
+func (f *framer) readTypeInfo() *TypeInfo {
|
|
|
+ id := f.readShort()
|
|
|
typ := &TypeInfo{
|
|
|
- Proto: version,
|
|
|
- Type: Type(x),
|
|
|
+ // we need to pass proto to the marshaller here
|
|
|
+ Proto: f.proto,
|
|
|
+ Type: Type(id),
|
|
|
}
|
|
|
+
|
|
|
switch typ.Type {
|
|
|
case TypeCustom:
|
|
|
typ.Custom = f.readString()
|
|
|
if cassType := getApacheCassandraType(typ.Custom); cassType != TypeCustom {
|
|
|
- typ = &TypeInfo{Type: cassType}
|
|
|
+ typ = &TypeInfo{
|
|
|
+ Proto: f.proto,
|
|
|
+ Type: cassType,
|
|
|
+ }
|
|
|
switch typ.Type {
|
|
|
case TypeMap:
|
|
|
- typ.Key = f.readTypeInfo(version)
|
|
|
+ typ.Key = f.readTypeInfo()
|
|
|
fallthrough
|
|
|
case TypeList, TypeSet:
|
|
|
- typ.Elem = f.readTypeInfo(version)
|
|
|
+ typ.Elem = f.readTypeInfo()
|
|
|
}
|
|
|
}
|
|
|
case TypeMap:
|
|
|
- typ.Key = f.readTypeInfo(version)
|
|
|
+ typ.Key = f.readTypeInfo()
|
|
|
fallthrough
|
|
|
case TypeList, TypeSet:
|
|
|
- typ.Elem = f.readTypeInfo(version)
|
|
|
+ typ.Elem = f.readTypeInfo()
|
|
|
}
|
|
|
+
|
|
|
return typ
|
|
|
}
|
|
|
|
|
|
-func (f *frame) readMetaData(version uint8) ([]ColumnInfo, []byte) {
|
|
|
- flags := f.readInt()
|
|
|
- numColumns := f.readInt()
|
|
|
+type resultMetadata struct {
|
|
|
+ flags int
|
|
|
|
|
|
- var pageState []byte
|
|
|
- if flags&2 != 0 {
|
|
|
- pageState = f.readBytes()
|
|
|
+ // only if flagPageState
|
|
|
+ pagingState []byte
|
|
|
+
|
|
|
+ columns []ColumnInfo
|
|
|
+}
|
|
|
+
|
|
|
+func (r resultMetadata) String() string {
|
|
|
+ return fmt.Sprintf("[metadata flags=0x%x paging_state=% X columns=%v]", r.flags, r.pagingState, r.columns)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) parseResultMetadata() resultMetadata {
|
|
|
+ meta := resultMetadata{
|
|
|
+ flags: f.readInt(),
|
|
|
}
|
|
|
|
|
|
- globalKeyspace := ""
|
|
|
- globalTable := ""
|
|
|
- if flags&1 != 0 {
|
|
|
- globalKeyspace = f.readString()
|
|
|
- globalTable = f.readString()
|
|
|
+ colCount := f.readInt()
|
|
|
+
|
|
|
+ if meta.flags&flagHasMorePages == flagHasMorePages {
|
|
|
+ meta.pagingState = f.readBytes()
|
|
|
}
|
|
|
|
|
|
- columns := make([]ColumnInfo, numColumns)
|
|
|
- for i := 0; i < numColumns; i++ {
|
|
|
- columns[i].Keyspace = globalKeyspace
|
|
|
- columns[i].Table = globalTable
|
|
|
- if flags&1 == 0 {
|
|
|
- columns[i].Keyspace = f.readString()
|
|
|
- columns[i].Table = f.readString()
|
|
|
- }
|
|
|
- columns[i].Name = f.readString()
|
|
|
- columns[i].TypeInfo = f.readTypeInfo(version)
|
|
|
+ if meta.flags&flagNoMetaData == flagNoMetaData {
|
|
|
+ return meta
|
|
|
}
|
|
|
- return columns, pageState
|
|
|
-}
|
|
|
|
|
|
-func (f *frame) readError() RequestError {
|
|
|
- code := f.readInt()
|
|
|
- msg := f.readString()
|
|
|
- errD := errorFrame{code, msg}
|
|
|
- switch code {
|
|
|
- case errUnavailable:
|
|
|
- cl := Consistency(f.readShort())
|
|
|
- required := f.readInt()
|
|
|
- alive := f.readInt()
|
|
|
- return RequestErrUnavailable{errorFrame: errD,
|
|
|
- Consistency: cl,
|
|
|
- Required: required,
|
|
|
- Alive: alive}
|
|
|
- case errWriteTimeout:
|
|
|
- cl := Consistency(f.readShort())
|
|
|
- received := f.readInt()
|
|
|
- blockfor := f.readInt()
|
|
|
- writeType := f.readString()
|
|
|
- return RequestErrWriteTimeout{errorFrame: errD,
|
|
|
- Consistency: cl,
|
|
|
- Received: received,
|
|
|
- BlockFor: blockfor,
|
|
|
- WriteType: writeType,
|
|
|
- }
|
|
|
- case errReadTimeout:
|
|
|
- cl := Consistency(f.readShort())
|
|
|
- received := f.readInt()
|
|
|
- blockfor := f.readInt()
|
|
|
- dataPresent := (*f)[0]
|
|
|
- *f = (*f)[1:]
|
|
|
- return RequestErrReadTimeout{errorFrame: errD,
|
|
|
- Consistency: cl,
|
|
|
- Received: received,
|
|
|
- BlockFor: blockfor,
|
|
|
- DataPresent: dataPresent,
|
|
|
- }
|
|
|
- case errAlreadyExists:
|
|
|
- ks := f.readString()
|
|
|
- table := f.readString()
|
|
|
- return RequestErrAlreadyExists{errorFrame: errD,
|
|
|
- Keyspace: ks,
|
|
|
- Table: table,
|
|
|
- }
|
|
|
- case errUnprepared:
|
|
|
- stmtId := f.readShortBytes()
|
|
|
- return RequestErrUnprepared{errorFrame: errD,
|
|
|
- StatementId: stmtId,
|
|
|
+ var keyspace, table string
|
|
|
+ globalSpec := meta.flags&flagGlobalTableSpec == flagGlobalTableSpec
|
|
|
+ if globalSpec {
|
|
|
+ keyspace = f.readString()
|
|
|
+ table = f.readString()
|
|
|
+ }
|
|
|
+
|
|
|
+ cols := make([]ColumnInfo, colCount)
|
|
|
+
|
|
|
+ for i := 0; i < colCount; i++ {
|
|
|
+ col := &cols[i]
|
|
|
+
|
|
|
+ if !globalSpec {
|
|
|
+ col.Keyspace = f.readString()
|
|
|
+ col.Table = f.readString()
|
|
|
+ } else {
|
|
|
+ col.Keyspace = keyspace
|
|
|
+ col.Table = table
|
|
|
}
|
|
|
- default:
|
|
|
- return errD
|
|
|
+
|
|
|
+ col.Name = f.readString()
|
|
|
+ col.TypeInfo = f.readTypeInfo()
|
|
|
}
|
|
|
+
|
|
|
+ meta.columns = cols
|
|
|
+
|
|
|
+ return meta
|
|
|
}
|
|
|
|
|
|
-func (f *frame) writeConsistency(c Consistency) {
|
|
|
- f.writeShort(consistencyCodes[c])
|
|
|
+type resultVoidFrame struct {
|
|
|
+ frameHeader
|
|
|
}
|
|
|
|
|
|
-func (f frame) encodeFrame(version uint8, dst frame) (frame, error) {
|
|
|
- return f, nil
|
|
|
+func (f *resultVoidFrame) String() string {
|
|
|
+ return "[result_void]"
|
|
|
}
|
|
|
|
|
|
-var consistencyCodes = []uint16{
|
|
|
- Any: 0x0000,
|
|
|
- One: 0x0001,
|
|
|
- Two: 0x0002,
|
|
|
- Three: 0x0003,
|
|
|
- Quorum: 0x0004,
|
|
|
- All: 0x0005,
|
|
|
- LocalQuorum: 0x0006,
|
|
|
- EachQuorum: 0x0007,
|
|
|
- Serial: 0x0008,
|
|
|
- LocalSerial: 0x0009,
|
|
|
- LocalOne: 0x000A,
|
|
|
+func (f *framer) parseResultFrame() (frame, error) {
|
|
|
+ kind := f.readInt()
|
|
|
+
|
|
|
+ switch kind {
|
|
|
+ case resultKindVoid:
|
|
|
+ return &resultVoidFrame{frameHeader: *f.header}, nil
|
|
|
+ case resultKindRows:
|
|
|
+ return f.parseResultRows(), nil
|
|
|
+ case resultKindKeyspace:
|
|
|
+ return f.parseResultSetKeyspace(), nil
|
|
|
+ case resultKindPrepared:
|
|
|
+ return f.parseResultPrepared(), nil
|
|
|
+ case resultKindSchemaChanged:
|
|
|
+ return f.parseResultSchemaChange(), nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil, NewErrProtocol("unknown result kind: %x", kind)
|
|
|
}
|
|
|
|
|
|
-type readyFrame struct{}
|
|
|
+type resultRowsFrame struct {
|
|
|
+ frameHeader
|
|
|
|
|
|
-type supportedFrame struct{}
|
|
|
+ meta resultMetadata
|
|
|
+ rows [][][]byte
|
|
|
+}
|
|
|
|
|
|
-type resultVoidFrame struct{}
|
|
|
+func (f *resultRowsFrame) String() string {
|
|
|
+ return fmt.Sprintf("[result_rows meta=%v]", f.meta)
|
|
|
+}
|
|
|
|
|
|
-type resultRowsFrame struct {
|
|
|
- Columns []ColumnInfo
|
|
|
- Rows [][][]byte
|
|
|
- PagingState []byte
|
|
|
+func (f *framer) parseResultRows() frame {
|
|
|
+ meta := f.parseResultMetadata()
|
|
|
+
|
|
|
+ numRows := f.readInt()
|
|
|
+ colCount := len(meta.columns)
|
|
|
+
|
|
|
+ rows := make([][][]byte, numRows)
|
|
|
+ for i := 0; i < numRows; i++ {
|
|
|
+ rows[i] = make([][]byte, colCount)
|
|
|
+ for j := 0; j < colCount; j++ {
|
|
|
+ rows[i][j] = f.readBytes()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return &resultRowsFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ meta: meta,
|
|
|
+ rows: rows,
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
type resultKeyspaceFrame struct {
|
|
|
- Keyspace string
|
|
|
+ frameHeader
|
|
|
+ keyspace string
|
|
|
+}
|
|
|
+
|
|
|
+func (r *resultKeyspaceFrame) String() string {
|
|
|
+ return fmt.Sprintf("[result_keyspace keyspace=%s]", r.keyspace)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) parseResultSetKeyspace() frame {
|
|
|
+ return &resultKeyspaceFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ keyspace: f.readString(),
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
type resultPreparedFrame struct {
|
|
|
- PreparedId []byte
|
|
|
- Arguments []ColumnInfo
|
|
|
- ReturnValues []ColumnInfo
|
|
|
+ frameHeader
|
|
|
+
|
|
|
+ preparedID []byte
|
|
|
+ reqMeta resultMetadata
|
|
|
+ respMeta resultMetadata
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) parseResultPrepared() frame {
|
|
|
+ frame := &resultPreparedFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ preparedID: f.readShortBytes(),
|
|
|
+ reqMeta: f.parseResultMetadata(),
|
|
|
+ }
|
|
|
+
|
|
|
+ if f.proto < protoVersion2 {
|
|
|
+ return frame
|
|
|
+ }
|
|
|
+
|
|
|
+ frame.respMeta = f.parseResultMetadata()
|
|
|
+
|
|
|
+ return frame
|
|
|
}
|
|
|
|
|
|
-type operation interface {
|
|
|
- encodeFrame(version uint8, dst frame) (frame, error)
|
|
|
+type resultSchemaChangeFrame struct {
|
|
|
+ frameHeader
|
|
|
+
|
|
|
+ change string
|
|
|
+ keyspace string
|
|
|
+ table string
|
|
|
}
|
|
|
|
|
|
-type startupFrame struct {
|
|
|
- CQLVersion string
|
|
|
- Compression string
|
|
|
+func (s *resultSchemaChangeFrame) String() string {
|
|
|
+ return fmt.Sprintf("[result_schema_change change=%s keyspace=%s table=%s]", s.change, s.keyspace, s.table)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) parseResultSchemaChange() frame {
|
|
|
+ frame := &resultSchemaChangeFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ }
|
|
|
+
|
|
|
+ if f.proto < protoVersion3 {
|
|
|
+ frame.change = f.readString()
|
|
|
+ frame.keyspace = f.readString()
|
|
|
+ frame.table = f.readString()
|
|
|
+ } else {
|
|
|
+ // TODO: improve type representation of this
|
|
|
+ frame.change = f.readString()
|
|
|
+ target := f.readString()
|
|
|
+ switch target {
|
|
|
+ case "KEYSPACE":
|
|
|
+ frame.keyspace = f.readString()
|
|
|
+ case "TABLE", "TYPE":
|
|
|
+ frame.keyspace = f.readString()
|
|
|
+ frame.table = f.readString()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return frame
|
|
|
+}
|
|
|
+
|
|
|
+type authenticateFrame struct {
|
|
|
+ frameHeader
|
|
|
+
|
|
|
+ class string
|
|
|
}
|
|
|
|
|
|
-func (op *startupFrame) String() string {
|
|
|
- return fmt.Sprintf("[startup cqlversion=%q compression=%q]", op.CQLVersion, op.Compression)
|
|
|
+func (a *authenticateFrame) String() string {
|
|
|
+ return fmt.Sprintf("[authenticate class=%q]", a.class)
|
|
|
}
|
|
|
|
|
|
-func (op *startupFrame) encodeFrame(version uint8, f frame) (frame, error) {
|
|
|
- if f == nil {
|
|
|
- f = newFrame(version)
|
|
|
+func (f *framer) parseAuthenticateFrame() frame {
|
|
|
+ return &authenticateFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ class: f.readString(),
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+type authSuccessFrame struct {
|
|
|
+ frameHeader
|
|
|
|
|
|
- f.setHeader(version, 0, 0, opStartup)
|
|
|
+ data []byte
|
|
|
+}
|
|
|
+
|
|
|
+func (a *authSuccessFrame) String() string {
|
|
|
+ return fmt.Sprintf("[auth_success data=%q]", a.data)
|
|
|
+}
|
|
|
|
|
|
- // TODO: fix this, this is actually a StringMap
|
|
|
- var size uint16 = 1
|
|
|
- if op.Compression != "" {
|
|
|
- size++
|
|
|
+func (f *framer) parseAuthSuccessFrame() frame {
|
|
|
+ return &authSuccessFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ data: f.readBytes(),
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+type authChallengeFrame struct {
|
|
|
+ frameHeader
|
|
|
|
|
|
- f.writeShort(size)
|
|
|
- f.writeString("CQL_VERSION")
|
|
|
- f.writeString(op.CQLVersion)
|
|
|
+ data []byte
|
|
|
+}
|
|
|
+
|
|
|
+func (a *authChallengeFrame) String() string {
|
|
|
+ return fmt.Sprintf("[auth_challenge data=%q]", a.data)
|
|
|
+}
|
|
|
|
|
|
- if op.Compression != "" {
|
|
|
- f.writeString("COMPRESSION")
|
|
|
- f.writeString(op.Compression)
|
|
|
+func (f *framer) parseAuthChallengeFrame() frame {
|
|
|
+ return &authChallengeFrame{
|
|
|
+ frameHeader: *f.header,
|
|
|
+ data: f.readBytes(),
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+type writeAuthResponseFrame struct {
|
|
|
+ data []byte
|
|
|
+}
|
|
|
|
|
|
- return f, nil
|
|
|
+func (a *writeAuthResponseFrame) String() string {
|
|
|
+ return fmt.Sprintf("[auth_response data=%q]", a.data)
|
|
|
}
|
|
|
|
|
|
-type queryFrame struct {
|
|
|
- Stmt string
|
|
|
- Prepared []byte
|
|
|
- Cons Consistency
|
|
|
- Values [][]byte
|
|
|
- PageSize int
|
|
|
- PageState []byte
|
|
|
+func (a *writeAuthResponseFrame) writeFrame(framer *framer, streamID int) error {
|
|
|
+ return framer.writeAuthResponseFrame(streamID, a.data)
|
|
|
}
|
|
|
|
|
|
-func (op *queryFrame) String() string {
|
|
|
- return fmt.Sprintf("[query statement=%q prepared=%x cons=%v ...]", op.Stmt, op.Prepared, op.Cons)
|
|
|
+func (f *framer) writeAuthResponseFrame(streamID int, data []byte) error {
|
|
|
+ f.writeHeader(f.flags, opAuthResponse, streamID)
|
|
|
+ f.writeBytes(data)
|
|
|
+ return f.finishWrite()
|
|
|
}
|
|
|
|
|
|
-func (op *queryFrame) encodeFrame(version uint8, f frame) (frame, error) {
|
|
|
- if version == 1 && (op.PageSize != 0 || len(op.PageState) > 0 ||
|
|
|
- (len(op.Values) > 0 && len(op.Prepared) == 0)) {
|
|
|
- return nil, ErrUnsupported
|
|
|
+type queryValues struct {
|
|
|
+ value []byte
|
|
|
+ // optional name, will set With names for values flag
|
|
|
+ name string
|
|
|
+}
|
|
|
+
|
|
|
+type queryParams struct {
|
|
|
+ consistency Consistency
|
|
|
+ // v2+
|
|
|
+ skipMeta bool
|
|
|
+ values []queryValues
|
|
|
+ pageSize int
|
|
|
+ pagingState []byte
|
|
|
+ serialConsistency Consistency
|
|
|
+ // v3+
|
|
|
+ timestamp *time.Time
|
|
|
+}
|
|
|
+
|
|
|
+func (q queryParams) String() string {
|
|
|
+ return fmt.Sprintf("[query_params consistency=%v skip_meta=%v page_size=%d paging_state=%q serial_consistency=%v timestamp=%v values=%v]",
|
|
|
+ q.consistency, q.skipMeta, q.pageSize, q.pagingState, q.serialConsistency, q.timestamp, q.values)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writeQueryParams(opts *queryParams) {
|
|
|
+ f.writeConsistency(opts.consistency)
|
|
|
+
|
|
|
+ if f.proto == protoVersion1 {
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- if f == nil {
|
|
|
- f = newFrame(version)
|
|
|
+ var flags byte
|
|
|
+ if len(opts.values) > 0 {
|
|
|
+ flags |= flagValues
|
|
|
+ }
|
|
|
+ if opts.skipMeta {
|
|
|
+ flags |= flagSkipMetaData
|
|
|
+ }
|
|
|
+ if opts.pageSize > 0 {
|
|
|
+ flags |= flagPageSize
|
|
|
+ }
|
|
|
+ if len(opts.pagingState) > 0 {
|
|
|
+ flags |= flagWithPagingState
|
|
|
+ }
|
|
|
+ if opts.serialConsistency > 0 {
|
|
|
+ flags |= flagWithSerialConsistency
|
|
|
}
|
|
|
|
|
|
- if len(op.Prepared) > 0 {
|
|
|
- f.setHeader(version, 0, 0, opExecute)
|
|
|
- f.writeShortBytes(op.Prepared)
|
|
|
- } else {
|
|
|
- f.setHeader(version, 0, 0, opQuery)
|
|
|
- f.writeLongString(op.Stmt)
|
|
|
+ names := false
|
|
|
+
|
|
|
+ // protoV3 specific things
|
|
|
+ if f.proto > protoVersion2 {
|
|
|
+ if opts.timestamp != nil {
|
|
|
+ flags |= flagDefaultTimestamp
|
|
|
+ }
|
|
|
+ if len(opts.values) > 0 && opts.values[0].name != "" {
|
|
|
+ flags |= flagWithNameValues
|
|
|
+ names = true
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if version >= 2 {
|
|
|
- f.writeConsistency(op.Cons)
|
|
|
- flagPos := len(f)
|
|
|
- f.writeByte(0)
|
|
|
+ f.writeByte(flags)
|
|
|
|
|
|
- if len(op.Values) > 0 {
|
|
|
- f[flagPos] |= flagQueryValues
|
|
|
- f.writeShort(uint16(len(op.Values)))
|
|
|
- for _, value := range op.Values {
|
|
|
- f.writeBytes(value)
|
|
|
+ if n := len(opts.values); n > 0 {
|
|
|
+ f.writeShort(uint16(n))
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
+ if names {
|
|
|
+ f.writeString(opts.values[i].name)
|
|
|
}
|
|
|
+ f.writeBytes(opts.values[i].value)
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ if opts.pageSize > 0 {
|
|
|
+ f.writeInt(int32(opts.pageSize))
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(opts.pagingState) > 0 {
|
|
|
+ f.writeBytes(opts.pagingState)
|
|
|
+ }
|
|
|
+
|
|
|
+ if opts.serialConsistency > 0 {
|
|
|
+ f.writeConsistency(opts.serialConsistency)
|
|
|
+ }
|
|
|
+
|
|
|
+ if f.proto > protoVersion2 && opts.timestamp != nil {
|
|
|
+ // timestamp in microseconds
|
|
|
+ // TODO: should the timpestamp be set on the queryParams or should we set
|
|
|
+ // it here?
|
|
|
+ ts := opts.timestamp.UnixNano() / 1000
|
|
|
+ f.writeLong(ts)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type writeQueryFrame struct {
|
|
|
+ statement string
|
|
|
+ params queryParams
|
|
|
+}
|
|
|
+
|
|
|
+func (w *writeQueryFrame) String() string {
|
|
|
+ return fmt.Sprintf("[query statement=%q params=%v]", w.statement, w.params)
|
|
|
+}
|
|
|
+
|
|
|
+func (w *writeQueryFrame) writeFrame(framer *framer, streamID int) error {
|
|
|
+ return framer.writeQueryFrame(streamID, w.statement, &w.params)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writeQueryFrame(streamID int, statement string, params *queryParams) error {
|
|
|
+ f.writeHeader(f.flags, opQuery, streamID)
|
|
|
+ f.writeLongString(statement)
|
|
|
+ f.writeQueryParams(params)
|
|
|
+
|
|
|
+ return f.finishWrite()
|
|
|
+}
|
|
|
+
|
|
|
+type frameWriter interface {
|
|
|
+ writeFrame(framer *framer, streamID int) error
|
|
|
+}
|
|
|
+
|
|
|
+type writeExecuteFrame struct {
|
|
|
+ preparedID []byte
|
|
|
+ params queryParams
|
|
|
+}
|
|
|
+
|
|
|
+func (e *writeExecuteFrame) String() string {
|
|
|
+ return fmt.Sprintf("[execute id=% X params=%v]", e.preparedID, &e.params)
|
|
|
+}
|
|
|
+
|
|
|
+func (e *writeExecuteFrame) writeFrame(fr *framer, streamID int) error {
|
|
|
+ return fr.writeExecuteFrame(streamID, e.preparedID, &e.params)
|
|
|
+}
|
|
|
|
|
|
- if op.PageSize > 0 {
|
|
|
- f[flagPos] |= flagPageSize
|
|
|
- f.writeInt(int32(op.PageSize))
|
|
|
+func (f *framer) writeExecuteFrame(streamID int, preparedID []byte, params *queryParams) error {
|
|
|
+ f.writeHeader(f.flags, opExecute, streamID)
|
|
|
+ f.writeShortBytes(preparedID)
|
|
|
+ if f.proto > protoVersion1 {
|
|
|
+ f.writeQueryParams(params)
|
|
|
+ } else {
|
|
|
+ n := len(params.values)
|
|
|
+ f.writeShort(uint16(n))
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
+ f.writeBytes(params.values[i].value)
|
|
|
}
|
|
|
+ f.writeConsistency(params.consistency)
|
|
|
+ }
|
|
|
+
|
|
|
+ return f.finishWrite()
|
|
|
+}
|
|
|
+
|
|
|
+// TODO: can we replace BatchStatemt with batchStatement? As they prety much
|
|
|
+// duplicate each other
|
|
|
+type batchStatment struct {
|
|
|
+ preparedID []byte
|
|
|
+ statement string
|
|
|
+ values []queryValues
|
|
|
+}
|
|
|
+
|
|
|
+type writeBatchFrame struct {
|
|
|
+ typ BatchType
|
|
|
+ statements []batchStatment
|
|
|
+ consistency Consistency
|
|
|
+ serialConsistency Consistency
|
|
|
+ defaultTimestamp bool
|
|
|
+}
|
|
|
+
|
|
|
+func (w *writeBatchFrame) writeFrame(framer *framer, streamID int) error {
|
|
|
+ return framer.writeBatchFrame(streamID, w)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writeBatchFrame(streamID int, w *writeBatchFrame) error {
|
|
|
+ f.writeHeader(f.flags, opBatch, streamID)
|
|
|
+ f.writeByte(byte(w.typ))
|
|
|
|
|
|
- if len(op.PageState) > 0 {
|
|
|
- f[flagPos] |= flagPageState
|
|
|
- f.writeBytes(op.PageState)
|
|
|
+ n := len(w.statements)
|
|
|
+ f.writeShort(uint16(n))
|
|
|
+
|
|
|
+ var flags byte
|
|
|
+
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
+ b := &w.statements[i]
|
|
|
+ if len(b.preparedID) == 0 {
|
|
|
+ f.writeByte(0)
|
|
|
+ f.writeLongString(b.statement)
|
|
|
+ continue
|
|
|
}
|
|
|
- } else if version == 1 {
|
|
|
- if len(op.Prepared) > 0 {
|
|
|
- f.writeShort(uint16(len(op.Values)))
|
|
|
- for _, value := range op.Values {
|
|
|
- f.writeBytes(value)
|
|
|
+
|
|
|
+ f.writeByte(1)
|
|
|
+ f.writeShortBytes(b.preparedID)
|
|
|
+ f.writeShort(uint16(len(b.values)))
|
|
|
+ for j := range b.values {
|
|
|
+ col := &b.values[j]
|
|
|
+ if f.proto > protoVersion2 && col.name != "" {
|
|
|
+ // TODO: move this check into the caller and set a flag on writeBatchFrame
|
|
|
+ // to indicate using named values
|
|
|
+ flags |= flagWithNameValues
|
|
|
+ f.writeString(col.name)
|
|
|
}
|
|
|
+ f.writeBytes(col.value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ f.writeConsistency(w.consistency)
|
|
|
+
|
|
|
+ if f.proto > protoVersion2 {
|
|
|
+ if w.serialConsistency > 0 {
|
|
|
+ flags |= flagWithSerialConsistency
|
|
|
+ }
|
|
|
+ if w.defaultTimestamp {
|
|
|
+ flags |= flagDefaultTimestamp
|
|
|
+ }
|
|
|
+
|
|
|
+ f.writeByte(flags)
|
|
|
+
|
|
|
+ if w.serialConsistency > 0 {
|
|
|
+ f.writeConsistency(w.serialConsistency)
|
|
|
+ }
|
|
|
+ if w.defaultTimestamp {
|
|
|
+ now := time.Now().UnixNano() / 1000
|
|
|
+ f.writeLong(now)
|
|
|
}
|
|
|
- f.writeConsistency(op.Cons)
|
|
|
}
|
|
|
|
|
|
- return f, nil
|
|
|
+ return f.finishWrite()
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readByte() byte {
|
|
|
+ b := f.rbuf[0]
|
|
|
+ f.rbuf = f.rbuf[1:]
|
|
|
+ return b
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readInt() (n int) {
|
|
|
+ n = int(int32(f.rbuf[0])<<24 | int32(f.rbuf[1])<<16 | int32(f.rbuf[2])<<8 | int32(f.rbuf[3]))
|
|
|
+ f.rbuf = f.rbuf[4:]
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readShort() (n uint16) {
|
|
|
+ n = uint16(f.rbuf[0])<<8 | uint16(f.rbuf[1])
|
|
|
+ f.rbuf = f.rbuf[2:]
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readLong() (n int64) {
|
|
|
+ n = int64(f.rbuf[0])<<56 | int64(f.rbuf[1])<<48 | int64(f.rbuf[2])<<40 | int64(f.rbuf[3])<<32 |
|
|
|
+ int64(f.rbuf[4])<<24 | int64(f.rbuf[5])<<16 | int64(f.rbuf[6])<<8 | int64(f.rbuf[7])
|
|
|
+ f.rbuf = f.rbuf[8:]
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readString() (s string) {
|
|
|
+ size := f.readShort()
|
|
|
+ s = string(f.rbuf[:size])
|
|
|
+ f.rbuf = f.rbuf[size:]
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readLongString() (s string) {
|
|
|
+ size := f.readInt()
|
|
|
+ s = string(f.rbuf[:size])
|
|
|
+ f.rbuf = f.rbuf[size:]
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readUUID() *UUID {
|
|
|
+ // TODO: how to handle this error, if it is a uuid, then sureley, problems?
|
|
|
+ u, _ := UUIDFromBytes(f.rbuf[:16])
|
|
|
+ f.rbuf = f.rbuf[16:]
|
|
|
+ return &u
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readStringList() []string {
|
|
|
+ size := f.readShort()
|
|
|
+
|
|
|
+ l := make([]string, size)
|
|
|
+ for i := 0; i < int(size); i++ {
|
|
|
+ l[i] = f.readString()
|
|
|
+ }
|
|
|
+
|
|
|
+ return l
|
|
|
}
|
|
|
|
|
|
-type prepareFrame struct {
|
|
|
- Stmt string
|
|
|
+func (f *framer) readBytes() []byte {
|
|
|
+ size := f.readInt()
|
|
|
+ if size < 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // we cant make assumptions about the length of the life of the supplied byte
|
|
|
+ // slice so we defensivly copy it out of the underlying buffer. This has the
|
|
|
+ // downside of increasing allocs per read but will provide much greater memory
|
|
|
+ // safety. The allocs can hopefully be improved in the future.
|
|
|
+ // TODO: dont copy into a new slice
|
|
|
+ l := make([]byte, size)
|
|
|
+ copy(l, f.rbuf[:size])
|
|
|
+ f.rbuf = f.rbuf[size:]
|
|
|
+
|
|
|
+ return l
|
|
|
}
|
|
|
|
|
|
-func (op *prepareFrame) String() string {
|
|
|
- return fmt.Sprintf("[prepare statement=%q]", op.Stmt)
|
|
|
+func (f *framer) readShortBytes() []byte {
|
|
|
+ size := f.readShort()
|
|
|
+
|
|
|
+ l := make([]byte, size)
|
|
|
+ copy(l, f.rbuf[:size])
|
|
|
+ f.rbuf = f.rbuf[size:]
|
|
|
+
|
|
|
+ return l
|
|
|
}
|
|
|
|
|
|
-func (op *prepareFrame) encodeFrame(version uint8, f frame) (frame, error) {
|
|
|
- if f == nil {
|
|
|
- f = newFrame(version)
|
|
|
+func (f *framer) readInet() (net.IP, int) {
|
|
|
+ size := f.rbuf[0]
|
|
|
+ f.rbuf = f.rbuf[1:]
|
|
|
+
|
|
|
+ if !(size == 4 || size == 16) {
|
|
|
+ panic(fmt.Sprintf("invalid IP size: %d", size))
|
|
|
}
|
|
|
- f.setHeader(version, 0, 0, opPrepare)
|
|
|
- f.writeLongString(op.Stmt)
|
|
|
- return f, nil
|
|
|
+
|
|
|
+ ip := make([]byte, size)
|
|
|
+ copy(ip, f.rbuf[:size])
|
|
|
+ f.rbuf = f.rbuf[size:]
|
|
|
+
|
|
|
+ port := f.readInt()
|
|
|
+ return net.IP(ip), port
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) readConsistency() Consistency {
|
|
|
+ return Consistency(f.readShort())
|
|
|
}
|
|
|
|
|
|
-type optionsFrame struct{}
|
|
|
+func (f *framer) readStringMap() map[string]string {
|
|
|
+ size := f.readShort()
|
|
|
+ m := make(map[string]string)
|
|
|
+
|
|
|
+ for i := 0; i < int(size); i++ {
|
|
|
+ k := f.readString()
|
|
|
+ v := f.readString()
|
|
|
+ m[k] = v
|
|
|
+ }
|
|
|
|
|
|
-func (op *optionsFrame) String() string {
|
|
|
- return "[options]"
|
|
|
+ return m
|
|
|
}
|
|
|
|
|
|
-func (op *optionsFrame) encodeFrame(version uint8, f frame) (frame, error) {
|
|
|
- if f == nil {
|
|
|
- f = newFrame(version)
|
|
|
+func (f *framer) readStringMultiMap() map[string][]string {
|
|
|
+ size := f.readShort()
|
|
|
+ m := make(map[string][]string)
|
|
|
+
|
|
|
+ for i := 0; i < int(size); i++ {
|
|
|
+ k := f.readString()
|
|
|
+ v := f.readStringList()
|
|
|
+ m[k] = v
|
|
|
}
|
|
|
- f.setHeader(version, 0, 0, opOptions)
|
|
|
- return f, nil
|
|
|
+
|
|
|
+ return m
|
|
|
}
|
|
|
|
|
|
-type authenticateFrame struct {
|
|
|
- Authenticator string
|
|
|
+func (f *framer) writeByte(b byte) {
|
|
|
+ f.wbuf = append(f.wbuf, b)
|
|
|
+}
|
|
|
+
|
|
|
+// these are protocol level binary types
|
|
|
+func (f *framer) writeInt(n int32) {
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ byte(n>>24),
|
|
|
+ byte(n>>16),
|
|
|
+ byte(n>>8),
|
|
|
+ byte(n),
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writeShort(n uint16) {
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ byte(n>>8),
|
|
|
+ byte(n),
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writeLong(n int64) {
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ byte(n>>56),
|
|
|
+ byte(n>>48),
|
|
|
+ byte(n>>40),
|
|
|
+ byte(n>>32),
|
|
|
+ byte(n>>24),
|
|
|
+ byte(n>>16),
|
|
|
+ byte(n>>8),
|
|
|
+ byte(n),
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
-func (op *authenticateFrame) String() string {
|
|
|
- return fmt.Sprintf("[authenticate authenticator=%q]", op.Authenticator)
|
|
|
+func (f *framer) writeString(s string) {
|
|
|
+ f.writeShort(uint16(len(s)))
|
|
|
+ f.wbuf = append(f.wbuf, s...)
|
|
|
}
|
|
|
|
|
|
-type authResponseFrame struct {
|
|
|
- Data []byte
|
|
|
+func (f *framer) writeLongString(s string) {
|
|
|
+ f.writeInt(int32(len(s)))
|
|
|
+ f.wbuf = append(f.wbuf, s...)
|
|
|
}
|
|
|
|
|
|
-func (op *authResponseFrame) String() string {
|
|
|
- return fmt.Sprintf("[auth_response data=%q]", op.Data)
|
|
|
+func (f *framer) writeUUID(u *UUID) {
|
|
|
+ f.wbuf = append(f.wbuf, u[:]...)
|
|
|
}
|
|
|
|
|
|
-func (op *authResponseFrame) encodeFrame(version uint8, f frame) (frame, error) {
|
|
|
- if f == nil {
|
|
|
- f = newFrame(version)
|
|
|
+func (f *framer) writeStringList(l []string) {
|
|
|
+ f.writeShort(uint16(len(l)))
|
|
|
+ for _, s := range l {
|
|
|
+ f.writeString(s)
|
|
|
}
|
|
|
- f.setHeader(version, 0, 0, opAuthResponse)
|
|
|
- f.writeBytes(op.Data)
|
|
|
- return f, nil
|
|
|
}
|
|
|
|
|
|
-type authSuccessFrame struct {
|
|
|
- Data []byte
|
|
|
+func (f *framer) writeBytes(p []byte) {
|
|
|
+ // TODO: handle null case correctly,
|
|
|
+ // [bytes] A [int] n, followed by n bytes if n >= 0. If n < 0,
|
|
|
+ // no byte should follow and the value represented is `null`.
|
|
|
+ if p == nil {
|
|
|
+ f.writeInt(-1)
|
|
|
+ } else {
|
|
|
+ f.writeInt(int32(len(p)))
|
|
|
+ f.wbuf = append(f.wbuf, p...)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-func (op *authSuccessFrame) String() string {
|
|
|
- return fmt.Sprintf("[auth_success data=%q]", op.Data)
|
|
|
+func (f *framer) writeShortBytes(p []byte) {
|
|
|
+ f.writeShort(uint16(len(p)))
|
|
|
+ f.wbuf = append(f.wbuf, p...)
|
|
|
}
|
|
|
|
|
|
-type authChallengeFrame struct {
|
|
|
- Data []byte
|
|
|
+func (f *framer) writeInet(ip net.IP, port int) {
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ byte(len(ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+ f.wbuf = append(f.wbuf,
|
|
|
+ []byte(ip)...,
|
|
|
+ )
|
|
|
+
|
|
|
+ f.writeInt(int32(port))
|
|
|
}
|
|
|
|
|
|
-func (op *authChallengeFrame) String() string {
|
|
|
- return fmt.Sprintf("[auth_challenge data=%q]", op.Data)
|
|
|
+func (f *framer) writeConsistency(cons Consistency) {
|
|
|
+ f.writeShort(uint16(cons))
|
|
|
+}
|
|
|
+
|
|
|
+func (f *framer) writeStringMap(m map[string]string) {
|
|
|
+ f.writeShort(uint16(len(m)))
|
|
|
+ for k, v := range m {
|
|
|
+ f.writeString(k)
|
|
|
+ f.writeString(v)
|
|
|
+ }
|
|
|
}
|