run gofmt

This commit is contained in:
Cadey Ratio 2017-07-15 13:50:10 -07:00
parent edf576cd33
commit 40a5e952d2
10 changed files with 367 additions and 372 deletions

129
api.go
View File

@ -1,6 +1,6 @@
package gorqlite package gorqlite
/* /*
this file has low level stuff: this file has low level stuff:
rqliteApiGet() rqliteApiGet()
@ -33,69 +33,69 @@ import "time"
func (conn *Connection) rqliteApiGet(apiOp apiOperation) ([]byte, error) { func (conn *Connection) rqliteApiGet(apiOp apiOperation) ([]byte, error) {
var responseBody []byte var responseBody []byte
trace("%s: rqliteApiGet() called",conn.ID) trace("%s: rqliteApiGet() called", conn.ID)
// only api_STATUS now - maybe someday BACKUP // only api_STATUS now - maybe someday BACKUP
if ( apiOp != api_STATUS ) { if apiOp != api_STATUS {
return responseBody, errors.New("rqliteApiGet() called for invalid api operation") return responseBody, errors.New("rqliteApiGet() called for invalid api operation")
} }
// just to be safe, check this // just to be safe, check this
peersToTry := conn.cluster.makePeerList() peersToTry := conn.cluster.makePeerList()
if ( len(peersToTry) < 1 ) { if len(peersToTry) < 1 {
return responseBody, errors.New("I don't have any cluster info") return responseBody, errors.New("I don't have any cluster info")
} }
trace("%s: I have a peer list %d peers long",conn.ID,len(peersToTry)) trace("%s: I have a peer list %d peers long", conn.ID, len(peersToTry))
// failure log is used so that if all peers fail, we can say something // failure log is used so that if all peers fail, we can say something
// about why each failed // about why each failed
failureLog := make([]string,0) failureLog := make([]string, 0)
PeerLoop: PeerLoop:
for peerNum, peerToTry := range peersToTry { for peerNum, peerToTry := range peersToTry {
trace("%s: attemping to contact peer %d",conn.ID,peerNum) trace("%s: attemping to contact peer %d", conn.ID, peerNum)
// docs say default GET policy is up to 10 follows automatically // docs say default GET policy is up to 10 follows automatically
url := conn.assembleURL(api_STATUS,peerToTry) url := conn.assembleURL(api_STATUS, peerToTry)
req, err := http.NewRequest("GET",url,nil) req, err := http.NewRequest("GET", url, nil)
if ( err != nil ) { if err != nil {
trace("%s: got error '%s' doing http.NewRequest", conn.ID,err.Error()) trace("%s: got error '%s' doing http.NewRequest", conn.ID, err.Error())
failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) failureLog = append(failureLog, fmt.Sprintf("%s failed due to %s", url, err.Error()))
continue PeerLoop continue PeerLoop
} }
trace("%s: http.NewRequest() OK") trace("%s: http.NewRequest() OK")
req.Header.Set("Content-Type","application/json") req.Header.Set("Content-Type", "application/json")
client := &http.Client{} client := &http.Client{}
client.Timeout = time.Duration(conn.timeout) * time.Second client.Timeout = time.Duration(conn.timeout) * time.Second
response, err := client.Do(req) response, err := client.Do(req)
if ( err != nil ) { if err != nil {
trace("%s: got error '%s' doing client.Do", conn.ID,err.Error()) trace("%s: got error '%s' doing client.Do", conn.ID, err.Error())
failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) failureLog = append(failureLog, fmt.Sprintf("%s failed due to %s", url, err.Error()))
continue PeerLoop continue PeerLoop
} }
defer response.Body.Close() defer response.Body.Close()
trace("%s: client.Do() OK") trace("%s: client.Do() OK")
responseBody, err := ioutil.ReadAll(response.Body) responseBody, err := ioutil.ReadAll(response.Body)
if ( err != nil ) { if err != nil {
trace("%s: got error '%s' doing ioutil.ReadAll", conn.ID,err.Error()) trace("%s: got error '%s' doing ioutil.ReadAll", conn.ID, err.Error())
failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) failureLog = append(failureLog, fmt.Sprintf("%s failed due to %s", url, err.Error()))
continue PeerLoop continue PeerLoop
} }
trace("%s: ioutil.ReadAll() OK") trace("%s: ioutil.ReadAll() OK")
if ( response.Status != "200 OK" ) { if response.Status != "200 OK" {
trace("%s: got code %s",conn.ID,response.Status) trace("%s: got code %s", conn.ID, response.Status)
failureLog = append(failureLog,fmt.Sprintf("%s failed, got: %s",url,response.Status)) failureLog = append(failureLog, fmt.Sprintf("%s failed, got: %s", url, response.Status))
continue PeerLoop continue PeerLoop
} }
// if we got here, we succeeded // if we got here, we succeeded
trace("%s: api call OK, returning",conn.ID) trace("%s: api call OK, returning", conn.ID)
return responseBody, nil return responseBody, nil
} }
// if we got here, all peers failed. Let's build a verbose error message // if we got here, all peers failed. Let's build a verbose error message
var stringBuffer bytes.Buffer var stringBuffer bytes.Buffer
stringBuffer.WriteString("tried all peers unsuccessfully. here are the results:\n") stringBuffer.WriteString("tried all peers unsuccessfully. here are the results:\n")
for n, v := range failureLog { for n, v := range failureLog {
stringBuffer.WriteString(fmt.Sprintf(" peer #%d: %s\n",n,v)) stringBuffer.WriteString(fmt.Sprintf(" peer #%d: %s\n", n, v))
} }
return responseBody, errors.New(stringBuffer.String()) return responseBody, errors.New(stringBuffer.String())
} }
@ -114,78 +114,80 @@ PeerLoop:
* *****************************************************************/ * *****************************************************************/
func (conn *Connection) rqliteApiPost (apiOp apiOperation, sqlStatements []string) ([]byte, error) { func (conn *Connection) rqliteApiPost(apiOp apiOperation, sqlStatements []string) ([]byte, error) {
var responseBody []byte var responseBody []byte
switch (apiOp) { switch apiOp {
case api_QUERY: case api_QUERY:
trace("%s: rqliteApiGet() post called for a QUERY of %d statements",conn.ID,len(sqlStatements)) trace("%s: rqliteApiGet() post called for a QUERY of %d statements", conn.ID, len(sqlStatements))
case api_WRITE: case api_WRITE:
trace("%s: rqliteApiGet() post called for a QUERY of %d statements",conn.ID,len(sqlStatements)) trace("%s: rqliteApiGet() post called for a QUERY of %d statements", conn.ID, len(sqlStatements))
default: default:
return responseBody, errors.New("weird! called for an invalid apiOperation in rqliteApiPost()") return responseBody, errors.New("weird! called for an invalid apiOperation in rqliteApiPost()")
} }
// jsonify the statements. not really needed in the // jsonify the statements. not really needed in the
// case of api_STATUS but doesn't hurt // case of api_STATUS but doesn't hurt
jStatements , err := json.Marshal(sqlStatements) jStatements, err := json.Marshal(sqlStatements)
if ( err != nil ) { return nil, err } if err != nil {
return nil, err
}
// just to be safe, check this // just to be safe, check this
peersToTry := conn.cluster.makePeerList() peersToTry := conn.cluster.makePeerList()
if ( len(peersToTry) < 1 ) { if len(peersToTry) < 1 {
return responseBody, errors.New("I don't have any cluster info") return responseBody, errors.New("I don't have any cluster info")
} }
// failure log is used so that if all peers fail, we can say something // failure log is used so that if all peers fail, we can say something
// about why each failed // about why each failed
failureLog := make([]string,0) failureLog := make([]string, 0)
PeerLoop: PeerLoop:
for peerNum, peer := range peersToTry { for peerNum, peer := range peersToTry {
trace("%s: trying peer #%d",conn.ID,peerNum) trace("%s: trying peer #%d", conn.ID, peerNum)
// we're doing a post, and the RFCs say that if you get a 301, it's not // we're doing a post, and the RFCs say that if you get a 301, it's not
// automatically followed, so we have to do that ourselves // automatically followed, so we have to do that ourselves
responseStatus := "Haven't Tried Yet" responseStatus := "Haven't Tried Yet"
var url string var url string
for ( responseStatus == "Haven't Tried Yet" || responseStatus == "301 Moved Permanently" ) { for responseStatus == "Haven't Tried Yet" || responseStatus == "301 Moved Permanently" {
url = conn.assembleURL(apiOp,peer) url = conn.assembleURL(apiOp, peer)
req, err := http.NewRequest("POST",url,bytes.NewBuffer(jStatements)) req, err := http.NewRequest("POST", url, bytes.NewBuffer(jStatements))
if ( err != nil ) { if err != nil {
trace("%s: got error '%s' doing http.NewRequest", conn.ID,err.Error()) trace("%s: got error '%s' doing http.NewRequest", conn.ID, err.Error())
failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) failureLog = append(failureLog, fmt.Sprintf("%s failed due to %s", url, err.Error()))
continue PeerLoop continue PeerLoop
} }
req.Header.Set("Content-Type","application/json") req.Header.Set("Content-Type", "application/json")
client := &http.Client{} client := &http.Client{}
response, err := client.Do(req) response, err := client.Do(req)
if ( err != nil ) { if err != nil {
trace("%s: got error '%s' doing client.Do", conn.ID,err.Error()) trace("%s: got error '%s' doing client.Do", conn.ID, err.Error())
failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) failureLog = append(failureLog, fmt.Sprintf("%s failed due to %s", url, err.Error()))
continue PeerLoop continue PeerLoop
} }
defer response.Body.Close() defer response.Body.Close()
responseBody, err = ioutil.ReadAll(response.Body) responseBody, err = ioutil.ReadAll(response.Body)
if ( err != nil ) { if err != nil {
trace("%s: got error '%s' doing ioutil.ReadAll", conn.ID,err.Error()) trace("%s: got error '%s' doing ioutil.ReadAll", conn.ID, err.Error())
failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) failureLog = append(failureLog, fmt.Sprintf("%s failed due to %s", url, err.Error()))
continue PeerLoop continue PeerLoop
} }
responseStatus = response.Status responseStatus = response.Status
if ( responseStatus == "301 Moved Permanently" ) { if responseStatus == "301 Moved Permanently" {
v := response.Header["Location"] v := response.Header["Location"]
failureLog = append(failureLog,fmt.Sprintf("%s redirected me to %s",url,v[0])) failureLog = append(failureLog, fmt.Sprintf("%s redirected me to %s", url, v[0]))
url = v[0] url = v[0]
continue PeerLoop continue PeerLoop
} else if ( responseStatus == "200 OK" ) { } else if responseStatus == "200 OK" {
trace("%s: api call OK, returning",conn.ID) trace("%s: api call OK, returning", conn.ID)
return responseBody, nil return responseBody, nil
} else { } else {
trace("%s: got error in responseStatus: %s", conn.ID, responseStatus) trace("%s: got error in responseStatus: %s", conn.ID, responseStatus)
failureLog = append(failureLog,fmt.Sprintf("%s failed, got: %s",url,response.Status)) failureLog = append(failureLog, fmt.Sprintf("%s failed, got: %s", url, response.Status))
continue PeerLoop continue PeerLoop
} }
} }
@ -195,8 +197,7 @@ PeerLoop:
var stringBuffer bytes.Buffer var stringBuffer bytes.Buffer
stringBuffer.WriteString("tried all peers unsuccessfully. here are the results:\n") stringBuffer.WriteString("tried all peers unsuccessfully. here are the results:\n")
for n, v := range failureLog { for n, v := range failureLog {
stringBuffer.WriteString(fmt.Sprintf(" peer #%d: %s\n",n,v)) stringBuffer.WriteString(fmt.Sprintf(" peer #%d: %s\n", n, v))
} }
return responseBody, errors.New(stringBuffer.String()) return responseBody, errors.New(stringBuffer.String())
} }

