<commit_before>package styx

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"

	"aqwari.net/net/styx/internal/qidpool"
	"aqwari.net/net/styx/internal/styxfile"
	"aqwari.net/net/styx/internal/tracing"
	"aqwari.net/net/styx/internal/util"
	"aqwari.net/net/styx/styxproto"

	"golang.org/x/net/context"
)

var (
	errFidInUse     = errors.New("fid already in use")
	errTagInUse     = errors.New("tag in use")
	errNoFid        = errors.New("no such fid")
	errNotSupported = errors.New("not supported")
)

type fcall interface {
	styxproto.Msg
	Fid() uint32
}

// A note on identifiers (fids & tags)
//
// identifiers are chosen by the client, not by the server.  Therefore,
// it is important that the performance and behavior of a server does
// *not* change based on the fid or tag a client chooses. This is why
// a map is used; its performance is good, and doesn't change based
// on the input a client chooses (rather, it does not change in a way
// a client can predict).

// A conn receives and sends 9P messages across a single network connection.
// Multiple "sessions" may take place over a single connection. The conn
// struct contains the necessary information to route 9P messages to their
// established sessions.
type conn struct {
	// These wrap the network connection to read and write messages.
	*styxproto.Decoder
	*styxproto.Encoder

	// The Server a connection was spawned from. Contains configuration
	// settings and the authentication function, if any.
	srv *Server

	// The network connection itself. We expose it in the struct so that
	// it is available for transport-based auth and any timeouts we need
	// to implement.
	rwc io.ReadWriteCloser

	// This serves as the parent context for the context attached to all
	// requests.
	cx context.Context

	// While srv.MaxSize holds the *desired* 9P protocol message
	// size, msize will contain the actual maximum negotiated with
	// the client, through a Tversion/Rversion exchange.
	msize int64

	// There is no "session id" in 9P. However, because all fids
	// for a connection must be derived from the fid established
	// in a Tattach call, any message that contains a fid can be
	// traced back to the original Tattach message.
	sessionFid *util.Map

	// Qids for the file tree, added on-demand.
	qidpool *qidpool.Pool

	// used to implement request cancellation when a Tflush
	// message is received.
	pendingReq map[uint16]context.CancelFunc
}

func (c *conn) remoteAddr() net.Addr {
	type hasRemote interface {
		RemoteAddr() net.Addr
	}
	if nc, ok := c.rwc.(hasRemote); ok {
		return nc.RemoteAddr()
	}
	return nil
}

func (c *conn) sessionByFid(fid uint32) (*Session, bool) {
	if v, ok := c.sessionFid.Get(fid); ok {
		return v.(*Session), true
	}
	return nil, false
}

// Close the connection
func (c *conn) close() error {
	// Cancel all pending requests
	for tag, cancel := range c.pendingReq {
		cancel()
		delete(c.pendingReq, tag)
	}

	// Close all open files and sessions
	c.sessionFid.Do(func(m map[interface{}]interface{}) {
		seen := make(map[*Session]struct{}, len(m))
		for k, v := range m {
			session := v.(*Session)
			if _, ok := seen[session]; !ok {
				seen[session] = struct{}{}
				session.endSession()
			}
			// Should probably let the GC take care of this
			delete(m, k)
		}
	})

	return c.rwc.Close()
}

func newConn(srv *Server, rwc io.ReadWriteCloser) *conn {
	var msize int64 = styxproto.DefaultMaxSize
	if srv.MaxSize > 0 {
		if srv.MaxSize > styxproto.MinBufSize {
			msize = srv.MaxSize
		} else {
			msize = styxproto.MinBufSize
		}
	}
	var enc *styxproto.Encoder
	var dec *styxproto.Decoder
	if srv.TraceLog != nil {
		enc = tracing.Encoder(rwc, func(m styxproto.Msg) {
			srv.TraceLog.Printf("← %03d %s", m.Tag(), m)
		})
		dec = tracing.Decoder(rwc, func(m styxproto.Msg) {
			srv.TraceLog.Printf("→ %03d %s", m.Tag(), m)
		})
	} else {
		enc = styxproto.NewEncoder(rwc)
		dec = styxproto.NewDecoder(rwc)
	}
	return &conn{
		Decoder:    dec,
		Encoder:    enc,
		srv:        srv,
		rwc:        rwc,
		cx:         context.TODO(),
		msize:      msize,
		sessionFid: util.NewMap(),
		pendingReq: make(map[uint16]context.CancelFunc),
		qidpool:    qidpool.New(),
	}
}

func (c *conn) qid(name string, qtype uint8) styxproto.Qid {
	return c.qidpool.Put(name, qtype)
}

// All request contexts must have their cancel functions
// called, to free up resources in the context.
func (c *conn) clearTag(tag uint16) {
	if cancel, ok := c.pendingReq[tag]; ok {
		cancel()
		delete(c.pendingReq, tag)
	}
}

// runs in its own goroutine, one per connection.
func (c *conn) serve() {
	defer c.close()

	if !c.acceptTversion() {
		return
	}

Loop:
	for c.Next() {
		for _, m := range c.Messages() {
			if !c.handleMessage(m) {
				break Loop
			}
		}
	}
	c.srv.logf("closed connection from %s", c.remoteAddr())
}

func (c *conn) handleMessage(m styxproto.Msg) bool {
	if _, ok := c.pendingReq[m.Tag()]; ok {
		c.Rerror(m.Tag(), "%s", errTagInUse)
		return false
	}
	cx, cancel := context.WithCancel(c.cx)
	c.pendingReq[m.Tag()] = cancel

	switch m := m.(type) {
	case styxproto.Tauth:
		return c.handleTauth(cx, m)
	case styxproto.Tattach:
		return c.handleTattach(cx, m)
	case styxproto.Tflush:
		return c.handleTflush(cx, m)
	case fcall:
		return c.handleFcall(cx, m)
	case styxproto.BadMessage:
		c.srv.logf("got bad message from %s: %s", c.remoteAddr(), m.Err)
		c.Rerror(m.Tag(), "bad message: %s", m.Err)
		c.clearTag(m.Tag())
		return false
	default:
		c.Rerror(m.Tag(), "unexpected %T message", m)
		return false
	}
	return true
}

// This is the first thing we do on a new connection. The first
// message a client sends *must* be a Tversion message.
func (c *conn) acceptTversion() bool {
	c.Encoder.MaxSize = c.msize
	c.Decoder.MaxSize = c.msize

Loop:
	for c.Next() {
		for _, m := range c.Messages() {
			tver, ok := m.(styxproto.Tversion)
			if !ok {
				c.Rerror(m.Tag(), "need Tversion")
				break Loop
			}
			msize := tver.Msize()
			if msize < styxproto.MinBufSize {
				c.Rerror(m.Tag(), "buffer too small")
				break Loop
			}
			if msize < c.msize {
				c.msize = msize
				c.Encoder.MaxSize = msize
				c.Decoder.MaxSize = msize
			}
			if !bytes.HasPrefix(tver.Version(), []byte("9P2000")) {
				c.Rversion(uint32(c.msize), "unknown")
			}
			c.Rversion(uint32(c.msize), "9P2000")
			return true
		}
	}
	return false
}

// NOTE(droyo) consider a scenario where a malicious actor connects
// to the server that repeatedly spams Tauth requests. It can quickly
// use up resources on the server. Consider the following measures:
//
// - rate-limiting Tauth requests
// - Setting a per-connection session limit
// - close connections that have not established a session in N seconds
func (c *conn) handleTauth(cx context.Context, m styxproto.Tauth) bool {
	defer c.clearTag(m.Tag())
	if c.srv.Auth == nil {
		c.Rerror(m.Tag(), "%s", errNotSupported)
		return true
	}
	if _, ok := c.sessionFid.Get(m.Afid()); ok {
		c.Rerror(m.Tag(), "fid %x in use", m.Afid())
		return false
	}
	client, server := net.Pipe()
	ch := &Channel{
		Context:         c.cx,
		ReadWriteCloser: server,
	}
	rwc, err := styxfile.New(client)
	if err != nil {
		// This should never happen
		panic(err)
	}
	s := newSession(c, m)
	go func() {
		s.authC <- c.srv.Auth(ch, s.User, s.Access)
		close(s.authC)
	}()

	c.sessionFid.Put(m.Afid(), s)
	s.files.Put(m.Afid(), file{rwc: rwc, auth: true})
	s.IncRef()
	return true
}

func (c *conn) handleTattach(cx context.Context, m styxproto.Tattach) bool {
	defer c.clearTag(m.Tag())
	var handler Handler = DefaultServeMux
	if c.srv.Handler != nil {
		handler = c.srv.Handler
	}
	var s *Session
	if c.srv.Auth == nil {
		s = newSession(c, m)
	} else {
		// TODO(droyo) when a transport-based authentication scheme
		// is in use, the client should not have to do a Tauth request.
		// We should call the Auth handler if Afid is NOFID, passing it
		// a util.BlackHole.
		if !c.sessionFid.Fetch(s, m.Afid()) {
			c.Rerror(m.Tag(), "invalid afid %x", m.Afid())
			return false
		}
		// From attach(5): The same validated afid may be used for
		// multiple attach messages with the same uname and aname.
		if s.User != string(m.Uname()) || s.Access != string(m.Aname()) {
			c.Rerror(m.Tag(), "afid mismatch for %s on %s", m.Uname(), m.Aname())
			return false
		}
		if err := <-s.authC; err != nil {
			c.Rerror(m.Tag(), "auth failed: %s", err)
			return false
		}
	}
	go func() {
		handler.Serve9P(s)
		s.cleanupHandler()
	}()
	c.sessionFid.Put(m.Fid(), s)
	s.IncRef()
	s.files.Put(m.Fid(), file{name: "/", rwc: nil})
	c.Rattach(m.Tag(), c.qid(".", styxproto.QTDIR))
	return true
}

func (c *conn) handleTflush(cx context.Context, m styxproto.Tflush) bool {
	defer c.clearTag(m.Tag())

	oldtag := m.Oldtag()
	c.clearTag(oldtag)

	c.Rflush(m.Tag())
	return true
}

func (c *conn) handleFcall(cx context.Context, msg fcall) bool {
	s, ok := c.sessionByFid(msg.Fid())
	if !ok {
		c.Rerror(msg.Tag(), "%s", errNoFid)
		return false
	}

	file, ok := s.fetchFile(msg.Fid())
	if !ok {
		panic("bug: fid in session map, but no file associated")
		return false
	}

	// NOTE(droyo) on security and anonymous users: On a server with
	// authentication enabled, a client can only ever establish a handle
	// to the auth file.  At this point, we have checked that the fid
	// is valid, so *file can only be an auth file if the user has not
	// completed a Tattach.
	if file.auth {
		// Limit the number of request handlers we have to
		// audit.
		switch msg := msg.(type) {
		case styxproto.Twrite:
		case styxproto.Tread:
		case styxproto.Tstat:
		case styxproto.Tclunk:
		default:
			c.Rerror(msg.Tag(), "%T not allowed on afid", msg)
			c.clearTag(msg.Tag())
			return false
		}
	}

	switch msg := msg.(type) {
	case styxproto.Twalk:
		return s.handleTwalk(cx, msg, file)
	case styxproto.Topen:
		return s.handleTopen(cx, msg, file)
	case styxproto.Tcreate:
		return s.handleTcreate(cx, msg, file)
	case styxproto.Tread:
		return s.handleTread(cx, msg, file)
	case styxproto.Twrite:
		return s.handleTwrite(cx, msg, file)
	case styxproto.Tremove:
		return s.handleTremove(cx, msg, file)
	case styxproto.Tstat:
		return s.handleTstat(cx, msg, file)
	case styxproto.Twstat:
		return s.handleTwstat(cx, msg, file)
	case styxproto.Tclunk:
		return s.handleTclunk(cx, msg, file)
	}
	// invalid messages should have been caught
	// in the conn.serve loop, so we should never
	// reach this point.
	panic(fmt.Errorf("unhandled message type %T", msg))
}
<commit_msg>Don't close the connection on a malformed (but recoverable) message<commit_after>package styx

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"

	"aqwari.net/net/styx/internal/qidpool"
	"aqwari.net/net/styx/internal/styxfile"
	"aqwari.net/net/styx/internal/tracing"
	"aqwari.net/net/styx/internal/util"
	"aqwari.net/net/styx/styxproto"

	"golang.org/x/net/context"
)

var (
	errFidInUse     = errors.New("fid already in use")
	errTagInUse     = errors.New("tag in use")
	errNoFid        = errors.New("no such fid")
	errNotSupported = errors.New("not supported")
)

type fcall interface {
	styxproto.Msg
	Fid() uint32
}

// A note on identifiers (fids & tags)
//
// identifiers are chosen by the client, not by the server.  Therefore,
// it is important that the performance and behavior of a server does
// *not* change based on the fid or tag a client chooses. This is why
// a map is used; its performance is good, and doesn't change based
// on the input a client chooses (rather, it does not change in a way
// a client can predict).

// A conn receives and sends 9P messages across a single network connection.
// Multiple "sessions" may take place over a single connection. The conn
// struct contains the necessary information to route 9P messages to their
// established sessions.
type conn struct {
	// These wrap the network connection to read and write messages.
	*styxproto.Decoder
	*styxproto.Encoder

	// The Server a connection was spawned from. Contains configuration
	// settings and the authentication function, if any.
	srv *Server

	// The network connection itself. We expose it in the struct so that
	// it is available for transport-based auth and any timeouts we need
	// to implement.
	rwc io.ReadWriteCloser

	// This serves as the parent context for the context attached to all
	// requests.
	cx context.Context

	// While srv.MaxSize holds the *desired* 9P protocol message
	// size, msize will contain the actual maximum negotiated with
	// the client, through a Tversion/Rversion exchange.
	msize int64

	// There is no "session id" in 9P. However, because all fids
	// for a connection must be derived from the fid established
	// in a Tattach call, any message that contains a fid can be
	// traced back to the original Tattach message.
	sessionFid *util.Map

	// Qids for the file tree, added on-demand.
	qidpool *qidpool.Pool

	// used to implement request cancellation when a Tflush
	// message is received.
	pendingReq map[uint16]context.CancelFunc
}

func (c *conn) remoteAddr() net.Addr {
	type hasRemote interface {
		RemoteAddr() net.Addr
	}
	if nc, ok := c.rwc.(hasRemote); ok {
		return nc.RemoteAddr()
	}
	return nil
}

func (c *conn) sessionByFid(fid uint32) (*Session, bool) {
	if v, ok := c.sessionFid.Get(fid); ok {
		return v.(*Session), true
	}
	return nil, false
}

// Close the connection
func (c *conn) close() error {
	// Cancel all pending requests
	for tag, cancel := range c.pendingReq {
		cancel()
		delete(c.pendingReq, tag)
	}

	// Close all open files and sessions
	c.sessionFid.Do(func(m map[interface{}]interface{}) {
		seen := make(map[*Session]struct{}, len(m))
		for k, v := range m {
			session := v.(*Session)
			if _, ok := seen[session]; !ok {
				seen[session] = struct{}{}
				session.endSession()
			}
			// Should probably let the GC take care of this
			delete(m, k)
		}
	})

	return c.rwc.Close()
}

func newConn(srv *Server, rwc io.ReadWriteCloser) *conn {
	var msize int64 = styxproto.DefaultMaxSize
	if srv.MaxSize > 0 {
		if srv.MaxSize > styxproto.MinBufSize {
			msize = srv.MaxSize
		} else {
			msize = styxproto.MinBufSize
		}
	}
	var enc *styxproto.Encoder
	var dec *styxproto.Decoder
	if srv.TraceLog != nil {
		enc = tracing.Encoder(rwc, func(m styxproto.Msg) {
			srv.TraceLog.Printf("← %03d %s", m.Tag(), m)
		})
		dec = tracing.Decoder(rwc, func(m styxproto.Msg) {
			srv.TraceLog.Printf("→ %03d %s", m.Tag(), m)
		})
	} else {
		enc = styxproto.NewEncoder(rwc)
		dec = styxproto.NewDecoder(rwc)
	}
	return &conn{
		Decoder:    dec,
		Encoder:    enc,
		srv:        srv,
		rwc:        rwc,
		cx:         context.TODO(),
		msize:      msize,
		sessionFid: util.NewMap(),
		pendingReq: make(map[uint16]context.CancelFunc),
		qidpool:    qidpool.New(),
	}
}

func (c *conn) qid(name string, qtype uint8) styxproto.Qid {
	return c.qidpool.Put(name, qtype)
}

// All request contexts must have their cancel functions
// called, to free up resources in the context.
func (c *conn) clearTag(tag uint16) {
	if cancel, ok := c.pendingReq[tag]; ok {
		cancel()
		delete(c.pendingReq, tag)
	}
}

// runs in its own goroutine, one per connection.
func (c *conn) serve() {
	defer c.close()

	if !c.acceptTversion() {
		return
	}

Loop:
	for c.Next() {
		for _, m := range c.Messages() {
			if !c.handleMessage(m) {
				break Loop
			}
		}
	}
	c.srv.logf("closed connection from %s", c.remoteAddr())
}

func (c *conn) handleMessage(m styxproto.Msg) bool {
	if _, ok := c.pendingReq[m.Tag()]; ok {
		c.Rerror(m.Tag(), "%s", errTagInUse)
		return false
	}
	cx, cancel := context.WithCancel(c.cx)
	c.pendingReq[m.Tag()] = cancel

	switch m := m.(type) {
	case styxproto.Tauth:
		return c.handleTauth(cx, m)
	case styxproto.Tattach:
		return c.handleTattach(cx, m)
	case styxproto.Tflush:
		return c.handleTflush(cx, m)
	case fcall:
		return c.handleFcall(cx, m)
	case styxproto.BadMessage:
		c.srv.logf("got bad message from %s: %s", c.remoteAddr(), m.Err)
		c.Rerror(m.Tag(), "bad message: %s", m.Err)
		c.clearTag(m.Tag())
		return true
	default:
		c.Rerror(m.Tag(), "unexpected %T message", m)
		return false
	}
	return true
}

// This is the first thing we do on a new connection. The first
// message a client sends *must* be a Tversion message.
func (c *conn) acceptTversion() bool {
	c.Encoder.MaxSize = c.msize
	c.Decoder.MaxSize = c.msize

Loop:
	for c.Next() {
		for _, m := range c.Messages() {
			tver, ok := m.(styxproto.Tversion)
			if !ok {
				c.Rerror(m.Tag(), "need Tversion")
				break Loop
			}
			msize := tver.Msize()
			if msize < styxproto.MinBufSize {
				c.Rerror(m.Tag(), "buffer too small")
				break Loop
			}
			if msize < c.msize {
				c.msize = msize
				c.Encoder.MaxSize = msize
				c.Decoder.MaxSize = msize
			}
			if !bytes.HasPrefix(tver.Version(), []byte("9P2000")) {
				c.Rversion(uint32(c.msize), "unknown")
			}
			c.Rversion(uint32(c.msize), "9P2000")
			return true
		}
	}
	return false
}

// NOTE(droyo) consider a scenario where a malicious actor connects
// to the server that repeatedly spams Tauth requests. It can quickly
// use up resources on the server. Consider the following measures:
//
// - rate-limiting Tauth requests
// - Setting a per-connection session limit
// - close connections that have not established a session in N seconds
func (c *conn) handleTauth(cx context.Context, m styxproto.Tauth) bool {
	defer c.clearTag(m.Tag())
	if c.srv.Auth == nil {
		c.Rerror(m.Tag(), "%s", errNotSupported)
		return true
	}
	if _, ok := c.sessionFid.Get(m.Afid()); ok {
		c.Rerror(m.Tag(), "fid %x in use", m.Afid())
		return false
	}
	client, server := net.Pipe()
	ch := &Channel{
		Context:         c.cx,
		ReadWriteCloser: server,
	}
	rwc, err := styxfile.New(client)
	if err != nil {
		// This should never happen
		panic(err)
	}
	s := newSession(c, m)
	go func() {
		s.authC <- c.srv.Auth(ch, s.User, s.Access)
		close(s.authC)
	}()

	c.sessionFid.Put(m.Afid(), s)
	s.files.Put(m.Afid(), file{rwc: rwc, auth: true})
	s.IncRef()
	return true
}

func (c *conn) handleTattach(cx context.Context, m styxproto.Tattach) bool {
	defer c.clearTag(m.Tag())
	var handler Handler = DefaultServeMux
	if c.srv.Handler != nil {
		handler = c.srv.Handler
	}
	var s *Session
	if c.srv.Auth == nil {
		s = newSession(c, m)
	} else {
		// TODO(droyo) when a transport-based authentication scheme
		// is in use, the client should not have to do a Tauth request.
		// We should call the Auth handler if Afid is NOFID, passing it
		// a util.BlackHole.
		if !c.sessionFid.Fetch(s, m.Afid()) {
			c.Rerror(m.Tag(), "invalid afid %x", m.Afid())
			return false
		}
		// From attach(5): The same validated afid may be used for
		// multiple attach messages with the same uname and aname.
		if s.User != string(m.Uname()) || s.Access != string(m.Aname()) {
			c.Rerror(m.Tag(), "afid mismatch for %s on %s", m.Uname(), m.Aname())
			return false
		}
		if err := <-s.authC; err != nil {
			c.Rerror(m.Tag(), "auth failed: %s", err)
			return false
		}
	}
	go func() {
		handler.Serve9P(s)
		s.cleanupHandler()
	}()
	c.sessionFid.Put(m.Fid(), s)
	s.IncRef()
	s.files.Put(m.Fid(), file{name: "/", rwc: nil})
	c.Rattach(m.Tag(), c.qid(".", styxproto.QTDIR))
	return true
}

func (c *conn) handleTflush(cx context.Context, m styxproto.Tflush) bool {
	defer c.clearTag(m.Tag())

	oldtag := m.Oldtag()
	c.clearTag(oldtag)

	c.Rflush(m.Tag())
	return true
}

func (c *conn) handleFcall(cx context.Context, msg fcall) bool {
	s, ok := c.sessionByFid(msg.Fid())
	if !ok {
		c.Rerror(msg.Tag(), "%s", errNoFid)
		return false
	}

	file, ok := s.fetchFile(msg.Fid())
	if !ok {
		panic("bug: fid in session map, but no file associated")
		return false
	}

	// NOTE(droyo) on security and anonymous users: On a server with
	// authentication enabled, a client can only ever establish a handle
	// to the auth file.  At this point, we have checked that the fid
	// is valid, so *file can only be an auth file if the user has not
	// completed a Tattach.
	if file.auth {
		// Limit the number of request handlers we have to
		// audit.
		switch msg := msg.(type) {
		case styxproto.Twrite:
		case styxproto.Tread:
		case styxproto.Tstat:
		case styxproto.Tclunk:
		default:
			c.Rerror(msg.Tag(), "%T not allowed on afid", msg)
			c.clearTag(msg.Tag())
			return false
		}
	}

	switch msg := msg.(type) {
	case styxproto.Twalk:
		return s.handleTwalk(cx, msg, file)
	case styxproto.Topen:
		return s.handleTopen(cx, msg, file)
	case styxproto.Tcreate:
		return s.handleTcreate(cx, msg, file)
	case styxproto.Tread:
		return s.handleTread(cx, msg, file)
	case styxproto.Twrite:
		return s.handleTwrite(cx, msg, file)
	case styxproto.Tremove:
		return s.handleTremove(cx, msg, file)
	case styxproto.Tstat:
		return s.handleTstat(cx, msg, file)
	case styxproto.Twstat:
		return s.handleTwstat(cx, msg, file)
	case styxproto.Tclunk:
		return s.handleTclunk(cx, msg, file)
	}
	// invalid messages should have been caught
	// in the conn.serve loop, so we should never
	// reach this point.
	panic(fmt.Errorf("unhandled message type %T", msg))
}
<|endoftext|>