View File

@ -5,7 +5,7 @@ package gorqlite
types: types:
peer peer
rqliteCluster rqliteCluster
Connection methods: Connection methods:
assembleURL (from a peer) assembleURL (from a peer)
updateClusterInfo (does the full cluster discovery via status) updateClusterInfo (does the full cluster discovery via status)
@ -32,19 +32,18 @@ import "strings"
this is an internal type to abstact peer info. this is an internal type to abstact peer info.
note that hostname is sometimes used for "has this struct been note that hostname is sometimes used for "has this struct been
inialized" checks. inialized" checks.
* *****************************************************************/ * *****************************************************************/
type peer struct { type peer struct {
hostname string // hostname or "localhost" hostname string // hostname or "localhost"
port string // "4001" or port, only ever used as a string port string // "4001" or port, only ever used as a string
} }
func (p *peer) String() string { func (p *peer) String() string {
return fmt.Sprintf("%s:%s",p.hostname,p.port) return fmt.Sprintf("%s:%s", p.hostname, p.port)
} }
/* ***************************************************************** /* *****************************************************************
@ -56,9 +55,9 @@ func (p *peer) String() string {
* *****************************************************************/ * *****************************************************************/
type rqliteCluster struct { type rqliteCluster struct {
leader peer leader peer
otherPeers []peer otherPeers []peer
conn *Connection conn *Connection
} }
/* ***************************************************************** /* *****************************************************************
@ -72,16 +71,16 @@ type rqliteCluster struct {
* *****************************************************************/ * *****************************************************************/
func (rc *rqliteCluster) makePeerList() []peer { func (rc *rqliteCluster) makePeerList() []peer {
trace("%s: makePeerList() called",rc.conn.ID) trace("%s: makePeerList() called", rc.conn.ID)
var peerList []peer var peerList []peer
peerList = append(peerList,rc.leader) peerList = append(peerList, rc.leader)
for _, p := range rc.otherPeers { for _, p := range rc.otherPeers {
peerList = append(peerList,p) peerList = append(peerList, p)
} }
trace("%s: makePeerList() returning this list:",rc.conn.ID) trace("%s: makePeerList() returning this list:", rc.conn.ID)
for n, v := range peerList { for n, v := range peerList {
trace("%s: makePeerList() peer %d -> %s",rc.conn.ID,n,v.hostname + ":" + v.port) trace("%s: makePeerList() peer %d -> %s", rc.conn.ID, n, v.hostname+":"+v.port)
} }
return peerList return peerList
@ -105,13 +104,13 @@ func (rc *rqliteCluster) makePeerList() []peer {
func (conn *Connection) assembleURL(apiOp apiOperation, p peer) string { func (conn *Connection) assembleURL(apiOp apiOperation, p peer) string {
var stringBuffer bytes.Buffer var stringBuffer bytes.Buffer
if ( conn.wantsHTTPS == true ) { if conn.wantsHTTPS == true {
stringBuffer.WriteString("https") stringBuffer.WriteString("https")
} else { } else {
stringBuffer.WriteString("http") stringBuffer.WriteString("http")
} }
stringBuffer.WriteString("://") stringBuffer.WriteString("://")
if ( conn.username != "" && conn.password != "" ) { if conn.username != "" && conn.password != "" {
stringBuffer.WriteString(conn.username) stringBuffer.WriteString(conn.username)
stringBuffer.WriteString(":") stringBuffer.WriteString(":")
stringBuffer.WriteString(conn.password) stringBuffer.WriteString(conn.password)
@ -122,26 +121,26 @@ func (conn *Connection) assembleURL(apiOp apiOperation, p peer) string {
stringBuffer.WriteString(p.port) stringBuffer.WriteString(p.port)
switch apiOp { switch apiOp {
case api_STATUS: case api_STATUS:
stringBuffer.WriteString("/status") stringBuffer.WriteString("/status")
case api_QUERY: case api_QUERY:
stringBuffer.WriteString("/db/query") stringBuffer.WriteString("/db/query")
case api_WRITE: case api_WRITE:
stringBuffer.WriteString("/db/execute") stringBuffer.WriteString("/db/execute")
} }
if ( apiOp == api_QUERY || apiOp == api_WRITE ) { if apiOp == api_QUERY || apiOp == api_WRITE {
stringBuffer.WriteString("?timings&transaction&level=") stringBuffer.WriteString("?timings&transaction&level=")
stringBuffer.WriteString(consistencyLevelNames[conn.consistencyLevel]) stringBuffer.WriteString(consistencyLevelNames[conn.consistencyLevel])
} }
switch apiOp { switch apiOp {
case api_QUERY: case api_QUERY:
trace("%s: assembled URL for an api_QUERY: %s",conn.ID,stringBuffer.String()) trace("%s: assembled URL for an api_QUERY: %s", conn.ID, stringBuffer.String())
case api_STATUS: case api_STATUS:
trace("%s: assembled URL for an api_STATUS: %s",conn.ID,stringBuffer.String()) trace("%s: assembled URL for an api_STATUS: %s", conn.ID, stringBuffer.String())
case api_WRITE: case api_WRITE:
trace("%s: assembled URL for an api_WRITE: %s",conn.ID,stringBuffer.String()) trace("%s: assembled URL for an api_WRITE: %s", conn.ID, stringBuffer.String())
} }
return stringBuffer.String() return stringBuffer.String()
@ -160,22 +159,26 @@ func (conn *Connection) assembleURL(apiOp apiOperation, p peer) string {
* *****************************************************************/ * *****************************************************************/
func (conn *Connection) updateClusterInfo() error { func (conn *Connection) updateClusterInfo() error {
trace("%s: updateClusterInfo() called",conn.ID) trace("%s: updateClusterInfo() called", conn.ID)
// start with a fresh new cluster // start with a fresh new cluster
var rc rqliteCluster var rc rqliteCluster
rc.conn = conn rc.conn = conn
responseBody, err := conn.rqliteApiGet(api_STATUS) responseBody, err := conn.rqliteApiGet(api_STATUS)
if ( err != nil ) { return err } if err != nil {
trace("%s: updateClusterInfo() back from api call OK",conn.ID) return err
}
trace("%s: updateClusterInfo() back from api call OK", conn.ID)
sections := make(map[string]interface{}) sections := make(map[string]interface{})
err = json.Unmarshal(responseBody,&sections) err = json.Unmarshal(responseBody, &sections)
if ( err != nil ) { return err } if err != nil {
return err
}
sMap := sections["store"].(map[string]interface{}) sMap := sections["store"].(map[string]interface{})
leaderRaftAddr := sMap["leader"].(string) leaderRaftAddr := sMap["leader"].(string)
trace("%s: leader from store section is %s",conn.ID,leaderRaftAddr) trace("%s: leader from store section is %s", conn.ID, leaderRaftAddr)
// leader in this case is the RAFT address // leader in this case is the RAFT address
// we want the HTTP address, so we'll use this as // we want the HTTP address, so we'll use this as
@ -185,37 +188,36 @@ func (conn *Connection) updateClusterInfo() error {
apiPeers := meta["APIPeers"].(map[string]interface{}) apiPeers := meta["APIPeers"].(map[string]interface{})
for raftAddr, httpAddr := range apiPeers { for raftAddr, httpAddr := range apiPeers {
trace("%s: examining httpAddr %s",conn.ID,httpAddr) trace("%s: examining httpAddr %s", conn.ID, httpAddr)
/* httpAddr are usually hostname:port */ /* httpAddr are usually hostname:port */
var p peer var p peer
parts := strings.Split(httpAddr.(string),":") parts := strings.Split(httpAddr.(string), ":")
p.hostname = parts[0] p.hostname = parts[0]
p.port = parts[1] p.port = parts[1]
// so is this the leader? // so is this the leader?
if ( leaderRaftAddr == raftAddr ) { if leaderRaftAddr == raftAddr {
trace ("%s: found leader at %s",conn.ID,httpAddr) trace("%s: found leader at %s", conn.ID, httpAddr)
rc.leader = p rc.leader = p
} else { } else {
rc.otherPeers = append(rc.otherPeers, p) rc.otherPeers = append(rc.otherPeers, p)
} }
} }
if ( rc.leader.hostname == "" ) { if rc.leader.hostname == "" {
return errors.New("could not determine leader from API status call") return errors.New("could not determine leader from API status call")
} }
// dump to trace // dump to trace
trace("%s: here is my cluster config:",conn.ID) trace("%s: here is my cluster config:", conn.ID)
trace("%s: leader : %s",conn.ID,rc.leader.String()) trace("%s: leader : %s", conn.ID, rc.leader.String())
for n, v := range rc.otherPeers { for n, v := range rc.otherPeers {
trace("%s: otherPeer #%d: %s",conn.ID,n,v.String()) trace("%s: otherPeer #%d: %s", conn.ID, n, v.String())
} }
// now make it official // now make it official
conn.cluster = rc conn.cluster = rc
return nil return nil
} }

View File

@ -6,34 +6,33 @@ import "os"
func TestInitCluster(t *testing.T) { func TestInitCluster(t *testing.T) {
TraceOn(os.Stderr) TraceOn(os.Stderr)
t.Logf("trying Open") t.Logf("trying Open")
conn, err := Open(testUrl()) conn, err := Open(testUrl())
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fatal(err) t.Fatal(err)
} }
l, err := conn.Leader() l, err := conn.Leader()
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
if ( len(l) < 1 ) { if len(l) < 1 {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
p, err := conn.Peers() p, err := conn.Peers()
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
if ( len(p) < 1 ) { if len(p) < 1 {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
} }

119
conn.go
View File

@ -35,13 +35,13 @@ var wantsTrace bool
/* /*
The connection abstraction. Note that since rqlite is stateless, The connection abstraction. Note that since rqlite is stateless,
there really is no "connection". However, this type holds there really is no "connection". However, this type holds
information such as the current leader, peers, connection information such as the current leader, peers, connection
string to build URLs, etc. string to build URLs, etc.
Connections are assigned a "connection ID" which is a pseudo-UUID Connections are assigned a "connection ID" which is a pseudo-UUID
for connection identification in trace output only. This helps for connection identification in trace output only. This helps
sort out what's going on if you have multiple connections going sort out what's going on if you have multiple connections going
at once. It's generated using a non-standards-or-anything-else-compliant at once. It's generated using a non-standards-or-anything-else-compliant
function that uses crypto/rand to generate 16 random bytes. function that uses crypto/rand to generate 16 random bytes.
Note that the Connection objection holds info on all peers, gathered Note that the Connection objection holds info on all peers, gathered
@ -49,23 +49,22 @@ var wantsTrace bool
*/ */
type Connection struct { type Connection struct {
cluster rqliteCluster cluster rqliteCluster
/* /*
name type default name type default
*/ */
username string // username or "" username string // username or ""
password string // username or "" password string // username or ""
consistencyLevel consistencyLevel // WEAK consistencyLevel consistencyLevel // WEAK
wantsHTTPS bool // false unless connection URL is https wantsHTTPS bool // false unless connection URL is https
// variables below this line need to be initialized in Open() // variables below this line need to be initialized in Open()
timeout int // 10 timeout int // 10
hasBeenClosed bool // false hasBeenClosed bool // false
ID string // generated in init() ID string // generated in init()
} }
/* ***************************************************************** /* *****************************************************************
@ -76,7 +75,7 @@ type Connection struct {
func (conn *Connection) Close() { func (conn *Connection) Close() {
conn.hasBeenClosed = true conn.hasBeenClosed = true
trace("%s: %s",conn.ID,"closing connection") trace("%s: %s", conn.ID, "closing connection")
} }
/* ***************************************************************** /* *****************************************************************
@ -86,8 +85,8 @@ func (conn *Connection) Close() {
* *****************************************************************/ * *****************************************************************/
func (conn *Connection) ConsistencyLevel() (string, error) { func (conn *Connection) ConsistencyLevel() (string, error) {
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
return "", errClosed return "", errClosed
} }
return consistencyLevelNames[conn.consistencyLevel], nil return consistencyLevelNames[conn.consistencyLevel], nil
} }
@ -99,16 +98,16 @@ func (conn *Connection) ConsistencyLevel() (string, error) {
* *****************************************************************/ * *****************************************************************/
func (conn *Connection) Leader() (string, error) { func (conn *Connection) Leader() (string, error) {
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
return "", errClosed return "", errClosed
} }
trace("%s: Leader(), calling updateClusterInfo()",conn.ID) trace("%s: Leader(), calling updateClusterInfo()", conn.ID)
err := conn.updateClusterInfo() err := conn.updateClusterInfo()
if ( err != nil ) { if err != nil {
trace("%s: Leader() got error from updateClusterInfo(): %s",conn.ID,err.Error()) trace("%s: Leader() got error from updateClusterInfo(): %s", conn.ID, err.Error())
return "", err return "", err
} else { } else {
trace("%s: Leader(), updateClusterInfo() OK",conn.ID) trace("%s: Leader(), updateClusterInfo() OK", conn.ID)
} }
return conn.cluster.leader.String(), nil return conn.cluster.leader.String(), nil
} }
@ -120,23 +119,23 @@ func (conn *Connection) Leader() (string, error) {
* *****************************************************************/ * *****************************************************************/
func (conn *Connection) Peers() ([]string, error) { func (conn *Connection) Peers() ([]string, error) {
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
var ans []string var ans []string
return ans, errClosed return ans, errClosed
} }
plist := make ([]string,0) plist := make([]string, 0)
trace("%s: Peers(), calling updateClusterInfo()",conn.ID) trace("%s: Peers(), calling updateClusterInfo()", conn.ID)
err := conn.updateClusterInfo() err := conn.updateClusterInfo()
if ( err != nil ) { if err != nil {
trace("%s: Peers() got error from updateClusterInfo(): %s",conn.ID,err.Error()) trace("%s: Peers() got error from updateClusterInfo(): %s", conn.ID, err.Error())
return plist, err return plist, err
} else { } else {
trace("%s: Peers(), updateClusterInfo() OK",conn.ID) trace("%s: Peers(), updateClusterInfo() OK", conn.ID)
} }
plist = append(plist,conn.cluster.leader.String()) plist = append(plist, conn.cluster.leader.String())
for _, p := range conn.cluster.otherPeers { for _, p := range conn.cluster.otherPeers {
plist = append(plist,p.String()) plist = append(plist, p.String())
} }
return plist, nil return plist, nil
} }
@ -148,15 +147,15 @@ func (conn *Connection) Peers() ([]string, error) {
* *****************************************************************/ * *****************************************************************/
func (conn *Connection) SetConsistencyLevel(levelDesired string) error { func (conn *Connection) SetConsistencyLevel(levelDesired string) error {
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
return errClosed return errClosed
} }
_, ok := consistencyLevels[levelDesired] _, ok := consistencyLevels[levelDesired]
if ( ok ) { if ok {
conn.consistencyLevel = consistencyLevels[levelDesired] conn.consistencyLevel = consistencyLevels[levelDesired]
return nil return nil
} }
return errors.New(fmt.Sprintf("unknown consistency level: %s",levelDesired)) return errors.New(fmt.Sprintf("unknown consistency level: %s", levelDesired))
} }
/* ***************************************************************** /* *****************************************************************
@ -171,7 +170,7 @@ func (conn *Connection) SetConsistencyLevel(levelDesired string) error {
be the leader. The next thing Open() does is updateClusterInfo() be the leader. The next thing Open() does is updateClusterInfo()
so the truth will be revealed soon enough. so the truth will be revealed soon enough.
initConnection() does not talk to rqlite. It only parses the initConnection() does not talk to rqlite. It only parses the
connection URL and prepares the new connection for work. connection URL and prepares the new connection for work.
URL format: URL format:
@ -204,21 +203,21 @@ func (conn *Connection) initConnection(url string) error {
// do some sanity checks. You know users. // do some sanity checks. You know users.
if ( len(url) < 7 ) { if len(url) < 7 {
return errors.New("url specified is impossibly short") return errors.New("url specified is impossibly short")
} }
if ( strings.HasPrefix(url,"http") == false ) { if strings.HasPrefix(url, "http") == false {
return errors.New("url does not start with 'http'") return errors.New("url does not start with 'http'")
} }
u, err := nurl.Parse(url) u, err := nurl.Parse(url)
if ( err != nil ) { if err != nil {
return err return err
} }
trace("%s: net.url.Parse() OK",conn.ID) trace("%s: net.url.Parse() OK", conn.ID)
if ( u.Scheme == "https" ) { if u.Scheme == "https" {
conn.wantsHTTPS = true conn.wantsHTTPS = true
} }
@ -233,27 +232,27 @@ func (conn *Connection) initConnection(url string) error {
// not guaranteed, so test if set // not guaranteed, so test if set
pass, isset := u.User.Password() pass, isset := u.User.Password()
if ( isset ) { if isset {
conn.password = pass conn.password = pass
} else { } else {
conn.password = "" conn.password = ""
} }
} }
if ( u.Host == "" ) { if u.Host == "" {
conn.cluster.leader.hostname = "localhost" conn.cluster.leader.hostname = "localhost"
} else { } else {
conn.cluster.leader.hostname = u.Host conn.cluster.leader.hostname = u.Host
} }
if ( u.Host == "" ) { if u.Host == "" {
conn.cluster.leader.hostname = "localhost" conn.cluster.leader.hostname = "localhost"
conn.cluster.leader.port = "4001" conn.cluster.leader.port = "4001"
} else { } else {
// SplitHostPort() should only return an error if there is no host port. // SplitHostPort() should only return an error if there is no host port.
// I think. // I think.
h, p, err := net.SplitHostPort(u.Host) h, p, err := net.SplitHostPort(u.Host)
if ( err != nil ) { if err != nil {
conn.cluster.leader.hostname = u.Host conn.cluster.leader.hostname = u.Host
} else { } else {
conn.cluster.leader.hostname = h conn.cluster.leader.hostname = h
@ -263,41 +262,39 @@ func (conn *Connection) initConnection(url string) error {
/* /*
at the moment, the only allowed query is "level=" with at the moment, the only allowed query is "level=" with
the desired consistency level the desired consistency level
*/ */
// default // default
conn.consistencyLevel = cl_WEAK conn.consistencyLevel = cl_WEAK
if ( u.RawQuery != "" ) { if u.RawQuery != "" {
if ( u.RawQuery == "level=weak") { if u.RawQuery == "level=weak" {
// that's ok but nothing to do // that's ok but nothing to do
} else if ( u.RawQuery == "level=strong" ) { } else if u.RawQuery == "level=strong" {
conn.consistencyLevel = cl_STRONG conn.consistencyLevel = cl_STRONG
} else if ( u.RawQuery == "level=none" ) { // the fools! } else if u.RawQuery == "level=none" { // the fools!
conn.consistencyLevel = cl_NONE conn.consistencyLevel = cl_NONE
} else { } else {
return errors.New("don't know what to do with this query: " + u.RawQuery) return errors.New("don't know what to do with this query: " + u.RawQuery)
} }
} }
trace("%s: parseDefaultPeer() is done:",conn.ID) trace("%s: parseDefaultPeer() is done:", conn.ID)
if ( conn.wantsHTTPS == true ) { if conn.wantsHTTPS == true {
trace("%s: %s -> %s",conn.ID,"wants https?","yes") trace("%s: %s -> %s", conn.ID, "wants https?", "yes")
} else { } else {
trace("%s: %s -> %s",conn.ID,"wants https?","no") trace("%s: %s -> %s", conn.ID, "wants https?", "no")
} }
trace("%s: %s -> %s",conn.ID,"username",conn.username) trace("%s: %s -> %s", conn.ID, "username", conn.username)
trace("%s: %s -> %s",conn.ID,"password",conn.password) trace("%s: %s -> %s", conn.ID, "password", conn.password)
trace("%s: %s -> %s",conn.ID,"hostname",conn.cluster.leader.hostname) trace("%s: %s -> %s", conn.ID, "hostname", conn.cluster.leader.hostname)
trace("%s: %s -> %s",conn.ID,"port",conn.cluster.leader.port) trace("%s: %s -> %s", conn.ID, "port", conn.cluster.leader.port)
trace("%s: %s -> %s",conn.ID,"consistencyLevel",consistencyLevelNames[conn.consistencyLevel]) trace("%s: %s -> %s", conn.ID, "consistencyLevel", consistencyLevelNames[conn.consistencyLevel])
conn.cluster.conn = conn conn.cluster.conn = conn
return nil return nil
} }

View File

@ -10,11 +10,11 @@
Learn more about rqlite at: https://github.com/rqlite/rqlite Learn more about rqlite at: https://github.com/rqlite/rqlite
*/ */
package gorqlite package gorqlite
/* /*
this file contains package-level stuff: this file contains package-level stuff:
consts consts
init() init()
Open, TraceOn(), TraceOff() Open, TraceOn(), TraceOff()
@ -33,16 +33,19 @@ import "strings"
* *****************************************************************/ * *****************************************************************/
type consistencyLevel int type consistencyLevel int
const ( const (
cl_NONE consistencyLevel = iota cl_NONE consistencyLevel = iota
cl_WEAK cl_WEAK
cl_STRONG cl_STRONG
) )
// used in several places, actually // used in several places, actually
var consistencyLevelNames map[consistencyLevel]string var consistencyLevelNames map[consistencyLevel]string
var consistencyLevels map[string]consistencyLevel var consistencyLevels map[string]consistencyLevel
type apiOperation int type apiOperation int
const ( const (
api_QUERY apiOperation = iota api_QUERY apiOperation = iota
api_STATUS api_STATUS
@ -56,7 +59,7 @@ const (
* *****************************************************************/ * *****************************************************************/
func init() { func init() {
traceOut = ioutil.Discard traceOut = ioutil.Discard
consistencyLevelNames = make(map[consistencyLevel]string) consistencyLevelNames = make(map[consistencyLevel]string)
consistencyLevelNames[cl_NONE] = "none" consistencyLevelNames[cl_NONE] = "none"
@ -70,7 +73,6 @@ func init() {
} }
/* ***************************************************************** /* *****************************************************************
Open() creates and returns a "connection" to rqlite. Open() creates and returns a "connection" to rqlite.
@ -99,7 +101,7 @@ func Open(connURL string) (Connection, error) {
return conn, err return conn, err
} }
conn.ID = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) conn.ID = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
trace("%s: Open() called for url: %s",conn.ID,connURL) trace("%s: Open() called for url: %s", conn.ID, connURL)
// set defaults // set defaults
conn.timeout = 10 conn.timeout = 10
@ -107,7 +109,7 @@ func Open(connURL string) (Connection, error) {
// parse the URL given // parse the URL given
err = conn.initConnection(connURL) err = conn.initConnection(connURL)
if ( err != nil ) { if err != nil {
return conn, err return conn, err
} }
@ -124,7 +126,7 @@ func Open(connURL string) (Connection, error) {
func: trace() func: trace()
adds a message to the trace output adds a message to the trace output
not a public function. we (inside) can add - outside they can not a public function. we (inside) can add - outside they can
only see. only see.
@ -132,7 +134,7 @@ func Open(connURL string) (Connection, error) {
Call trace as: Sprintf pattern , args... Call trace as: Sprintf pattern , args...
This is done so that the more expensive Sprintf() stuff is This is done so that the more expensive Sprintf() stuff is
done only if truly needed. When tracing is off, calls to done only if truly needed. When tracing is off, calls to
trace() just hit a bool check and return. If tracing is on, trace() just hit a bool check and return. If tracing is on,
then the Sprintfing is done at a leisurely pace because, well, then the Sprintfing is done at a leisurely pace because, well,
we're tracing. we're tracing.
@ -146,7 +148,7 @@ func Open(connURL string) (Connection, error) {
func trace(pattern string, args ...interface{}) { func trace(pattern string, args ...interface{}) {
// don't do the probably expensive Sprintf() if not needed // don't do the probably expensive Sprintf() if not needed
if ( wantsTrace == false ) { if wantsTrace == false {
return return
} }
@ -156,8 +158,8 @@ func trace(pattern string, args ...interface{}) {
// make sure there is one and only one newline // make sure there is one and only one newline
nlPattern := strings.TrimSpace(pattern) + "\n" nlPattern := strings.TrimSpace(pattern) + "\n"
msg := fmt.Sprintf(nlPattern,args...) msg := fmt.Sprintf(nlPattern, args...)
traceOut.Write( []byte( msg ) ) traceOut.Write([]byte(msg))
} }
/* /*

135
query.go
View File

@ -91,51 +91,51 @@ QueryOne() is a convenience method that wraps Query() into a single-statement
method. method.
*/ */
func (conn *Connection) QueryOne(sqlStatement string) (qr QueryResult, err error) { func (conn *Connection) QueryOne(sqlStatement string) (qr QueryResult, err error) {
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
qr.Err = errClosed qr.Err = errClosed
return qr, errClosed return qr, errClosed
} }
sqlStatements := make([]string,0) sqlStatements := make([]string, 0)
sqlStatements = append(sqlStatements,sqlStatement) sqlStatements = append(sqlStatements, sqlStatement)
qra, err := conn.Query(sqlStatements) qra, err := conn.Query(sqlStatements)
return qra[0], err return qra[0], err
} }
/* /*
Query() is used to perform SELECT operations in the database. Query() is used to perform SELECT operations in the database.
It takes an array of SQL statements and executes them in a single transaction, returning an array of QueryResult vars. It takes an array of SQL statements and executes them in a single transaction, returning an array of QueryResult vars.
*/ */
func (conn *Connection) Query(sqlStatements []string) (results []QueryResult, err error) { func (conn *Connection) Query(sqlStatements []string) (results []QueryResult, err error) {
results = make([]QueryResult,0) results = make([]QueryResult, 0)
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
var errResult QueryResult var errResult QueryResult
errResult.Err = errClosed errResult.Err = errClosed
results = append(results,errResult) results = append(results, errResult)
return results, errClosed return results, errClosed
} }
trace("%s: Query() for %d statements",conn.ID,len(sqlStatements)) trace("%s: Query() for %d statements", conn.ID, len(sqlStatements))
// if we get an error POSTing, that's a showstopper // if we get an error POSTing, that's a showstopper
response, err := conn.rqliteApiPost(api_QUERY,sqlStatements) response, err := conn.rqliteApiPost(api_QUERY, sqlStatements)
if ( err != nil ) { if err != nil {
trace("%s: rqliteApiCall() ERROR: %s",conn.ID,err.Error()) trace("%s: rqliteApiCall() ERROR: %s", conn.ID, err.Error())
var errResult QueryResult var errResult QueryResult
errResult.Err = err errResult.Err = err
results = append(results,errResult) results = append(results, errResult)
return results, err return results, err
} }
trace("%s: rqliteApiCall() OK",conn.ID) trace("%s: rqliteApiCall() OK", conn.ID)
// if we get an error Unmarshaling, that's a showstopper // if we get an error Unmarshaling, that's a showstopper
var sections map[string]interface{} var sections map[string]interface{}
err = json.Unmarshal(response,&sections) err = json.Unmarshal(response, &sections)
if ( err != nil ) { if err != nil {
trace("%s: json.Unmarshal() ERROR: %s",conn.ID,err.Error()) trace("%s: json.Unmarshal() ERROR: %s", conn.ID, err.Error())
var errResult QueryResult var errResult QueryResult
errResult.Err = err errResult.Err = err
results = append(results,errResult) results = append(results, errResult)
return results, err return results, err
} }
@ -145,13 +145,13 @@ func (conn *Connection) Query(sqlStatements []string) (results []QueryResult, er
*/ */
resultsArray := sections["results"].([]interface{}) resultsArray := sections["results"].([]interface{})
trace("%s: I have %d result(s) to parse",conn.ID,len(resultsArray)) trace("%s: I have %d result(s) to parse", conn.ID, len(resultsArray))
numStatementErrors := 0 numStatementErrors := 0
for n, r := range resultsArray { for n, r := range resultsArray {
trace("%s: parsing result %d",conn.ID,n) trace("%s: parsing result %d", conn.ID, n)
var thisQR QueryResult var thisQR QueryResult
thisQR.conn = conn thisQR.conn = conn
// r is a hash with columns, types, values, and time // r is a hash with columns, types, values, and time
thisResult := r.(map[string]interface{}) thisResult := r.(map[string]interface{})
@ -159,9 +159,9 @@ func (conn *Connection) Query(sqlStatements []string) (results []QueryResult, er
// did we get an error? // did we get an error?
_, ok := thisResult["error"] _, ok := thisResult["error"]
if ok { if ok {
trace("%s: have an error on this result: %s",conn.ID,thisResult["error"].(string)) trace("%s: have an error on this result: %s", conn.ID, thisResult["error"].(string))
thisQR.Err = errors.New(thisResult["error"].(string)) thisQR.Err = errors.New(thisResult["error"].(string))
results = append(results,thisQR) results = append(results, thisQR)
numStatementErrors++ numStatementErrors++
continue continue
} }
@ -172,28 +172,28 @@ func (conn *Connection) Query(sqlStatements []string) (results []QueryResult, er
// column & type are an array of strings // column & type are an array of strings
c := thisResult["columns"].([]interface{}) c := thisResult["columns"].([]interface{})
t := thisResult["types"].([]interface{}) t := thisResult["types"].([]interface{})
for i := 0; i < len(c) ; i++ { for i := 0; i < len(c); i++ {
thisQR.columns = append(thisQR.columns,c[i].(string)) thisQR.columns = append(thisQR.columns, c[i].(string))
thisQR.types = append(thisQR.types,t[i].(string)) thisQR.types = append(thisQR.types, t[i].(string))
} }
// and values are an array of arrays // and values are an array of arrays
if ( thisResult["values"] != nil ) { if thisResult["values"] != nil {
thisQR.values = thisResult["values"].([]interface{}) thisQR.values = thisResult["values"].([]interface{})
} else { } else {
trace("%s: fyi, no values this query",conn.ID) trace("%s: fyi, no values this query", conn.ID)
} }
thisQR.rowNumber = -1 thisQR.rowNumber = -1
trace("%s: this result (#col,time) %d %f",conn.ID,len(thisQR.columns),thisQR.Timing) trace("%s: this result (#col,time) %d %f", conn.ID, len(thisQR.columns), thisQR.Timing)
results = append(results,thisQR) results = append(results, thisQR)
} }
trace("%s: finished parsing, returning %d results",conn.ID,len(results)) trace("%s: finished parsing, returning %d results", conn.ID, len(results))
if ( numStatementErrors > 0 ) { if numStatementErrors > 0 {
return results, errors.New(fmt.Sprintf("there were %d statement errors",numStatementErrors)) return results, errors.New(fmt.Sprintf("there were %d statement errors", numStatementErrors))
} else { } else {
return results, nil return results, nil
} }
@ -217,12 +217,12 @@ then a QueryResult would hold any errors from that query, a list of columns and
Query() returns an array of QueryResult vars, while QueryOne() returns a single variable. Query() returns an array of QueryResult vars, while QueryOne() returns a single variable.
*/ */
type QueryResult struct { type QueryResult struct {
conn *Connection conn *Connection
Err error Err error
columns []string columns []string
types []string types []string
Timing float64 Timing float64
values []interface{} values []interface{}
rowNumber int64 rowNumber int64
} }
@ -232,7 +232,7 @@ type QueryResult struct {
/* ***************************************************************** /* *****************************************************************
method: QueryResult.Columns() method: QueryResult.Columns()
* *****************************************************************/ * *****************************************************************/
@ -252,7 +252,7 @@ func (qr *QueryResult) Columns() []string {
/* /*
Map() returns the current row (as advanced by Next()) as a map[string]interface{} Map() returns the current row (as advanced by Next()) as a map[string]interface{}
The key is a string corresponding to a column name. The key is a string corresponding to a column name.
The value is the corresponding column. The value is the corresponding column.
Note that only json values are supported, so you will need to type the interface{} accordingly. Note that only json values are supported, so you will need to type the interface{} accordingly.
@ -260,13 +260,13 @@ Note that only json values are supported, so you will need to type the interface
func (qr *QueryResult) Map() (map[string]interface{}, error) { func (qr *QueryResult) Map() (map[string]interface{}, error) {
trace("%s: Map() called for row %d", qr.conn.ID, qr.rowNumber) trace("%s: Map() called for row %d", qr.conn.ID, qr.rowNumber)
ans := make(map[string]interface{}) ans := make(map[string]interface{})
if ( qr.rowNumber == -1 ) { if qr.rowNumber == -1 {
return ans, errors.New("you need to Next() before you Map(), sorry, it's complicated") return ans, errors.New("you need to Next() before you Map(), sorry, it's complicated")
} }
thisRowValues := qr.values[qr.rowNumber].([]interface {}) thisRowValues := qr.values[qr.rowNumber].([]interface{})
for i := 0; i<len(qr.columns); i++ { for i := 0; i < len(qr.columns); i++ {
ans[qr.columns[i]] = thisRowValues[i] ans[qr.columns[i]] = thisRowValues[i]
} }
@ -290,15 +290,15 @@ A common idiom:
rows := conn.Write(something) rows := conn.Write(something)
for rows.Next() { for rows.Next() {
// your Scan/Map and processing here. // your Scan/Map and processing here.
} }
*/ */
func (qr *QueryResult) Next() bool { func (qr *QueryResult) Next() bool {
if ( qr.rowNumber >= int64(len(qr.values) - 1 )) { if qr.rowNumber >= int64(len(qr.values)-1) {
return false return false
} }
qr.rowNumber += 1 qr.rowNumber += 1
return true return true
} }
/* ***************************************************************** /* *****************************************************************
@ -346,31 +346,31 @@ are a subset of the types JSON uses:
booleans, JSON arrays, and JSON objects are not supported, booleans, JSON arrays, and JSON objects are not supported,
since sqlite does not support them. since sqlite does not support them.
*/ */
func (qr *QueryResult) Scan(dest... interface{}) error { func (qr *QueryResult) Scan(dest ...interface{}) error {
trace("%s: Scan() called for %d vars", qr.conn.ID,len(dest)) trace("%s: Scan() called for %d vars", qr.conn.ID, len(dest))
if ( qr.rowNumber == -1 ) { if qr.rowNumber == -1 {
return errors.New("you need to Next() before you Scan(), sorry, it's complicated") return errors.New("you need to Next() before you Scan(), sorry, it's complicated")
} }
if ( len(dest) != len(qr.columns) ) { if len(dest) != len(qr.columns) {
return errors.New(fmt.Sprintf("expected %d columns but got %d vars\n", len(qr.columns), len(dest))) return errors.New(fmt.Sprintf("expected %d columns but got %d vars\n", len(qr.columns), len(dest)))
} }
thisRowValues := qr.values[qr.rowNumber].([]interface {}) thisRowValues := qr.values[qr.rowNumber].([]interface{})
for n, d := range dest { for n, d := range dest {
switch d.(type) { switch d.(type) {
case *int64: case *int64:
f := int64(thisRowValues[n].(float64)) f := int64(thisRowValues[n].(float64))
*d.(*int64) = f *d.(*int64) = f
case *float64: case *float64:
f := float64(thisRowValues[n].(float64)) f := float64(thisRowValues[n].(float64))
*d.(*float64) = f *d.(*float64) = f
case *string: case *string:
s := string(thisRowValues[n].(string)) s := string(thisRowValues[n].(string))
*d.(*string) = s *d.(*string) = s
default: default:
return errors.New(fmt.Sprintf("unknown destination type to scan into in variable #%d",n)) return errors.New(fmt.Sprintf("unknown destination type to scan into in variable #%d", n))
} }
} }
@ -393,4 +393,3 @@ This info may additionally conflict with the reality that your data is being JSO
func (qr *QueryResult) Types() []string { func (qr *QueryResult) Types() []string {
return qr.types return qr.types
} }

View File

@ -2,7 +2,7 @@ package gorqlite
import "testing" import "testing"
func TestQueryOne (t *testing.T) { func TestQueryOne(t *testing.T) {
var wr WriteResult var wr WriteResult
var qr QueryResult var qr QueryResult
var wResults []WriteResult var wResults []WriteResult
@ -11,59 +11,59 @@ func TestQueryOne (t *testing.T) {
t.Logf("trying Open") t.Logf("trying Open")
conn, err := Open(testUrl()) conn, err := Open(testUrl())
if ( err != nil ) { if err != nil {
t.Logf("--> FATAL") t.Logf("--> FATAL")
t.Fatal() t.Fatal()
} }
t.Logf("trying WriteOne DROP") t.Logf("trying WriteOne DROP")
wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName()) wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName())
if ( err != nil ) { if err != nil {
t.Logf("--> FATAL") t.Logf("--> FATAL")
t.Fatal() t.Fatal()
} }
t.Logf("trying WriteOne CREATE") t.Logf("trying WriteOne CREATE")
wr, err = conn.WriteOne("CREATE TABLE " + testTableName() + " (id integer, name text)") wr, err = conn.WriteOne("CREATE TABLE " + testTableName() + " (id integer, name text)")
if ( err != nil ) { if err != nil {
t.Logf("--> FATAL") t.Logf("--> FATAL")
t.Fatal() t.Fatal()
} }
t.Logf("trying Write INSERT") t.Logf("trying Write INSERT")
s := make([]string,0) s := make([]string, 0)
s = append(s,"INSERT INTO " + testTableName() + " (id, name) VALUES ( 1, 'Romulan' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 1, 'Romulan' )")
s = append(s,"INSERT INTO " + testTableName() + " (id, name) VALUES ( 2, 'Vulcan' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 2, 'Vulcan' )")
s = append(s,"INSERT INTO " + testTableName() + " (id, name) VALUES ( 3, 'Klingon' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 3, 'Klingon' )")
s = append(s,"INSERT INTO " + testTableName() + " (id, name) VALUES ( 4, 'Ferengi' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 4, 'Ferengi' )")
s = append(s,"INSERT INTO " + testTableName() + " (id, name) VALUES ( 5, 'Cardassian' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 5, 'Cardassian' )")
wResults, err = conn.Write(s) wResults, err = conn.Write(s)
if ( err != nil ) { if err != nil {
t.Logf("--> FATAL") t.Logf("--> FATAL")
t.Fatal() t.Fatal()
} }
t.Logf("trying QueryOne") t.Logf("trying QueryOne")
qr, err = conn.QueryOne("SELECT name FROM " + testTableName() + " WHERE id > 3") qr, err = conn.QueryOne("SELECT name FROM " + testTableName() + " WHERE id > 3")
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying Next()") t.Logf("trying Next()")
na := qr.Next() na := qr.Next()
if ( na != true ) { if na != true {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying Map()") t.Logf("trying Map()")
r, err := qr.Map() r, err := qr.Map()
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
if ( r["name"].(string) != "Ferengi" ) { if r["name"].(string) != "Ferengi" {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
@ -71,51 +71,51 @@ func TestQueryOne (t *testing.T) {
t.Logf("trying Scan(), also float64->int64 in Scan()") t.Logf("trying Scan(), also float64->int64 in Scan()")
var id int64 var id int64
var name string var name string
err = qr.Scan(&id,&name) err = qr.Scan(&id, &name)
if ( err == nil ) { if err == nil {
t.Logf("--> FAILED (%s)",err.Error()) t.Logf("--> FAILED (%s)", err.Error())
t.Fail() t.Fail()
} }
err = qr.Scan(&name) err = qr.Scan(&name)
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED (%s)",err.Error()) t.Logf("--> FAILED (%s)", err.Error())
t.Fail() t.Fail()
} }
if ( name != "Ferengi" ) { if name != "Ferengi" {
t.Logf("--> FAILED, name should be 'Ferengi' but it's '%s'",name) t.Logf("--> FAILED, name should be 'Ferengi' but it's '%s'", name)
t.Fail() t.Fail()
} }
qr.Next() qr.Next()
err = qr.Scan(&name) err = qr.Scan(&name)
if ( name != "Cardassian" ) { if name != "Cardassian" {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying WriteOne DROP") t.Logf("trying WriteOne DROP")
wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "") wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "")
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying Close") t.Logf("trying Close")
conn.Close() conn.Close()
t.Logf("trying WriteOne after Close") t.Logf("trying WriteOne after Close")
wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "") wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "")
if ( err == nil ) { if err == nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
_ = wr _ = wr
t.Logf("trying Write after Close") t.Logf("trying Write after Close")
t1 := make([]string,0) t1 := make([]string, 0)
t1 = append(t1,"DROP TABLE IF EXISTS " + testTableName() + "") t1 = append(t1, "DROP TABLE IF EXISTS "+testTableName()+"")
t1 = append(t1,"DROP TABLE IF EXISTS " + testTableName() + "") t1 = append(t1, "DROP TABLE IF EXISTS "+testTableName()+"")
wResults, err = conn.Write(t1) wResults, err = conn.Write(t1)
if ( err == nil ) { if err == nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
@ -123,24 +123,22 @@ func TestQueryOne (t *testing.T) {
t.Logf("trying QueryOne after Close") t.Logf("trying QueryOne after Close")
qr, err = conn.QueryOne("SELECT id FROM " + testTableName() + "") qr, err = conn.QueryOne("SELECT id FROM " + testTableName() + "")
if ( err == nil ) { if err == nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
_ = qr _ = qr
t.Logf("trying Query after Close") t.Logf("trying Query after Close")
t2 := make([]string,0) t2 := make([]string, 0)
t2 = append(t2,"SELECT id FROM " + testTableName() + "") t2 = append(t2, "SELECT id FROM "+testTableName()+"")
t2 = append(t2,"SELECT name FROM " + testTableName() + "") t2 = append(t2, "SELECT name FROM "+testTableName()+"")
t2 = append(t2,"SELECT id,name FROM " + testTableName() + "") t2 = append(t2, "SELECT id,name FROM "+testTableName()+"")
qResults, err = conn.Query(t2) qResults, err = conn.Query(t2)
if ( err == nil ) { if err == nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
_ = qResults _ = qResults
} }

View File

@ -8,7 +8,7 @@ import "os"
func testUrl() string { func testUrl() string {
url := os.Getenv("GORQLITE_TEST_URL") url := os.Getenv("GORQLITE_TEST_URL")
if ( url == "" ) { if url == "" {
url = "http://" url = "http://"
} }
return url return url
@ -16,10 +16,8 @@ func testUrl() string {
func testTableName() string { func testTableName() string {
tableName := os.Getenv("GORQLITE_TEST_TABLE") tableName := os.Getenv("GORQLITE_TEST_TABLE")
if ( tableName == "" ) { if tableName == "" {
tableName = "gorqlite_test" tableName = "gorqlite_test"
} }
return tableName return tableName
} }

View File

@ -1,7 +1,7 @@
package gorqlite package gorqlite
/* /*
this file has this file has
Write() Write()
WriteResult and its methods WriteResult and its methods
*/ */
@ -58,15 +58,15 @@ method.
*/ */
func (conn *Connection) WriteOne(sqlStatement string) (wr WriteResult, err error) { func (conn *Connection) WriteOne(sqlStatement string) (wr WriteResult, err error) {
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
wr.Err = errClosed wr.Err = errClosed
return wr, errClosed return wr, errClosed
} }
sqlStatements := make([]string,0) sqlStatements := make([]string, 0)
sqlStatements = append(sqlStatements,sqlStatement) sqlStatements = append(sqlStatements, sqlStatement)
wra , err := conn.Write(sqlStatements) wra, err := conn.Write(sqlStatements)
return wra[0], err return wra[0], err
} }
/* /*
Write() is used to perform DDL/DML in the database. ALTER, CREATE, DELETE, DROP, INSERT, UPDATE, etc. all go through Write(). Write() is used to perform DDL/DML in the database. ALTER, CREATE, DELETE, DROP, INSERT, UPDATE, etc. all go through Write().
@ -78,34 +78,34 @@ All statements are executed as a single transaction.
Write() returns an error if one is encountered during its operation. If it's something like a call to the rqlite API, then it'll return that error. If one statement out of several has an error, it will return a generic "there were %d statement errors" and you'll have to look at the individual statement's Err for more info. Write() returns an error if one is encountered during its operation. If it's something like a call to the rqlite API, then it'll return that error. If one statement out of several has an error, it will return a generic "there were %d statement errors" and you'll have to look at the individual statement's Err for more info.
*/ */
func (conn *Connection) Write(sqlStatements []string) (results []WriteResult, err error) { func (conn *Connection) Write(sqlStatements []string) (results []WriteResult, err error) {
results = make([]WriteResult,0) results = make([]WriteResult, 0)
if ( conn.hasBeenClosed) { if conn.hasBeenClosed {
var errResult WriteResult var errResult WriteResult
errResult.Err = errClosed errResult.Err = errClosed
results = append(results,errResult) results = append(results, errResult)
return results, errClosed return results, errClosed
} }
trace("%s: Write() for %d statements",conn.ID,len(sqlStatements)) trace("%s: Write() for %d statements", conn.ID, len(sqlStatements))
response, err := conn.rqliteApiPost(api_WRITE,sqlStatements) response, err := conn.rqliteApiPost(api_WRITE, sqlStatements)
if ( err != nil ) { if err != nil {
trace("%s: rqliteApiCall() ERROR: %s",conn.ID,err.Error()) trace("%s: rqliteApiCall() ERROR: %s", conn.ID, err.Error())
var errResult WriteResult var errResult WriteResult
errResult.Err = err errResult.Err = err
results = append(results,errResult) results = append(results, errResult)
return results, err return results, err
} }
trace("%s: rqliteApiCall() OK",conn.ID) trace("%s: rqliteApiCall() OK", conn.ID)
var sections map[string]interface{} var sections map[string]interface{}
err = json.Unmarshal(response,&sections) err = json.Unmarshal(response, &sections)
if ( err != nil ) { if err != nil {
trace("%s: json.Unmarshal() ERROR: %s",conn.ID,err.Error()) trace("%s: json.Unmarshal() ERROR: %s", conn.ID, err.Error())
var errResult WriteResult var errResult WriteResult
errResult.Err = err errResult.Err = err
results = append(results,errResult) results = append(results, errResult)
return results, err return results, err
} }
@ -115,44 +115,44 @@ func (conn *Connection) Write(sqlStatements []string) (results []WriteResult, er
*/ */
resultsArray := sections["results"].([]interface{}) resultsArray := sections["results"].([]interface{})
trace("%s: I have %d result(s) to parse",conn.ID,len(resultsArray)) trace("%s: I have %d result(s) to parse", conn.ID, len(resultsArray))
numStatementErrors := 0 numStatementErrors := 0
for n, k := range resultsArray { for n, k := range resultsArray {
trace("%s: starting on result %d",conn.ID,n) trace("%s: starting on result %d", conn.ID, n)
thisResult := k.(map[string]interface{}) thisResult := k.(map[string]interface{})
var thisWR WriteResult var thisWR WriteResult
thisWR.conn = conn thisWR.conn = conn
// did we get an error? // did we get an error?
_, ok := thisResult["error"] _, ok := thisResult["error"]
if ok { if ok {
trace("%s: have an error on this result: %s",conn.ID,thisResult["error"].(string)) trace("%s: have an error on this result: %s", conn.ID, thisResult["error"].(string))
thisWR.Err = errors.New(thisResult["error"].(string)) thisWR.Err = errors.New(thisResult["error"].(string))
results = append(results,thisWR) results = append(results, thisWR)
numStatementErrors += 1 numStatementErrors += 1
continue continue
} }
_, ok = thisResult["last_insert_id"] _, ok = thisResult["last_insert_id"]
if ok { if ok {
thisWR.LastInsertID = int64(thisResult["last_insert_id"].(float64)) thisWR.LastInsertID = int64(thisResult["last_insert_id"].(float64))
} }
_, ok = thisResult["rows_affected"] // could be zero for a CREATE _, ok = thisResult["rows_affected"] // could be zero for a CREATE
if ok { if ok {
thisWR.RowsAffected = int64(thisResult["rows_affected"].(float64)) thisWR.RowsAffected = int64(thisResult["rows_affected"].(float64))
} }
thisWR.Timing = thisResult["time"].(float64) thisWR.Timing = thisResult["time"].(float64)
trace("%s: this result (LII,RA,T): %d %d %f",conn.ID,thisWR.LastInsertID,thisWR.RowsAffected,thisWR.Timing) trace("%s: this result (LII,RA,T): %d %d %f", conn.ID, thisWR.LastInsertID, thisWR.RowsAffected, thisWR.Timing)
results = append(results,thisWR) results = append(results, thisWR)
} }
trace("%s: finished parsing, returning %d results",conn.ID,len(results)) trace("%s: finished parsing, returning %d results", conn.ID, len(results))
if ( numStatementErrors > 0 ) { if numStatementErrors > 0 {
return results, errors.New(fmt.Sprintf("there were %d statement errors",numStatementErrors)) return results, errors.New(fmt.Sprintf("there were %d statement errors", numStatementErrors))
} else { } else {
return results, nil return results, nil
} }
@ -170,10 +170,9 @@ A WriteResult holds the result of a single statement sent to Write().
Write() returns an array of WriteResult vars, while WriteOne() returns a single WriteResult. Write() returns an array of WriteResult vars, while WriteOne() returns a single WriteResult.
*/ */
type WriteResult struct { type WriteResult struct {
Err error // don't trust the rest if this isn't nil Err error // don't trust the rest if this isn't nil
Timing float64 Timing float64
RowsAffected int64 // affected by the change RowsAffected int64 // affected by the change
LastInsertID int64 // if relevant, otherwise zero value LastInsertID int64 // if relevant, otherwise zero value
conn *Connection conn *Connection
} }

View File

@ -1,108 +1,108 @@
package gorqlite package gorqlite
import "testing" import "testing"
// import "os" // import "os"
func TestWriteOne (t *testing.T) { func TestWriteOne(t *testing.T) {
var wr WriteResult var wr WriteResult
var err error var err error
t.Logf("trying Open") t.Logf("trying Open")
conn, err := Open(testUrl()) conn, err := Open(testUrl())
if ( err != nil ) { if err != nil {
t.Logf("--> FATAL") t.Logf("--> FATAL")
t.Fatal(err) t.Fatal(err)
} }
t.Logf("trying WriteOne DROP") t.Logf("trying WriteOne DROP")
wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "") wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "")
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying WriteOne CTHULHU (should fail, bad SQL)") t.Logf("trying WriteOne CTHULHU (should fail, bad SQL)")
wr, err = conn.WriteOne("CTHULHU") wr, err = conn.WriteOne("CTHULHU")
if ( err == nil ) { if err == nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying WriteOne CREATE") t.Logf("trying WriteOne CREATE")
wr, err = conn.WriteOne("CREATE TABLE " + testTableName() + " (id integer, name text)") wr, err = conn.WriteOne("CREATE TABLE " + testTableName() + " (id integer, name text)")
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying WriteOne INSERT") t.Logf("trying WriteOne INSERT")
wr, err = conn.WriteOne("INSERT INTO " + testTableName() + " (id, name) VALUES ( 1, 'aaa bbb ccc' )") wr, err = conn.WriteOne("INSERT INTO " + testTableName() + " (id, name) VALUES ( 1, 'aaa bbb ccc' )")
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("checking WriteOne RowsAffected") t.Logf("checking WriteOne RowsAffected")
if ( wr.RowsAffected != 1 ) { if wr.RowsAffected != 1 {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying WriteOne DROP") t.Logf("trying WriteOne DROP")
wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "") wr, err = conn.WriteOne("DROP TABLE IF EXISTS " + testTableName() + "")
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
} }
func TestWrite (t *testing.T) { func TestWrite(t *testing.T) {
var results []WriteResult var results []WriteResult
var err error var err error
var s []string var s []string
t.Logf("trying Open") t.Logf("trying Open")
conn, err := Open(testUrl()) conn, err := Open(testUrl())
if ( err != nil ) { if err != nil {
t.Logf("--> FATAL") t.Logf("--> FATAL")
t.Fatal(err) t.Fatal(err)
} }
t.Logf("trying Write DROP & CREATE") t.Logf("trying Write DROP & CREATE")
s = make([]string,0) s = make([]string, 0)
s = append(s, "DROP TABLE IF EXISTS " + testTableName() + "") s = append(s, "DROP TABLE IF EXISTS "+testTableName()+"")
s = append(s, "CREATE TABLE " + testTableName() + " (id integer, name text)") s = append(s, "CREATE TABLE "+testTableName()+" (id integer, name text)")
results, err = conn.Write(s) results, err = conn.Write(s)
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying Write INSERT") t.Logf("trying Write INSERT")
s = make([]string,0) s = make([]string, 0)
s = append (s, "INSERT INTO " + testTableName() + " (id, name) VALUES ( 1, 'aaa bbb ccc' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 1, 'aaa bbb ccc' )")
s = append (s, "INSERT INTO " + testTableName() + " (id, name) VALUES ( 2, 'ddd eee fff' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 2, 'ddd eee fff' )")
s = append (s, "INSERT INTO " + testTableName() + " (id, name) VALUES ( 3, 'ggg hhh iii' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 3, 'ggg hhh iii' )")
s = append (s, "INSERT INTO " + testTableName() + " (id, name) VALUES ( 4, 'jjj kkk lll' )") s = append(s, "INSERT INTO "+testTableName()+" (id, name) VALUES ( 4, 'jjj kkk lll' )")
results, err = conn.Write(s) results, err = conn.Write(s)
if ( err != nil ) { if err != nil {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
if ( len(results) != 4 ) { if len(results) != 4 {
t.Logf("--> FAILED") t.Logf("--> FAILED")
t.Fail() t.Fail()
} }
t.Logf("trying Write DROP")
s = make([]string,0)
s = append(s, "DROP TABLE IF EXISTS " + testTableName() + "")
results, err = conn.Write(s)
if ( err != nil ) {
t.Logf("--> FAILED")
t.Fail()
}
}
t.Logf("trying Write DROP")
s = make([]string, 0)
s = append(s, "DROP TABLE IF EXISTS "+testTableName()+"")
results, err = conn.Write(s)
if err != nil {
t.Logf("--> FAILED")
t.Fail()
}
}