<commit_before>package rados

// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"

import "unsafe"
import "bytes"

type Conn struct {
    cluster C.rados_t
}

func (c *Conn) PingMonitor(id string) (string, error) {
    c_id := C.CString(id)
    defer C.free(unsafe.Pointer(c_id))

    var strlen C.size_t
    var strout *C.char

    ret := C.rados_ping_monitor(c.cluster, c_id, &strout, &strlen)
    defer C.rados_buffer_free(strout)

    if ret == 0 {
        reply := C.GoStringN(strout, (C.int)(strlen))
        return reply, nil
    } else {
        return "", RadosError(int(ret))
    }
}

// Connect establishes a connection to a RADOS cluster. It returns an error,
// if any.
func (c *Conn) Connect() error {
    ret := C.rados_connect(c.cluster)
    if ret == 0 {
        return nil
    } else {
        return RadosError(int(ret))
    }
}

func (c *Conn) Shutdown() {
    C.rados_shutdown(c.cluster)
}

func (c *Conn) ReadConfigFile(path string) error {
    c_path := C.CString(path)
    defer C.free(unsafe.Pointer(c_path))
    ret := C.rados_conf_read_file(c.cluster, c_path)
    if ret == 0 {
        return nil
    } else {
        return RadosError(int(ret))
    }
}

func (c *Conn) ReadDefaultConfigFile() error {
    ret := C.rados_conf_read_file(c.cluster, nil)
    if ret == 0 {
        return nil
    } else {
        return RadosError(int(ret))
    }
}

func (c *Conn) OpenPool(pool string) (*Pool, error) {
    c_pool := C.CString(pool)
    defer C.free(unsafe.Pointer(c_pool))
    ioctx := &Pool{}
    ret := C.rados_ioctx_create(c.cluster, c_pool, &ioctx.ioctx)
    if ret == 0 {
        return ioctx, nil
    } else {
        return nil, RadosError(int(ret))
    }
}

// ListPools returns the current list of pool names. It returns an error, if
// any.
func (c *Conn) ListPools() (names []string, err error) {
    buf := make([]byte, 4096)
    for {
        ret := int(C.rados_pool_list(c.cluster,
            (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
        if ret < 0 {
            return nil, RadosError(int(ret))
        }

        if ret > len(buf) {
            buf = make([]byte, ret)
            continue
        }

        tmp := bytes.SplitAfter(buf[:ret-1], []byte{0})
        for _, s := range tmp {
            if len(s) > 0 {
                name := C.GoString((*C.char)(unsafe.Pointer(&s[0])))
                names = append(names, name)
            }
        }

        return names, nil
    }
}

// SetConfigOption sets the configuration option named option to have the
// value value. It returns an error, if any.
func (c *Conn) SetConfigOption(option, value string) error {
    c_opt, c_val := C.CString(option), C.CString(value)
    defer C.free(unsafe.Pointer(c_opt))
    defer C.free(unsafe.Pointer(c_val))
    ret := C.rados_conf_set(c.cluster, c_opt, c_val)
    if ret < 0 {
        return RadosError(int(ret))
    } else {
        return nil
    }
}
<commit_msg>lib: implement WaitForLatestOSDMap<commit_after>package rados

// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"

import "unsafe"
import "bytes"

type Conn struct {
    cluster C.rados_t
}

func (c *Conn) PingMonitor(id string) (string, error) {
    c_id := C.CString(id)
    defer C.free(unsafe.Pointer(c_id))

    var strlen C.size_t
    var strout *C.char

    ret := C.rados_ping_monitor(c.cluster, c_id, &strout, &strlen)
    defer C.rados_buffer_free(strout)

    if ret == 0 {
        reply := C.GoStringN(strout, (C.int)(strlen))
        return reply, nil
    } else {
        return "", RadosError(int(ret))
    }
}

// Connect establishes a connection to a RADOS cluster. It returns an error,
// if any.
func (c *Conn) Connect() error {
    ret := C.rados_connect(c.cluster)
    if ret == 0 {
        return nil
    } else {
        return RadosError(int(ret))
    }
}

func (c *Conn) Shutdown() {
    C.rados_shutdown(c.cluster)
}

func (c *Conn) ReadConfigFile(path string) error {
    c_path := C.CString(path)
    defer C.free(unsafe.Pointer(c_path))
    ret := C.rados_conf_read_file(c.cluster, c_path)
    if ret == 0 {
        return nil
    } else {
        return RadosError(int(ret))
    }
}

func (c *Conn) ReadDefaultConfigFile() error {
    ret := C.rados_conf_read_file(c.cluster, nil)
    if ret == 0 {
        return nil
    } else {
        return RadosError(int(ret))
    }
}

func (c *Conn) OpenPool(pool string) (*Pool, error) {
    c_pool := C.CString(pool)
    defer C.free(unsafe.Pointer(c_pool))
    ioctx := &Pool{}
    ret := C.rados_ioctx_create(c.cluster, c_pool, &ioctx.ioctx)
    if ret == 0 {
        return ioctx, nil
    } else {
        return nil, RadosError(int(ret))
    }
}

// ListPools returns the current list of pool names. It returns an error, if
// any.
func (c *Conn) ListPools() (names []string, err error) {
    buf := make([]byte, 4096)
    for {
        ret := int(C.rados_pool_list(c.cluster,
            (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
        if ret < 0 {
            return nil, RadosError(int(ret))
        }

        if ret > len(buf) {
            buf = make([]byte, ret)
            continue
        }

        tmp := bytes.SplitAfter(buf[:ret-1], []byte{0})
        for _, s := range tmp {
            if len(s) > 0 {
                name := C.GoString((*C.char)(unsafe.Pointer(&s[0])))
                names = append(names, name)
            }
        }

        return names, nil
    }
}

// SetConfigOption sets the configuration option named option to have the
// value value. It returns an error, if any.
func (c *Conn) SetConfigOption(option, value string) error {
    c_opt, c_val := C.CString(option), C.CString(value)
    defer C.free(unsafe.Pointer(c_opt))
    defer C.free(unsafe.Pointer(c_val))
    ret := C.rados_conf_set(c.cluster, c_opt, c_val)
    if ret < 0 {
        return RadosError(int(ret))
    } else {
        return nil
    }
}

// WaitForLatestOSDMap blocks the caller until the latest OSD map has been
// retrieved. It returns an error, if any.
func (c *Conn) WaitForLatestOSDMap() error {
    ret := C.rados_wait_for_latest_osdmap(c.cluster)
    if ret < 0 {
        return RadosError(int(ret))
    } else {
        return nil
    }
}
<|endoftext|>


<commit_before>package neptulon

import (
	"io"
	"log"
	"net"
	"time"

	"github.com/neptulon/cmap"
	"github.com/neptulon/shortid"

	"golang.org/x/net/websocket"
)

// Conn is a client connection.
type Conn struct {
	ID         string
	Session    *cmap.CMap
	middleware []func(ctx *ReqCtx) error
	resRoutes  *cmap.CMap // message ID (string) -> handler func(ctx *ResCtx) error : expected responses for requests that we've sent
	ws         *websocket.Conn
	deadline   time.Duration
	closed     bool
}

// NewConn creates a new Conn object.
func NewConn() (*Conn, error) {
	id, err := shortid.UUID()
	if err != nil {
		return nil, err
	}

	return &Conn{
		ID:        id,
		Session:   cmap.New(),
		resRoutes: cmap.New(),
		deadline:  time.Second * time.Duration(300),
	}, nil
}

// SetDeadline set the read/write deadlines for the connection, in seconds.
// Default value for read/write deadline is 300 seconds.
func (c *Conn) SetDeadline(seconds int) {
	c.deadline = time.Second * time.Duration(seconds)
}

// Middleware registers middleware to handle incoming request messages.
func (c *Conn) Middleware(middleware ...func(ctx *ReqCtx) error) {
	c.middleware = append(c.middleware, middleware...)
}

// Connect connects to the given WebSocket server.
func (c *Conn) Connect(addr string) error {
	ws, err := websocket.Dial(addr, "", "http://localhost")
	if err != nil {
		return err
	}

	c.ws = ws
	go c.startReceive()
	time.Sleep(time.Millisecond) // give receive goroutine a few cycles to start
	return nil
}

// RemoteAddr returns the remote network address.
func (c *Conn) RemoteAddr() net.Addr {
	return c.ws.RemoteAddr()
}

// SendRequest sends a JSON-RPC request through the connection with an auto generated request ID.
// resHandler is called when a response is returned.
func (c *Conn) SendRequest(method string, params interface{}, resHandler func(res *ResCtx) error) (reqID string, err error) {
	id, err := shortid.UUID()
	if err != nil {
		return "", err
	}

	req := request{ID: id, Method: method, Params: params}
	if err = c.send(req); err != nil {
		return "", err
	}

	c.resRoutes.Set(req.ID, resHandler)
	return id, nil
}

// SendRequestArr sends a JSON-RPC request through the connection, with array params and auto generated request ID.
// resHandler is called when a response is returned.
func (c *Conn) SendRequestArr(method string, resHandler func(res *ResCtx) error, params ...interface{}) (reqID string, err error) {
	return c.SendRequest(method, params, resHandler)
}

// Close closes the connection.
func (c *Conn) Close() error {
	c.closed = true
	return c.ws.Close()
}

// SendResponse sends a JSON-RPC response message through the connection.
func (c *Conn) sendResponse(id string, result interface{}, err *ResError) error {
	return c.send(response{ID: id, Result: result, Error: err})
}

// Send sends the given message through the connection.
func (c *Conn) send(msg interface{}) error {
	if err := c.ws.SetWriteDeadline(time.Now().Add(c.deadline)); err != nil {
		return err
	}

	return websocket.JSON.Send(c.ws, msg)
}

// Receive receives message from the connection.
func (c *Conn) receive(msg *message) error {
	if err := c.ws.SetReadDeadline(time.Now().Add(c.deadline)); err != nil {
		return err
	}

	return websocket.JSON.Receive(c.ws, &msg)
}

// UseConn reuses an established websocket.Conn.
// This function blocks and does not return until the connection is closed by another goroutine.
func (c *Conn) useConn(ws *websocket.Conn) {
	c.ws = ws
	c.startReceive()
}

// startReceive starts receiving messages. This method blocks and does not return until the connection is closed.
func (c *Conn) startReceive() {
	// append the last middleware to request stack, which will write the response to connection, if any
	c.middleware = append(c.middleware, func(ctx *ReqCtx) error {
		if ctx.Res != nil || ctx.Err != nil {
			return ctx.Conn.sendResponse(ctx.ID, ctx.Res, ctx.Err)
		}

		return nil
	})

	for {
		var m message
		err := c.receive(&m)
		if err != nil {
			// if peer closed the connection
			if err == io.EOF {
				log.Printf("Peer disconnected. Conn ID: %v, Remote Addr: %v\n", c.ID, c.RemoteAddr())
				break
			}

			// if we closed the connection
			if c.closed {
				log.Printf("Connection closed. Conn ID: %v, Remote Addr: %v\n", c.ID, c.RemoteAddr())
				break
			}

			log.Println("Error while receiving message:", err)
			break
		}

		// if the message is a request
		if m.Method != "" {
			if err := newReqCtx(c, m.ID, m.Method, m.Params, c.middleware).Next(); err != nil {
				log.Println("Error while handling request:", err)
				break
			}

			continue
		}

		// if the message is a response
		if m.ID != "" && (m.Result != nil || m.Error != nil) {
			if resHandler, ok := c.resRoutes.GetOk(m.ID); ok {
				err := resHandler.(func(ctx *ResCtx) error)(newResCtx(c, m.ID, m.Result, m.Error))
				c.resRoutes.Delete(m.ID)
				if err != nil {
					log.Println("Error while handling response:", err)
					break
				}
			} else {
				log.Println("Error while handling response: got response to a request with unknown ID:", m.ID)
				break
			}
		}

		// if the message is not a JSON-RPC message
		log.Printf("Received on unknown message. Conn ID: %v, Remote Addr: %v\n", c.ID, c.RemoteAddr())
	}
}
<commit_msg>fix typo in unknown message handling<commit_after>package neptulon

import (
	"io"
	"log"
	"net"
	"time"

	"github.com/neptulon/cmap"
	"github.com/neptulon/shortid"

	"golang.org/x/net/websocket"
)

// Conn is a client connection.
type Conn struct {
	ID         string
	Session    *cmap.CMap
	middleware []func(ctx *ReqCtx) error
	resRoutes  *cmap.CMap // message ID (string) -> handler func(ctx *ResCtx) error : expected responses for requests that we've sent
	ws         *websocket.Conn
	deadline   time.Duration
	closed     bool
}

// NewConn creates a new Conn object.
func NewConn() (*Conn, error) {
	id, err := shortid.UUID()
	if err != nil {
		return nil, err
	}

	return &Conn{
		ID:        id,
		Session:   cmap.New(),
		resRoutes: cmap.New(),
		deadline:  time.Second * time.Duration(300),
	}, nil
}

// SetDeadline set the read/write deadlines for the connection, in seconds.
// Default value for read/write deadline is 300 seconds.
func (c *Conn) SetDeadline(seconds int) {
	c.deadline = time.Second * time.Duration(seconds)
}

// Middleware registers middleware to handle incoming request messages.
func (c *Conn) Middleware(middleware ...func(ctx *ReqCtx) error) {
	c.middleware = append(c.middleware, middleware...)
}

// Connect connects to the given WebSocket server.
func (c *Conn) Connect(addr string) error {
	ws, err := websocket.Dial(addr, "", "http://localhost")
	if err != nil {
		return err
	}

	c.ws = ws
	go c.startReceive()
	time.Sleep(time.Millisecond) // give receive goroutine a few cycles to start
	return nil
}

// RemoteAddr returns the remote network address.
func (c *Conn) RemoteAddr() net.Addr {
	return c.ws.RemoteAddr()
}

// SendRequest sends a JSON-RPC request through the connection with an auto generated request ID.
// resHandler is called when a response is returned.
func (c *Conn) SendRequest(method string, params interface{}, resHandler func(res *ResCtx) error) (reqID string, err error) {
	id, err := shortid.UUID()
	if err != nil {
		return "", err
	}

	req := request{ID: id, Method: method, Params: params}
	if err = c.send(req); err != nil {
		return "", err
	}

	c.resRoutes.Set(req.ID, resHandler)
	return id, nil
}

// SendRequestArr sends a JSON-RPC request through the connection, with array params and auto generated request ID.
// resHandler is called when a response is returned.
func (c *Conn) SendRequestArr(method string, resHandler func(res *ResCtx) error, params ...interface{}) (reqID string, err error) {
	return c.SendRequest(method, params, resHandler)
}

// Close closes the connection.
func (c *Conn) Close() error {
	c.closed = true
	return c.ws.Close()
}

// SendResponse sends a JSON-RPC response message through the connection.
func (c *Conn) sendResponse(id string, result interface{}, err *ResError) error {
	return c.send(response{ID: id, Result: result, Error: err})
}

// Send sends the given message through the connection.
func (c *Conn) send(msg interface{}) error {
	if err := c.ws.SetWriteDeadline(time.Now().Add(c.deadline)); err != nil {
		return err
	}

	return websocket.JSON.Send(c.ws, msg)
}

// Receive receives message from the connection.
func (c *Conn) receive(msg *message) error {
	if err := c.ws.SetReadDeadline(time.Now().Add(c.deadline)); err != nil {
		return err
	}

	return websocket.JSON.Receive(c.ws, &msg)
}

// UseConn reuses an established websocket.Conn.
// This function blocks and does not return until the connection is closed by another goroutine.
func (c *Conn) useConn(ws *websocket.Conn) {
	c.ws = ws
	c.startReceive()
}

// startReceive starts receiving messages. This method blocks and does not return until the connection is closed.
func (c *Conn) startReceive() {
	// append the last middleware to request stack, which will write the response to connection, if any
	c.middleware = append(c.middleware, func(ctx *ReqCtx) error {
		if ctx.Res != nil || ctx.Err != nil {
			return ctx.Conn.sendResponse(ctx.ID, ctx.Res, ctx.Err)
		}

		return nil
	})

	for {
		var m message
		err := c.receive(&m)
		if err != nil {
			// if peer closed the connection
			if err == io.EOF {
				log.Printf("Peer disconnected. Conn ID: %v, Remote Addr: %v\n", c.ID, c.RemoteAddr())
				break
			}

			// if we closed the connection
			if c.closed {
				log.Printf("Connection closed. Conn ID: %v, Remote Addr: %v\n", c.ID, c.RemoteAddr())
				break
			}

			log.Println("Error while receiving message:", err)
			break
		}

		// if the message is a request
		if m.Method != "" {
			if err := newReqCtx(c, m.ID, m.Method, m.Params, c.middleware).Next(); err != nil {
				log.Println("Error while handling request:", err)
				break
			}

			continue
		}

		// if the message is not a JSON-RPC message
		if m.ID == "" || (m.Result == nil && m.Error == nil) {
			log.Printf("Received an unknown message. Conn ID: %v, Remote Addr: %v\n", c.ID, c.RemoteAddr())
		}

		// if the message is a response
		if resHandler, ok := c.resRoutes.GetOk(m.ID); ok {
			err := resHandler.(func(ctx *ResCtx) error)(newResCtx(c, m.ID, m.Result, m.Error))
			c.resRoutes.Delete(m.ID)
			if err != nil {
				log.Println("Error while handling response:", err)
				break
			}
		} else {
			log.Println("Error while handling response: got response to a request with unknown ID:", m.ID)
			break
		}
	}
}
<|endoftext|>


<commit_before>package imap

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
)

type connState int

const (
	stateNew connState = iota
	stateNotAuthenticated
	stateAuthenticated
	stateSelected
	stateLoggedOut
)

const lineEnding string = "\r\n"

// Conn represents a client connection to the IMAP server
type Conn struct {
	state           connState
	srv             *Server // Pointer to the IMAP server to which this connection belongs
	rwc             net.Conn
	Transcript      io.Writer
	recvReq         chan string
	user            User
	selectedMailbox Mailbox
}

func (c *Conn) setState(state connState) {
	c.state = state
}

func (c *Conn) handleRequest(req string) {
	for _, cmd := range commands {
		matches := cmd.match.FindStringSubmatch(req)
		if len(matches) > 0 {
			cmd.handler(matches, c)
			return
		}
	}

	c.writeResponse("", "BAD Command not understood")
}

func (c *Conn) Write(p []byte) (n int, err error) {
	fmt.Fprintf(c.Transcript, "S: %s", p)

	return c.rwc.Write(p)
}

// Write a response to the client
func (c *Conn) writeResponse(seq string, command string) {
	if seq == "" {
		seq = "*"
	}
	// Ensure the command is terminated with a line ending
	if !strings.HasSuffix(command, lineEnding) {
		command += lineEnding
	}
	fmt.Fprintf(c, "%s %s", seq, command)
}

// Send the server greeting to the client
func (c *Conn) sendWelcome() error {
	if c.state != stateNew {
		return errors.New("Welcome already sent")
	}
	c.writeResponse("", "OK IMAP4rev1 Service Ready")
	c.setState(stateNotAuthenticated)
	return nil
}

// Close forces the server to close the client's connection
func (c *Conn) Close() error {
	fmt.Fprintf(c.Transcript, "Server closing connection\n")
	return c.rwc.Close()
}

// Start tells the server to start communicating with the client (after
// the connection has been opened)
func (c *Conn) Start() error {
	if c.rwc == nil {
		return errors.New("No connection exists")
	}

	c.recvReq = make(chan string)

	go func(ch chan string) {
		scanner := bufio.NewScanner(c.rwc)
		for ok := scanner.Scan(); ok == true; ok = scanner.Scan() {
			text := scanner.Text()
			ch <- text
		}
		fmt.Fprintf(c.Transcript, "Client ended connection\n")
		close(ch)

	}(c.recvReq)

	for {
		// Always send welcome message if we are still in new connection state
		if c.state == stateNew {
			c.sendWelcome()
		}

		// Await requests from the client
		select {
		case req, ok := <-c.recvReq: // receive line of data from client
			if ok == false {
				return nil
			}
			fmt.Fprintf(c.Transcript, "C: %s\n", req)
			c.handleRequest(req)
		}
	}

	return nil
}
<commit_msg>Break out of for loop once the state transitions to logged out<commit_after>package imap

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
)

type connState int

const (
	stateNew connState = iota
	stateNotAuthenticated
	stateAuthenticated
	stateSelected
	stateLoggedOut
)

const lineEnding string = "\r\n"

// Conn represents a client connection to the IMAP server
type Conn struct {
	state           connState
	srv             *Server // Pointer to the IMAP server to which this connection belongs
	rwc             net.Conn
	Transcript      io.Writer
	recvReq         chan string
	user            User
	selectedMailbox Mailbox
}

func (c *Conn) setState(state connState) {
	c.state = state
}

func (c *Conn) handleRequest(req string) {
	for _, cmd := range commands {
		matches := cmd.match.FindStringSubmatch(req)
		if len(matches) > 0 {
			cmd.handler(matches, c)
			return
		}
	}

	c.writeResponse("", "BAD Command not understood")
}

func (c *Conn) Write(p []byte) (n int, err error) {
	fmt.Fprintf(c.Transcript, "S: %s", p)

	return c.rwc.Write(p)
}

// Write a response to the client
func (c *Conn) writeResponse(seq string, command string) {
	if seq == "" {
		seq = "*"
	}
	// Ensure the command is terminated with a line ending
	if !strings.HasSuffix(command, lineEnding) {
		command += lineEnding
	}
	fmt.Fprintf(c, "%s %s", seq, command)
}

// Send the server greeting to the client
func (c *Conn) sendWelcome() error {
	if c.state != stateNew {
		return errors.New("Welcome already sent")
	}
	c.writeResponse("", "OK IMAP4rev1 Service Ready")
	c.setState(stateNotAuthenticated)
	return nil
}

// Close forces the server to close the client's connection
func (c *Conn) Close() error {
	fmt.Fprintf(c.Transcript, "Server closing connection\n")
	return c.rwc.Close()
}

// Start tells the server to start communicating with the client (after
// the connection has been opened)
func (c *Conn) Start() error {
	if c.rwc == nil {
		return errors.New("No connection exists")
	}

	c.recvReq = make(chan string)

	go func(ch chan string) {
		scanner := bufio.NewScanner(c.rwc)
		for ok := scanner.Scan(); ok == true; ok = scanner.Scan() {
			text := scanner.Text()
			ch <- text
		}
		fmt.Fprintf(c.Transcript, "Client ended connection\n")
		close(ch)

	}(c.recvReq)

	for c.state != stateLoggedOut {
		// Always send welcome message if we are still in new connection state
		if c.state == stateNew {
			c.sendWelcome()
		}

		// Await requests from the client
		select {
		case req, ok := <-c.recvReq: // receive line of data from client
			if !ok {
				// The client has closed the connection
				c.state = stateLoggedOut
				break
			}
			fmt.Fprintf(c.Transcript, "C: %s\n", req)
			c.handleRequest(req)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package prestgo

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Name of the driver to use when calling `sql.Open`
const DriverName = "prestgo"

// Default data source parameters
const (
	DefaultPort    = "8080"
	DefaultCatalog = "hive"
	DefaultSchema  = "default"
)

var (
	// ErrNotSupported is returned when an unsupported feature is requested.
	ErrNotSupported = errors.New(DriverName + ": not supported")

	// ErrQueryFailed indicates that a network or server failure prevented the driver obtaining a query result.
	ErrQueryFailed = errors.New(DriverName + ": query failed")
)

func init() {
	sql.Register(DriverName, &drv{})
}

type drv struct{}

func (*drv) Open(name string) (driver.Conn, error) {
	return Open(name)
}

// Open creates a connection to the specified data source name which should be
// of the form "presto://hostname:port/catalog/schema". http.DefaultClient will
// be used for communicationg with the Presto server.
func Open(name string) (driver.Conn, error) {
	return ClientOpen(http.DefaultClient, name)
}

// ClientOpen creates a connection to the specified data source name using the supplied
// HTTP client. The data source name should be of the form
// "presto://hostname:port/catalog/schema".
func ClientOpen(client *http.Client, name string) (driver.Conn, error) {

	conf := make(config)
	conf.parseDataSource(name)

	cn := &conn{
		client:  client,
		addr:    conf["addr"],
		catalog: conf["catalog"],
		schema:  conf["schema"],
	}
	return cn, nil
}

type conn struct {
	client  *http.Client
	addr    string
	catalog string
	schema  string
}

var _ driver.Conn = &conn{}

func (c *conn) Prepare(query string) (driver.Stmt, error) {
	st := &stmt{
		conn:  c,
		query: query,
	}
	return st, nil
}

func (c *conn) Close() error {
	return nil
}

func (c *conn) Begin() (driver.Tx, error) {
	return nil, ErrNotSupported
}

type stmt struct {
	conn  *conn
	query string
}

var _ driver.Stmt = &stmt{}

func (s *stmt) Close() error {
	return nil
}

func (s *stmt) NumInput() int {
	return -1 // TODO: parse query for parameters
}

func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
	return nil, ErrNotSupported
}

func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
	// TODO: support query argument substitution
	if len(args) > 0 {
		return nil, ErrNotSupported
	}
	queryURL := fmt.Sprintf("http://%s/v1/statement", s.conn.addr)

	req, err := http.NewRequest("POST", queryURL, strings.NewReader(s.query))
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Presto-User", "default")
	req.Header.Add("X-Presto-Catalog", s.conn.catalog)
	req.Header.Add("X-Presto-Schema", s.conn.schema)

	resp, err := s.conn.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Presto doesn't use the http response code, parse errors come back as 200
	if resp.StatusCode != 200 {
		return nil, ErrQueryFailed
	}

	var sresp stmtResponse
	err = json.NewDecoder(resp.Body).Decode(&sresp)
	if err != nil {
		return nil, err
	}

	if sresp.Stats.State == "FAILED" {
		return nil, sresp.Error
	}

	r := &rows{
		conn:    s.conn,
		nextURI: sresp.NextURI,
	}

	return r, nil
}

type rows struct {
	conn     *conn
	nextURI  string
	fetched  bool
	rowindex int
	columns  []string
	types    []driver.ValueConverter
	data     []queryData
}

var _ driver.Rows = &rows{}

func (r *rows) fetch() error {
	nextReq, err := http.NewRequest("GET", r.nextURI, nil)
	if err != nil {
		return err
	}

	nextResp, err := r.conn.client.Do(nextReq)
	if err != nil {
		return err
	}

	if nextResp.StatusCode != 200 {
		nextResp.Body.Close()
		return ErrQueryFailed
	}

	var qresp queryResponse
	err = json.NewDecoder(nextResp.Body).Decode(&qresp)
	nextResp.Body.Close()
	if err != nil {
		return err
	}

	if qresp.Stats.State == "FAILED" {
		return qresp.Error
	}

	r.rowindex = 0
	r.data = qresp.Data

	// Note: qresp.Stats.State will be FINISHED when last page is retrieved
	r.nextURI = qresp.NextURI

	if !r.fetched {
		r.fetched = true
		r.columns = make([]string, len(qresp.Columns))
		r.types = make([]driver.ValueConverter, len(qresp.Columns))
		for i, col := range qresp.Columns {
			r.columns[i] = col.Name
			switch col.Type {
			case VarChar:
				r.types[i] = driver.String
			case BigInt:
				r.types[i] = bigIntConverter
			case Boolean:
				r.types[i] = driver.Bool
			case Double:
				r.types[i] = doubleConverter
			case Timestamp:
				r.types[i] = timestampConverter

			default:
				return fmt.Errorf("unsupported column type: %s", col.Type)
			}
		}
	}

	if len(qresp.Data) == 0 {
		return io.EOF
	}

	return nil
}

func (r *rows) Columns() []string {
	if !r.fetched {
		r.fetch()
	}
	return r.columns
}

func (r *rows) Close() error {
	return nil
}

func (r *rows) Next(dest []driver.Value) error {
	if !r.fetched || r.rowindex >= len(r.data) {
		if r.nextURI == "" {
			return io.EOF
		}
		if err := r.fetch(); err != nil {
			return err
		}
	}

	for i, v := range r.types {
		val, err := v.ConvertValue(r.data[r.rowindex][i])
		if err != nil {
			return err // TODO: more context in error
		}
		dest[i] = val
	}
	r.rowindex++
	return nil
}

type config map[string]string

func (c config) parseDataSource(ds string) error {
	u, err := url.Parse(ds)
	if err != nil {
		return err
	}

	if strings.IndexRune(u.Host, ':') == -1 {
		c["addr"] = u.Host + ":" + DefaultPort
	} else {
		c["addr"] = u.Host
	}

	c["catalog"] = DefaultCatalog
	c["schema"] = DefaultSchema

	pathSegments := strings.FieldsFunc(u.Path, func(c rune) bool { return c == '/' })
	if len(pathSegments) > 0 {
		c["catalog"] = pathSegments[0]
	}
	if len(pathSegments) > 1 {
		c["schema"] = pathSegments[1]
	}
	return nil
}

type valueConverterFunc func(v interface{}) (driver.Value, error)

func (fn valueConverterFunc) ConvertValue(v interface{}) (driver.Value, error) {
	return fn(v)
}

// bigIntConverter converts a value from the underlying json response into an int64.
// The Go JSON decoder uses float64 for generic numeric values
var bigIntConverter = valueConverterFunc(func(val interface{}) (driver.Value, error) {
	if vv, ok := val.(float64); ok {
		return int64(vv), nil
	}
	return nil, fmt.Errorf("%s: failed to convert %v (%T) into type int64", DriverName, val, val)
})

// doubleConverter converts a value from the underlying json response into an int64.
// The Go JSON decoder uses float64 for generic numeric values
var doubleConverter = valueConverterFunc(func(val interface{}) (driver.Value, error) {
	switch vv := val.(type) {
	case float64:
		return vv, nil
	case string:
		switch vv {
		case "Infinity":
			return math.Inf(1), nil
		case "NaN":
			return math.NaN(), nil
		}

	}
	return nil, fmt.Errorf("%s: failed to convert %v (%T) into type float64", DriverName, val, val)
})

// timestampConverter converts a value from the underlying json response into a time.Time.
var timestampConverter = valueConverterFunc(func(val interface{}) (driver.Value, error) {
	if vv, ok := val.(string); ok {
		// BUG: should parse using session time zone.
		ts, err := time.ParseInLocation("2006-01-02 15:04:05.000", vv, time.Local)
		return ts, err
	}
	return nil, fmt.Errorf("%s: failed to convert %v (%T) into type time.Time", DriverName, val, val)
})
<commit_msg>wait for data to be available<commit_after>package prestgo

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Name of the driver to use when calling `sql.Open`
const DriverName = "prestgo"

// Default data source parameters
const (
	DefaultPort    = "8080"
	DefaultCatalog = "hive"
	DefaultSchema  = "default"
)

var (
	// ErrNotSupported is returned when an unsupported feature is requested.
	ErrNotSupported = errors.New(DriverName + ": not supported")

	// ErrQueryFailed indicates that a network or server failure prevented the driver obtaining a query result.
	ErrQueryFailed = errors.New(DriverName + ": query failed")
)

func init() {
	sql.Register(DriverName, &drv{})
}

type drv struct{}

func (*drv) Open(name string) (driver.Conn, error) {
	return Open(name)
}

// Open creates a connection to the specified data source name which should be
// of the form "presto://hostname:port/catalog/schema". http.DefaultClient will
// be used for communicationg with the Presto server.
func Open(name string) (driver.Conn, error) {
	return ClientOpen(http.DefaultClient, name)
}

// ClientOpen creates a connection to the specified data source name using the supplied
// HTTP client. The data source name should be of the form
// "presto://hostname:port/catalog/schema".
func ClientOpen(client *http.Client, name string) (driver.Conn, error) {

	conf := make(config)
	conf.parseDataSource(name)

	cn := &conn{
		client:  client,
		addr:    conf["addr"],
		catalog: conf["catalog"],
		schema:  conf["schema"],
	}
	return cn, nil
}

type conn struct {
	client  *http.Client
	addr    string
	catalog string
	schema  string
}

var _ driver.Conn = &conn{}

func (c *conn) Prepare(query string) (driver.Stmt, error) {
	st := &stmt{
		conn:  c,
		query: query,
	}
	return st, nil
}

func (c *conn) Close() error {
	return nil
}

func (c *conn) Begin() (driver.Tx, error) {
	return nil, ErrNotSupported
}

type stmt struct {
	conn  *conn
	query string
}

var _ driver.Stmt = &stmt{}

func (s *stmt) Close() error {
	return nil
}

func (s *stmt) NumInput() int {
	return -1 // TODO: parse query for parameters
}

func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
	return nil, ErrNotSupported
}

func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
	// TODO: support query argument substitution
	if len(args) > 0 {
		return nil, ErrNotSupported
	}
	queryURL := fmt.Sprintf("http://%s/v1/statement", s.conn.addr)

	req, err := http.NewRequest("POST", queryURL, strings.NewReader(s.query))
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Presto-User", "default")
	req.Header.Add("X-Presto-Catalog", s.conn.catalog)
	req.Header.Add("X-Presto-Schema", s.conn.schema)

	resp, err := s.conn.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Presto doesn't use the http response code, parse errors come back as 200
	if resp.StatusCode != 200 {
		return nil, ErrQueryFailed
	}

	var sresp stmtResponse
	err = json.NewDecoder(resp.Body).Decode(&sresp)
	if err != nil {
		return nil, err
	}

	if sresp.Stats.State == "FAILED" {
		return nil, sresp.Error
	}

	r := &rows{
		conn:    s.conn,
		nextURI: sresp.NextURI,
	}

	return r, nil
}

type rows struct {
	conn     *conn
	nextURI  string
	fetched  bool
	rowindex int
	columns  []string
	types    []driver.ValueConverter
	data     []queryData
}

var _ driver.Rows = &rows{}

func (r *rows) fetch() error {
	var qresp queryResponse
	for {
		nextReq, err := http.NewRequest("GET", r.nextURI, nil)
		if err != nil {
			return err
		}

		nextResp, err := r.conn.client.Do(nextReq)
		if err != nil {
			return err
		}

		if nextResp.StatusCode != 200 {
			nextResp.Body.Close()
			return ErrQueryFailed
		}

		err = json.NewDecoder(nextResp.Body).Decode(&qresp)
		nextResp.Body.Close()
		if err != nil {
			return err
		}

		if qresp.Stats.State == "FAILED" {
			return qresp.Error
		}

		if qresp.Stats.State == "RUNNING" || qresp.Stats.State == "FINISHED" {
			break
		}

		time.Sleep(5 * time.Millisecond)
	}
	r.rowindex = 0
	r.data = qresp.Data

	// Note: qresp.Stats.State will be FINISHED when last page is retrieved
	r.nextURI = qresp.NextURI

	if !r.fetched {
		r.fetched = true
		r.columns = make([]string, len(qresp.Columns))
		r.types = make([]driver.ValueConverter, len(qresp.Columns))
		for i, col := range qresp.Columns {
			r.columns[i] = col.Name
			switch col.Type {
			case VarChar:
				r.types[i] = driver.String
			case BigInt:
				r.types[i] = bigIntConverter
			case Boolean:
				r.types[i] = driver.Bool
			case Double:
				r.types[i] = doubleConverter
			case Timestamp:
				r.types[i] = timestampConverter

			default:
				return fmt.Errorf("unsupported column type: %s", col.Type)
			}
		}
	}

	if len(qresp.Data) == 0 {
		return io.EOF
	}

	return nil
}

func (r *rows) Columns() []string {
	if !r.fetched {
		r.fetch()
	}
	return r.columns
}

func (r *rows) Close() error {
	return nil
}

func (r *rows) Next(dest []driver.Value) error {
	if !r.fetched || r.rowindex >= len(r.data) {
		if r.nextURI == "" {
			return io.EOF
		}
		if err := r.fetch(); err != nil {
			return err
		}
	}

	for i, v := range r.types {
		val, err := v.ConvertValue(r.data[r.rowindex][i])
		if err != nil {
			return err // TODO: more context in error
		}
		dest[i] = val
	}
	r.rowindex++
	return nil
}

type config map[string]string

func (c config) parseDataSource(ds string) error {
	u, err := url.Parse(ds)
	if err != nil {
		return err
	}

	if strings.IndexRune(u.Host, ':') == -1 {
		c["addr"] = u.Host + ":" + DefaultPort
	} else {
		c["addr"] = u.Host
	}

	c["catalog"] = DefaultCatalog
	c["schema"] = DefaultSchema

	pathSegments := strings.FieldsFunc(u.Path, func(c rune) bool { return c == '/' })
	if len(pathSegments) > 0 {
		c["catalog"] = pathSegments[0]
	}
	if len(pathSegments) > 1 {
		c["schema"] = pathSegments[1]
	}
	return nil
}

type valueConverterFunc func(v interface{}) (driver.Value, error)

func (fn valueConverterFunc) ConvertValue(v interface{}) (driver.Value, error) {
	return fn(v)
}

// bigIntConverter converts a value from the underlying json response into an int64.
// The Go JSON decoder uses float64 for generic numeric values
var bigIntConverter = valueConverterFunc(func(val interface{}) (driver.Value, error) {
	if vv, ok := val.(float64); ok {
		return int64(vv), nil
	}
	return nil, fmt.Errorf("%s: failed to convert %v (%T) into type int64", DriverName, val, val)
})

// doubleConverter converts a value from the underlying json response into an int64.
// The Go JSON decoder uses float64 for generic numeric values
var doubleConverter = valueConverterFunc(func(val interface{}) (driver.Value, error) {
	switch vv := val.(type) {
	case float64:
		return vv, nil
	case string:
		switch vv {
		case "Infinity":
			return math.Inf(1), nil
		case "NaN":
			return math.NaN(), nil
		}

	}
	return nil, fmt.Errorf("%s: failed to convert %v (%T) into type float64", DriverName, val, val)
})

// timestampConverter converts a value from the underlying json response into a time.Time.
var timestampConverter = valueConverterFunc(func(val interface{}) (driver.Value, error) {
	if vv, ok := val.(string); ok {
		// BUG: should parse using session time zone.
		ts, err := time.ParseInLocation("2006-01-02 15:04:05.000", vv, time.Local)
		return ts, err
	}
	return nil, fmt.Errorf("%s: failed to convert %v (%T) into type time.Time", DriverName, val, val)
})
<|endoftext|>


<commit_before>package cqlb

import (
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/gocql/gocql"
	"github.com/jinzhu/inflection"
	"github.com/relops/cqlr"
)

const (
	insertQueryTemplate = "insert into %s (%s) values(%s);"
	whereQueryTemplate  = "select %s from %s %s %s %s;"
)

type fieldTag struct {
	Name      string
	OmitEmpty bool
}

type Session struct {
	s              *gocql.Session
	query          interface{}
	args           []interface{}
	sel            []string
	limit          int
	allowFiltering bool
	consistency    gocql.Consistency
	value          reflect.Value
	indirect       reflect.Value
	tableName      string
}

func SetSession(s *gocql.Session) *Session {
	return &Session{s: s}
}

func (s *Session) AllowFiltering(b bool) *Session {
	c := s.clone()
	c.allowFiltering = b
	return c
}

func (s *Session) Consistency(consistency gocql.Consistency) *Session {
	c := s.clone()
	c.consistency = consistency
	return c
}

func (s *Session) Find(slice interface{}) error {
	// var fields map[string]interface{}
	// var fieldsToScan []interface{}
	// value := reflect.ValueOf(slice)
	// k := value.Kind()
	// if k != reflect.Slice {
	// 	return errors.New("value should be a slice.")
	// }
	// v := value.Index(0)
	// indirect := reflect.Indirect(v)
	// s.setModel(v)
	// query := s.query
	// vq := reflect.ValueOf(query)
	// kindQuery := vq.Kind()
	// switch kindQuery {
	// case reflect.Map:
	// 	fields = whereFieldsFromMap(query)
	// }
	// iter := s.s.Query(s.whereQuery(fields), values).Iter()
	// cols := iter.Columns()
	// values := make([]interface{}, len(cols))
	// names := f["names"].([]string)
	// for i, col := range cols {
	// 	values[i] = f["strategies"].(map[string]interface{})[col.Name]
	// }
	return nil
}

func (s *Session) Insert(v interface{}) error {
	f := fields(v)
	stmt := insertQuery(f)
	fmt.Println("fields", f)
	return s.s.Query(stmt, f["values"].([]interface{})...).Exec()
}

func (s *Session) Iter(value interface{}) *gocql.Iter {
	c := s.clone()
	var fields map[string]interface{}
	v := reflect.ValueOf(value)
	c.setModel(v)
	query := c.query
	args := c.args
	vq := reflect.ValueOf(query)
	kindQuery := vq.Kind()
	switch kindQuery {
	case reflect.Map:
		fields = whereFieldsFromMap(query)
	case reflect.Struct:
		fields = whereFieldsFromCondList(query, args)
	}
	values := fields["values"].([]interface{})
	wQuery := c.whereQuery(fields)
	fmt.Printf("[%s] %s\n", time.Now(), wQuery)
	q := c.s.Query(wQuery, values...)
	if consistency := c.consistency; consistency > 0 {
		q = q.Consistency(consistency)
	}
	return q.Iter()
}

func (s *Session) Limit(limit int) *Session {
	c := s.clone()
	c.limit = limit
	return c
}

func (s *Session) Model(value interface{}) *Session {
	v := reflect.ValueOf(value)
	ns := s.clone()
	ns.setModel(v)
	return ns
}

func (s *Session) PageSize(n int) {
	s.s.SetPageSize(n)
}

func (s *Session) Prefetch(p float64) {
	s.s.SetPrefetch(p)
}

func (s *Session) Scan(value interface{}) bool {
	var fields map[string]interface{}
	v := reflect.ValueOf(value)
	s.setModel(v)
	query := s.query
	vq := reflect.ValueOf(query)
	kindQuery := vq.Kind()
	switch kindQuery {
	case reflect.Map:
		fields = whereFieldsFromMap(query)
	}
	values := fields["values"].([]interface{})
	q := s.s.Query(s.whereQuery(fields), values...)
	if consistency := s.consistency; consistency > 0 {
		q = q.Consistency(consistency)
	}
	b := cqlr.BindQuery(q)
	return b.Scan(value)
}

func (s *Session) Select(sel ...string) *Session {
	c := s.clone()
	c.sel = append(c.sel, sel...)
	return c
}

func (s *Session) Table(name string) *Session {
	c := s.clone()
	c.tableName = name
	return c
}

func (s *Session) Token(sel ...string) *Session {
	c := s.clone()
	var tokenFields []string
	for i := 0; i < len(sel); i++ {
		s := sel[i]
		tokenFields = append(sel, fmt.Sprintf("token(%s)", s))
	}
	c.sel = append(c.sel, tokenFields...)
	return c
}

func (s *Session) Where(args ...interface{}) *Session {
	ns := s.clone()
	if len(args) <= 0 {
		return ns
	}
	ns.query = args[0]
	ns.args = args[1:len(args)]
	return ns
}

func (s *Session) allowFilteringString() string {
	if s.allowFiltering == true {
		return "ALLOW FILTERING"
	}
	return ""
}

func (s *Session) clone() *Session {
	ns := *s
	return &ns
}

func (s *Session) limitString() string {
	if limit := s.limit; limit > 0 {
		return fmt.Sprintf("LIMIT %v", limit)
	}
	return ""
}

func (s *Session) selectString() string {
	if sel := s.sel; len(sel) > 0 {
		return strings.Join(sel, ",")
	}
	return "*"
}

func (s *Session) setModel(v reflect.Value) {
	indirect := reflect.Indirect(v)
	t := indirect.Type()
	s.value = v
	s.indirect = indirect
	if s.tableName == "" {
		s.tableName = inflection.Plural(strings.ToLower(t.Name()))
	}
}

func (s *Session) whereQuery(f map[string]interface{}) string {
	var conditionsString string
	sel := s.selectString()
	limit := s.limitString()
	allowFiltering := s.allowFilteringString()
	if conditions := f["conditions"].(string); conditions != "" {
		conditionsString = fmt.Sprintf("WHERE %v", conditions)
	}
	query := fmt.Sprintf(whereQueryTemplate, sel, s.tableName, conditionsString, limit, allowFiltering)
	return query
}

func compile(v interface{}, cols []gocql.ColumnInfo) error {
	return nil
}

func contentOfSlice(v reflect.Value) []interface{} {
	slice := make([]interface{}, v.Len())
	for i := 0; i < v.Len(); i++ {
		f := reflect.Indirect(v.Index(i))
		slice[i] = f.Interface()
	}
	return slice
}

func fields(v interface{}) map[string]interface{} {
	var names string
	var slots string
	var values []interface{}
	strategies := make(map[string]interface{})
	result := make(map[string]interface{})
	value := reflect.ValueOf(v)
	indirect := reflect.Indirect(value)
	t := indirect.Type()
	result["table_name"] = inflection.Plural(strings.ToLower(t.Name()))
	for i := 0; i < t.NumField(); i++ {
		var inf interface{}
		var tagName string
		f := t.Field(i)
		fv := indirect.Field(i)
		tag := tag(f)
		fvIndirect := reflect.Indirect(fv)
		if fvIndirect.IsValid() == false {
			continue
		}
		inf = fvIndirect.Interface()
		isZero := isZero(inf)
		if isZero == true && tag.OmitEmpty == true {
			continue
		}
		if i != 0 {
			names += ","
			slots += ","
		}
		if tag.Name != "" {
			tagName = tag.Name
		} else {
			tagName = strings.ToLower(f.Name)
		}
		names += tagName
		slots += "?"
		strategies[tagName] = inf
		values = append(values, inf)
	}
	result["names"] = names
	result["values"] = values
	result["slots"] = slots
	return result
}

func insertQuery(f map[string]interface{}) string {
	query := fmt.Sprintf(insertQueryTemplate, f["table_name"], f["names"], f["slots"])
	return query
}

func isZero(x interface{}) bool {
	return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}

func tag(f reflect.StructField) *fieldTag {
	ft := &fieldTag{}
	tag := f.Tag.Get("cql")
	opts := strings.Split(tag, ",")
	ft.Name = opts[0]
	if len(opts) > 1 && opts[1] == "omitempty" {
		ft.OmitEmpty = true
	}
	return ft
}

func operatorForValue(value reflect.Value) string {
	kind := value.Kind().String()
	fmt.Println(kind)
	return ""
}

func whereFieldsFromCondList(query interface{}, args []interface{}) map[string]interface{} {
	var conditions string
	var values []interface{}
	var names []string
	result := make(map[string]interface{})
	condList := append(args, query)
	for i := 0; i < len(condList); i++ {
		condition := condList[i].(Condition)
		if i != 0 {
			conditions += " AND "
		}
		conditions += condition.String()
		names = append(names, condition.key)
		values = append(values, condition.values...)
	}
	result["conditions"] = conditions
	result["values"] = values
	result["names"] = names
	return result
}

func whereFieldsFromMap(value interface{}) map[string]interface{} {
	var conditions string
	var values []interface{}
	var names []string
	t1 := time.Now()
	result := make(map[string]interface{})
	v := reflect.ValueOf(value)
	keys := v.MapKeys()
	for i := 0; i < len(keys); i++ {
		key := keys[i]
		keyString := key.String()
		value := v.MapIndex(key)
		inf := value.Interface()
		if i != 0 {
			conditions += " AND "
		}
		conditions += fmt.Sprintf("%s = ?", keyString)
		names = append(names, keyString)
		values = append(values, inf)
	}
	result["conditions"] = conditions
	result["values"] = values
	result["names"] = names
	fmt.Println("duration whereFieldsFromMap", time.Since(t1))
	return result
}
<commit_msg>Setting page size and prefetch per query instead of per session.<commit_after>package cqlb

import (
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/gocql/gocql"
	"github.com/jinzhu/inflection"
	"github.com/relops/cqlr"
)

const (
	insertQueryTemplate = "insert into %s (%s) values(%s);"
	whereQueryTemplate  = "select %s from %s %s %s %s;"
)

type fieldTag struct {
	Name      string
	OmitEmpty bool
}

type Session struct {
	s              *gocql.Session
	query          interface{}
	args           []interface{}
	sel            []string
	limit          int
	allowFiltering bool
	consistency    gocql.Consistency
	value          reflect.Value
	indirect       reflect.Value
	tableName      string
	pageSize       int
	prefetch       float64
}

func SetSession(s *gocql.Session) *Session {
	return &Session{s: s}
}

func (s *Session) AllowFiltering(b bool) *Session {
	c := s.clone()
	c.allowFiltering = b
	return c
}

func (s *Session) Consistency(consistency gocql.Consistency) *Session {
	c := s.clone()
	c.consistency = consistency
	return c
}

func (s *Session) Find(slice interface{}) error {
	// var fields map[string]interface{}
	// var fieldsToScan []interface{}
	// value := reflect.ValueOf(slice)
	// k := value.Kind()
	// if k != reflect.Slice {
	// 	return errors.New("value should be a slice.")
	// }
	// v := value.Index(0)
	// indirect := reflect.Indirect(v)
	// s.setModel(v)
	// query := s.query
	// vq := reflect.ValueOf(query)
	// kindQuery := vq.Kind()
	// switch kindQuery {
	// case reflect.Map:
	// 	fields = whereFieldsFromMap(query)
	// }
	// iter := s.s.Query(s.whereQuery(fields), values).Iter()
	// cols := iter.Columns()
	// values := make([]interface{}, len(cols))
	// names := f["names"].([]string)
	// for i, col := range cols {
	// 	values[i] = f["strategies"].(map[string]interface{})[col.Name]
	// }
	return nil
}

func (s *Session) Insert(v interface{}) error {
	f := fields(v)
	stmt := insertQuery(f)
	fmt.Println("fields", f)
	return s.s.Query(stmt, f["values"].([]interface{})...).Exec()
}

func (s *Session) Iter(value interface{}) *gocql.Iter {
	c := s.clone()
	var fields map[string]interface{}
	v := reflect.ValueOf(value)
	c.setModel(v)
	query := c.query
	args := c.args
	vq := reflect.ValueOf(query)
	kindQuery := vq.Kind()
	switch kindQuery {
	case reflect.Map:
		fields = whereFieldsFromMap(query)
	case reflect.Struct:
		fields = whereFieldsFromCondList(query, args)
	}
	values := fields["values"].([]interface{})
	wQuery := c.whereQuery(fields)
	fmt.Printf("[%s] %s\n", time.Now(), wQuery)
	q := c.s.Query(wQuery, values...)
	if consistency := c.consistency; consistency > 0 {
		q = q.Consistency(consistency)
	}
	q = q.PageSize(c.pageSize)
	if prefetch := c.prefetch; prefetch > 0 {
		q.Prefetch(prefetch)
	}
	return q.Iter()
}

func (s *Session) Limit(limit int) *Session {
	c := s.clone()
	c.limit = limit
	return c
}

func (s *Session) Model(value interface{}) *Session {
	v := reflect.ValueOf(value)
	ns := s.clone()
	ns.setModel(v)
	return ns
}

func (s *Session) PageSize(n int) *Session {
	c := s.clone()
	c.pageSize = n
	return c
}

func (s *Session) Prefetch(p float64) *Session {
	c := s.clone()
	c.prefetch = p
	return c
}

func (s *Session) Scan(value interface{}) bool {
	var fields map[string]interface{}
	v := reflect.ValueOf(value)
	s.setModel(v)
	query := s.query
	vq := reflect.ValueOf(query)
	kindQuery := vq.Kind()
	switch kindQuery {
	case reflect.Map:
		fields = whereFieldsFromMap(query)
	}
	values := fields["values"].([]interface{})
	q := s.s.Query(s.whereQuery(fields), values...)
	if consistency := s.consistency; consistency > 0 {
		q = q.Consistency(consistency)
	}
	b := cqlr.BindQuery(q)
	return b.Scan(value)
}

func (s *Session) Select(sel ...string) *Session {
	c := s.clone()
	c.sel = append(c.sel, sel...)
	return c
}

func (s *Session) Table(name string) *Session {
	c := s.clone()
	c.tableName = name
	return c
}

func (s *Session) Token(sel ...string) *Session {
	c := s.clone()
	var tokenFields []string
	for i := 0; i < len(sel); i++ {
		s := sel[i]
		tokenFields = append(sel, fmt.Sprintf("token(%s)", s))
	}
	c.sel = append(c.sel, tokenFields...)
	return c
}

func (s *Session) Where(args ...interface{}) *Session {
	ns := s.clone()
	if len(args) <= 0 {
		return ns
	}
	ns.query = args[0]
	ns.args = args[1:len(args)]
	return ns
}

func (s *Session) allowFilteringString() string {
	if s.allowFiltering == true {
		return "ALLOW FILTERING"
	}
	return ""
}

func (s *Session) clone() *Session {
	ns := *s
	return &ns
}

func (s *Session) limitString() string {
	if limit := s.limit; limit > 0 {
		return fmt.Sprintf("LIMIT %v", limit)
	}
	return ""
}

func (s *Session) selectString() string {
	if sel := s.sel; len(sel) > 0 {
		return strings.Join(sel, ",")
	}
	return "*"
}

func (s *Session) setModel(v reflect.Value) {
	indirect := reflect.Indirect(v)
	t := indirect.Type()
	s.value = v
	s.indirect = indirect
	if s.tableName == "" {
		s.tableName = inflection.Plural(strings.ToLower(t.Name()))
	}
}

func (s *Session) whereQuery(f map[string]interface{}) string {
	var conditionsString string
	sel := s.selectString()
	limit := s.limitString()
	allowFiltering := s.allowFilteringString()
	if conditions := f["conditions"].(string); conditions != "" {
		conditionsString = fmt.Sprintf("WHERE %v", conditions)
	}
	query := fmt.Sprintf(whereQueryTemplate, sel, s.tableName, conditionsString, limit, allowFiltering)
	return query
}

func compile(v interface{}, cols []gocql.ColumnInfo) error {
	return nil
}

func contentOfSlice(v reflect.Value) []interface{} {
	slice := make([]interface{}, v.Len())
	for i := 0; i < v.Len(); i++ {
		f := reflect.Indirect(v.Index(i))
		slice[i] = f.Interface()
	}
	return slice
}

func fields(v interface{}) map[string]interface{} {
	var names string
	var slots string
	var values []interface{}
	strategies := make(map[string]interface{})
	result := make(map[string]interface{})
	value := reflect.ValueOf(v)
	indirect := reflect.Indirect(value)
	t := indirect.Type()
	result["table_name"] = inflection.Plural(strings.ToLower(t.Name()))
	for i := 0; i < t.NumField(); i++ {
		var inf interface{}
		var tagName string
		f := t.Field(i)
		fv := indirect.Field(i)
		tag := tag(f)
		fvIndirect := reflect.Indirect(fv)
		if fvIndirect.IsValid() == false {
			continue
		}
		inf = fvIndirect.Interface()
		isZero := isZero(inf)
		if isZero == true && tag.OmitEmpty == true {
			continue
		}
		if i != 0 {
			names += ","
			slots += ","
		}
		if tag.Name != "" {
			tagName = tag.Name
		} else {
			tagName = strings.ToLower(f.Name)
		}
		names += tagName
		slots += "?"
		strategies[tagName] = inf
		values = append(values, inf)
	}
	result["names"] = names
	result["values"] = values
	result["slots"] = slots
	return result
}

func insertQuery(f map[string]interface{}) string {
	query := fmt.Sprintf(insertQueryTemplate, f["table_name"], f["names"], f["slots"])
	return query
}

func isZero(x interface{}) bool {
	return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}

func tag(f reflect.StructField) *fieldTag {
	ft := &fieldTag{}
	tag := f.Tag.Get("cql")
	opts := strings.Split(tag, ",")
	ft.Name = opts[0]
	if len(opts) > 1 && opts[1] == "omitempty" {
		ft.OmitEmpty = true
	}
	return ft
}

func operatorForValue(value reflect.Value) string {
	kind := value.Kind().String()
	fmt.Println(kind)
	return ""
}

func whereFieldsFromCondList(query interface{}, args []interface{}) map[string]interface{} {
	var conditions string
	var values []interface{}
	var names []string
	result := make(map[string]interface{})
	condList := append(args, query)
	for i := 0; i < len(condList); i++ {
		condition := condList[i].(Condition)
		if i != 0 {
			conditions += " AND "
		}
		conditions += condition.String()
		names = append(names, condition.key)
		values = append(values, condition.values...)
	}
	result["conditions"] = conditions
	result["values"] = values
	result["names"] = names
	return result
}

func whereFieldsFromMap(value interface{}) map[string]interface{} {
	var conditions string
	var values []interface{}
	var names []string
	t1 := time.Now()
	result := make(map[string]interface{})
	v := reflect.ValueOf(value)
	keys := v.MapKeys()
	for i := 0; i < len(keys); i++ {
		key := keys[i]
		keyString := key.String()
		value := v.MapIndex(key)
		inf := value.Interface()
		if i != 0 {
			conditions += " AND "
		}
		conditions += fmt.Sprintf("%s = ?", keyString)
		names = append(names, keyString)
		values = append(values, inf)
	}
	result["conditions"] = conditions
	result["values"] = values
	result["names"] = names
	fmt.Println("duration whereFieldsFromMap", time.Since(t1))
	return result
}
<|endoftext|>


<commit_before>// Ogo

package ogo

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/Odinman/gorp"
	_ "github.com/go-sql-driver/mysql"
)

var (
	DataAccessor = make(map[string]string) // tablename::{read/write} => tag
)

const (
	DBTAG      string = "db"
	READTAG    string = "read"
	WRITETAG   string = "write"
	base       string = "0000-00-00 00:00:00.0000000"
	timeFormat string = "2006-01-02 15:04:05.999999"
)

//出/入库转换器
type SelfConverter interface {
	ToDb() (interface{}, error)                    //入库
	FromDb(interface{}) (gorp.CustomScanner, bool) //出库
}

type BaseConverter struct{}

/* {{{ func OpenDB(tag,dns string) error
 *
 */
func OpenDB(tag, dns string) (err error) {
	Debug("open mysql: %s,%s", tag, dns)
	gorp.TraceOn(fmt.Sprintf("[%s]", tag), Logger())
	gorp.SetTypeConvert(BaseConverter{})
	if err = gorp.Open(tag, "mysql", dns); err != nil {
		Debug("open error: %s", err)
	}
	return
}

/* }}} */

/* {{{ func (_ BaseConverter) ToDb(val interface{}) (interface{}, error)
 *
 */
func (_ BaseConverter) ToDb(val interface{}) (interface{}, error) {
	switch t := val.(type) {
	case *[]string, []string, map[string]string, *map[string]string, map[string]interface{}, *map[string]interface{}: //转为字符串
		c, _ := json.Marshal(t)
		return string(c), nil
	default:
		// 自定义的类型,如果实现了SelfConverter接口,则这里自动执行
		if _, ok := val.(SelfConverter); ok {
			Trace("selfconvert todb")
			return val.(SelfConverter).ToDb()
		} else if _, ok := reflect.Indirect(reflect.ValueOf(val)).Interface().(SelfConverter); ok { //如果采用了指针, 则到这里
			Trace("prt selfconvert todb")
			return val.(SelfConverter).ToDb()
		} else {
			Trace("not selfconvert todb")
		}
	}
	return val, nil
}

/* }}} */

/* {{{ func (_ BaseConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
 * 类型转换, 主要处理标准类型; 自定义类型通过SelfConverter实现
 */
func (_ BaseConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
	switch t := target.(type) {
	case **time.Time:
		binder := func(holder, target interface{}) error {
			var err error
			if holder.(*sql.NullString).Valid {
				var dt time.Time
				str := holder.(*sql.NullString).String
				switch len(str) {
				case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
					if str == base[:len(str)] {
						return nil
					}
					dt, err = time.ParseInLocation(timeFormat[:len(str)], str, time.Local)
				default:
					err = fmt.Errorf("Invalid Time-String: %s", str)
					return err
				}
				if err != nil {
					return err
				}
				//dt = dt.UTC()
				dt = dt.Local()
				*(target.(**time.Time)) = &dt
				return nil
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *time.Time:
		binder := func(holder, target interface{}) error {
			var err error
			if holder.(*sql.NullString).Valid {
				var dt time.Time
				str := holder.(*sql.NullString).String
				switch len(str) {
				case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
					if str == base[:len(str)] {
						return nil
					}
					dt, err = time.ParseInLocation(timeFormat[:len(str)], str, time.Local)
				default:
					err = fmt.Errorf("Invalid Time-String: %s", str)
					return err
				}
				if err != nil {
					return err
				}
				//dt = dt.UTC()
				dt = dt.Local()
				*(target.(*time.Time)) = dt
				return nil
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **[]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st []string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(**[]string)) = &st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *[]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st []string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(*[]string)) = st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **map[string]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(**map[string]string)) = &st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *map[string]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(*map[string]string)) = st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **map[string]interface{}:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]interface{}
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(**map[string]interface{})) = &st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *map[string]interface{}:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]interface{}
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(*map[string]interface{})) = st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **string:
		binder := func(holder, target interface{}) error {
			*t = &holder.(*sql.NullString).String
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **float64:
		binder := func(holder, target interface{}) error {
			*t = &holder.(*sql.NullFloat64).Float64
			return nil
		}
		return gorp.CustomScanner{new(sql.NullFloat64), target, binder}, true

	case **int64:
		binder := func(holder, target interface{}) error {
			*t = &holder.(*sql.NullInt64).Int64
			return nil
		}
		return gorp.CustomScanner{new(sql.NullInt64), target, binder}, true
	default:
		// 自定义的类型,如果实现了SelfConverter接口,则这里自动执行
		if t, ok := target.(SelfConverter); ok {
			Trace("selfconvert begin(value)")
			return t.FromDb(target)
		} else if t, ok := reflect.Indirect(reflect.ValueOf(target)).Interface().(SelfConverter); ok { //如果采用了指针, 则到这里
			Trace("ptr converter: %s", target)
			return t.FromDb(target)
		} else {
			Trace("no converter: %s", target)
		}
	}
	return gorp.CustomScanner{}, false
}

/* }}} */
<commit_msg>bugfix<commit_after>// Ogo

package ogo

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/Odinman/gorp"
	//"github.com/Odinman/ogo/utils"
	_ "github.com/go-sql-driver/mysql"
)

var (
	DataAccessor = make(map[string]string) // tablename::{read/write} => tag
)

const (
	DBTAG      string = "db"
	READTAG    string = "read"
	WRITETAG   string = "write"
	base       string = "0000-00-00 00:00:00.0000000"
	timeFormat string = "2006-01-02 15:04:05.999999"
)

//出/入库转换器
type SelfConverter interface {
	ToDb() (interface{}, error)                    //入库
	FromDb(interface{}) (gorp.CustomScanner, bool) //出库
}

type BaseConverter struct{}

/* {{{ func OpenDB(tag,dns string) error
 *
 */
func OpenDB(tag, dns string) (err error) {
	Debug("open mysql: %s,%s", tag, dns)
	gorp.TraceOn(fmt.Sprintf("[%s]", tag), Logger())
	gorp.SetTypeConvert(BaseConverter{})
	if err = gorp.Open(tag, "mysql", dns); err != nil {
		Debug("open error: %s", err)
	}
	return
}

/* }}} */

/* {{{ func (_ BaseConverter) ToDb(val interface{}) (interface{}, error)
 *
 */
func (_ BaseConverter) ToDb(val interface{}) (interface{}, error) {
	switch t := val.(type) {
	case *[]string, []string, map[string]string, *map[string]string, map[string]interface{}, *map[string]interface{}: //转为字符串
		c, _ := json.Marshal(t)
		return string(c), nil
	//case *float64:
	//	ot := utils.ParseFloat(*t)
	//	Info("float: %f", ot)
	//	return ot, nil
	//case float64:
	//	return utils.ParseFloat(t), nil
	default:
		// 自定义的类型,如果实现了SelfConverter接口,则这里自动执行
		if _, ok := val.(SelfConverter); ok {
			Trace("selfconvert todb")
			return val.(SelfConverter).ToDb()
		} else if _, ok := reflect.Indirect(reflect.ValueOf(val)).Interface().(SelfConverter); ok { //如果采用了指针, 则到这里
			Trace("prt selfconvert todb")
			return val.(SelfConverter).ToDb()
		} else {
			Trace("not selfconvert todb")
		}
	}
	return val, nil
}

/* }}} */

/* {{{ func (_ BaseConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
 * 类型转换, 主要处理标准类型; 自定义类型通过SelfConverter实现
 */
func (_ BaseConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
	switch t := target.(type) {
	case **time.Time:
		binder := func(holder, target interface{}) error {
			var err error
			if holder.(*sql.NullString).Valid {
				var dt time.Time
				str := holder.(*sql.NullString).String
				switch len(str) {
				case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
					if str == base[:len(str)] {
						return nil
					}
					dt, err = time.ParseInLocation(timeFormat[:len(str)], str, time.Local)
				default:
					err = fmt.Errorf("Invalid Time-String: %s", str)
					return err
				}
				if err != nil {
					return err
				}
				//dt = dt.UTC()
				dt = dt.Local()
				*(target.(**time.Time)) = &dt
				return nil
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *time.Time:
		binder := func(holder, target interface{}) error {
			var err error
			if holder.(*sql.NullString).Valid {
				var dt time.Time
				str := holder.(*sql.NullString).String
				switch len(str) {
				case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
					if str == base[:len(str)] {
						return nil
					}
					dt, err = time.ParseInLocation(timeFormat[:len(str)], str, time.Local)
				default:
					err = fmt.Errorf("Invalid Time-String: %s", str)
					return err
				}
				if err != nil {
					return err
				}
				//dt = dt.UTC()
				dt = dt.Local()
				*(target.(*time.Time)) = dt
				return nil
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **[]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st []string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(**[]string)) = &st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *[]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st []string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(*[]string)) = st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **map[string]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(**map[string]string)) = &st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *map[string]string:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]string
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(*map[string]string)) = st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **map[string]interface{}:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]interface{}
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(**map[string]interface{})) = &st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case *map[string]interface{}:
		binder := func(holder, target interface{}) error {
			if holder.(*sql.NullString).Valid {
				var st map[string]interface{}
				str := holder.(*sql.NullString).String
				//ogo.Debug("str: %s", str)
				if err := json.Unmarshal([]byte(str), &st); err != nil {
					return err
				}
				*(target.(*map[string]interface{})) = st
			}
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **string:
		binder := func(holder, target interface{}) error {
			*t = &holder.(*sql.NullString).String
			return nil
		}
		return gorp.CustomScanner{new(sql.NullString), target, binder}, true
	case **float64:
		binder := func(holder, target interface{}) error {
			*t = &holder.(*sql.NullFloat64).Float64
			return nil
		}
		return gorp.CustomScanner{new(sql.NullFloat64), target, binder}, true

	case **int64:
		binder := func(holder, target interface{}) error {
			*t = &holder.(*sql.NullInt64).Int64
			return nil
		}
		return gorp.CustomScanner{new(sql.NullInt64), target, binder}, true
	default:
		// 自定义的类型,如果实现了SelfConverter接口,则这里自动执行
		if t, ok := target.(SelfConverter); ok {
			Trace("selfconvert begin(value)")
			return t.FromDb(target)
		} else if t, ok := reflect.Indirect(reflect.ValueOf(target)).Interface().(SelfConverter); ok { //如果采用了指针, 则到这里
			Trace("ptr converter: %s", target)
			return t.FromDb(target)
		} else {
			Trace("no converter: %s", target)
		}
	}
	return gorp.CustomScanner{}, false
}

/* }}} */
<|endoftext|>