// ExecNeo executes a query that returns no rows. Implements a Neo-friendly alternative to sql/driver. func (s *boltStmt) ExecNeo(params map[string]interface{}) (Result, error) { if s.closed { return nil, errors.New("Neo4j Bolt statement already closed") } if s.rows != nil { return nil, errors.New("Another query is already open") } runResp, pullResp, _, err := s.conn.sendRunPullAllConsumeAll(s.query, params) if err != nil { return nil, err } success, ok := runResp.(messages.SuccessMessage) if !ok { return nil, errors.New("Unrecognized response type when running exec query: %#v", success) } log.Infof("Got run success message: %#v", success) success, ok = pullResp.(messages.SuccessMessage) if !ok { return nil, errors.New("Unrecognized response when discarding exec rows: %#v", success) } log.Infof("Got discard all success message: %#v", success) return newResult(success.Metadata), nil }
// Begin begins a new transaction with the Neo4J Database func (c *boltConn) Begin() (driver.Tx, error) { if c.transaction != nil { return nil, errors.New("An open transaction already exists") } if c.statement != nil { return nil, errors.New("Cannot open a transaction when you already have an open statement") } if c.closed { return nil, errors.New("Connection already closed") } successInt, pullInt, err := c.sendRunPullAllConsumeSingle("BEGIN", nil) if err != nil { return nil, errors.Wrap(err, "An error occurred beginning transaction") } success, ok := successInt.(messages.SuccessMessage) if !ok { return nil, errors.New("Unrecognized response type beginning transaction: %#v", success) } log.Infof("Got success message beginning transaction: %#v", success) success, ok = pullInt.(messages.SuccessMessage) if !ok { return nil, errors.New("Unrecognized response type pulling transaction: %#v", success) } log.Infof("Got success message pulling transaction: %#v", success) return newTx(c), nil }
func (c *boltConn) ackFailure(failure messages.FailureMessage) error { log.Infof("Acknowledging Failure: %#v", failure) ack := messages.NewAckFailureMessage() err := encoding.NewEncoder(c, c.chunkSize).Encode(ack) if err != nil { return errors.Wrap(err, "An error occurred encoding ack failure message") } for { respInt, err := encoding.NewDecoder(c).Decode() if err != nil { return errors.Wrap(err, "An error occurred decoding ack failure message response") } switch resp := respInt.(type) { case messages.IgnoredMessage: log.Infof("Got ignored message when acking failure: %#v", resp) continue case messages.SuccessMessage: log.Infof("Got success message when acking failure: %#v", resp) return nil case messages.FailureMessage: log.Errorf("Got failure message when acking failure: %#v", resp) return c.reset() default: log.Errorf("Got unrecognized response from acking failure: %#v", resp) err := c.Close() if err != nil { log.Errorf("An error occurred closing the session: %s", err) } return errors.New("Got unrecognized response from acking failure: %#v. CLOSING SESSION!", resp) } } }
// Rollback rolls back and closes the transaction func (t *boltTx) Rollback() error { if t.closed { return errors.New("Transaction already closed") } if t.conn.statement != nil { if err := t.conn.statement.Close(); err != nil { return errors.Wrap(err, "An error occurred closing open rows in transaction Rollback") } } successInt, pullInt, err := t.conn.sendRunPullAllConsumeSingle("ROLLBACK", nil) if err != nil { return errors.Wrap(err, "An error occurred rolling back transaction") } success, ok := successInt.(messages.SuccessMessage) if !ok { return errors.New("Unrecognized response type rolling back transaction: %#v", success) } log.Infof("Got success message rolling back transaction: %#v", success) pull, ok := pullInt.(messages.SuccessMessage) if !ok { return errors.New("Unrecognized response type pulling transaction: %#v", pull) } log.Infof("Got success message pulling transaction: %#v", pull) t.conn.transaction = nil t.closed = true return err }
// NextNeo gets the next row result // When the rows are completed, returns the success metadata // and io.EOF func (r *boltRows) NextNeo() ([]interface{}, map[string]interface{}, error) { if r.closed { return nil, nil, errors.New("Rows are already closed") } if !r.consumed { r.consumed = true if err := r.statement.conn.sendPullAll(); err != nil { r.finishedConsume = true return nil, nil, err } } respInt, err := r.statement.conn.consume() if err != nil { return nil, nil, err } switch resp := respInt.(type) { case messages.SuccessMessage: log.Infof("Got success message: %#v", resp) r.finishedConsume = true return nil, resp.Metadata, io.EOF case messages.RecordMessage: log.Infof("Got record message: %#v", resp) return resp.Fields, nil, nil default: return nil, nil, errors.New("Unrecognized response type getting next query row: %#v", resp) } }
func (c *boltConn) sendRun(query string, args map[string]interface{}) error { log.Infof("Sending RUN message: query %s (args: %#v)", query, args) runMessage := messages.NewRunMessage(query, args) if err := encoding.NewEncoder(c, c.chunkSize).Encode(runMessage); err != nil { return errors.Wrap(err, "An error occurred running query") } return nil }
func (c *boltConn) sendInit() (interface{}, error) { log.Infof("Sending INIT Message. ClientID: %s User: %s Password: %s", ClientID, c.user, c.password) initMessage := messages.NewInitMessage(ClientID, c.user, c.password) if err := encoding.NewEncoder(c, c.chunkSize).Encode(initMessage); err != nil { return nil, errors.Wrap(err, "An error occurred sending init message") } return c.consume() }
func (c *boltConn) sendDiscardAll() error { log.Infof("Sending DISCARD_ALL message") discardAllMessage := messages.NewDiscardAllMessage() err := encoding.NewEncoder(c, c.chunkSize).Encode(discardAllMessage) if err != nil { return errors.Wrap(err, "An error occurred encoding discard all query") } return nil }
func (c *boltConn) sendPullAll() error { log.Infof("Sending PULL_ALL message") pullAllMessage := messages.NewPullAllMessage() err := encoding.NewEncoder(c, c.chunkSize).Encode(pullAllMessage) if err != nil { return errors.Wrap(err, "An error occurred encoding pull all query") } return nil }
// NextPipeline gets the next row result // When the rows are completed, returns the success metadata and the next // set of rows. // When all rows are completed, returns io.EOF func (r *boltRows) NextPipeline() ([]interface{}, map[string]interface{}, PipelineRows, error) { if r.closed { return nil, nil, nil, errors.New("Rows are already closed") } respInt, err := r.statement.conn.consume() if err != nil { return nil, nil, nil, err } switch resp := respInt.(type) { case messages.SuccessMessage: log.Infof("Got success message: %#v", resp) if r.pipelineIndex == len(r.statement.queries)-1 { r.finishedConsume = true return nil, nil, nil, err } successResp, err := r.statement.conn.consume() if err == io.EOF { } else if err != nil { return nil, nil, nil, errors.Wrap(err, "An error occurred getting next set of rows from pipeline command: %#v", successResp) } success, ok := successResp.(messages.SuccessMessage) if !ok { return nil, nil, nil, errors.New("Unexpected response getting next set of rows from pipeline command: %#v", successResp) } r.statement.rows = newPipelineRows(r.statement, success.Metadata, r.pipelineIndex+1) r.statement.rows.closeStatement = r.closeStatement return nil, success.Metadata, r.statement.rows, nil case messages.RecordMessage: log.Infof("Got record message: %#v", resp) return resp.Fields, nil, nil, nil default: return nil, nil, nil, errors.New("Unrecognized response type getting next pipeline row: %#v", resp) } }
func (c *boltConn) reset() error { log.Info("Resetting session") reset := messages.NewResetMessage() err := encoding.NewEncoder(c, c.chunkSize).Encode(reset) if err != nil { return errors.Wrap(err, "An error occurred encoding reset message") } for { respInt, err := encoding.NewDecoder(c).Decode() if err != nil { return errors.Wrap(err, "An error occurred decoding reset message response") } switch resp := respInt.(type) { case messages.IgnoredMessage: log.Infof("Got ignored message when resetting session: %#v", resp) continue case messages.SuccessMessage: log.Infof("Got success message when resetting session: %#v", resp) return nil case messages.FailureMessage: log.Errorf("Got failure message when resetting session: %#v", resp) err = c.Close() if err != nil { log.Errorf("An error occurred closing the session: %s", err) } return errors.New("Error resetting session: %#v. CLOSING SESSION!", resp) default: log.Errorf("Got unrecognized response from resetting session: %#v", resp) err = c.Close() if err != nil { log.Errorf("An error occurred closing the session: %s", err) } return errors.New("Got unrecognized response from resetting session: %#v. CLOSING SESSION!", resp) } } }
// Close closes the rows func (r *boltRows) Close() error { if r.closed { return nil } if !r.consumed { // Discard all messages if not consumed respInt, err := r.statement.conn.sendDiscardAllConsume() if err != nil { return errors.Wrap(err, "An error occurred discarding messages on row close") } switch resp := respInt.(type) { case messages.SuccessMessage: log.Infof("Got success message: %#v", resp) default: return errors.New("Unrecognized response type discarding all rows: Value: %#v", resp) } } else if !r.finishedConsume { // If this is a pipeline statement, we need to "consume all" multiple times numConsume := 1 if r.statement.queries != nil { numQueries := len(r.statement.queries) if numQueries > 0 { // So, every pipeline statement has two successes // but by the time you get to the row object, one has // been consumed. Hence we need to clear out the // rest of the messages on close by taking the current // index * 2 but removing the first success numConsume = ((numQueries - r.pipelineIndex) * 2) - 1 } } // Clear out all unconsumed messages if we // never finished consuming them. _, _, err := r.statement.conn.consumeAllMultiple(numConsume) if err != nil { return errors.Wrap(err, "An error occurred clearing out unconsumed stream") } } r.closed = true r.statement.rows = nil if r.closeStatement { return r.statement.Close() } return nil }
func (c *boltConn) initialize() error { // Handle recorder. If there is no conn string, assume we're playing back a recording. // If there is a recorder and a conn string, assume we're recording the connection // Else, just create the conn normally var err error if c.connStr == "" && c.driver != nil && c.driver.recorder != nil { c.conn = c.driver.recorder } else if c.driver != nil && c.driver.recorder != nil { c.driver.recorder.Conn, err = c.createConn() if err != nil { return err } c.conn = c.driver.recorder } else { c.conn, err = c.createConn() if err != nil { return err } } if err := c.handShake(); err != nil { if e := c.Close(); e != nil { log.Errorf("An error occurred closing connection: %s", e) } return err } respInt, err := c.sendInit() if err != nil { if e := c.Close(); e != nil { log.Errorf("An error occurred closing connection: %s", e) } return err } switch resp := respInt.(type) { case messages.SuccessMessage: log.Infof("Successfully initiated Bolt connection: %+v", resp) return nil default: log.Errorf("Got an unrecognized message when initializing connection :%+v", resp) if e := c.Close(); e != nil { log.Errorf("An error occurred closing connection: %s", e) } return errors.New("Unrecognized response from the server: %#v", resp) } }
func (c *boltConn) consumeAll() ([]interface{}, interface{}, error) { log.Info("Consuming all responses until success/failure") responses := []interface{}{} for { respInt, err := c.consume() if err != nil { return nil, respInt, err } if success, isSuccess := respInt.(messages.SuccessMessage); isSuccess { log.Infof("Got success message: %#v", success) return responses, success, nil } responses = append(responses, respInt) } }
func (s *boltStmt) queryNeo(params map[string]interface{}) (*boltRows, error) { if s.closed { return nil, errors.New("Neo4j Bolt statement already closed") } if s.rows != nil { return nil, errors.New("Another query is already open") } respInt, err := s.conn.sendRunConsume(s.query, params) if err != nil { return nil, err } resp, ok := respInt.(messages.SuccessMessage) if !ok { return nil, errors.New("Unrecognized response type running query: %#v", resp) } log.Infof("Got success message on run query: %#v", resp) s.rows = newRows(s, resp.Metadata) return s.rows, nil }
func TestBoltDriverPool_Concurrent(t *testing.T) { if neo4jConnStr == "" { t.Skip("Cannot run this test when in recording mode") } var wg sync.WaitGroup wg.Add(2) driver, err := NewDriverPool(neo4jConnStr, 2) if err != nil { t.Fatalf("An error occurred opening driver pool: %#v", err) } one := make(chan bool) two := make(chan bool) three := make(chan bool) four := make(chan bool) five := make(chan bool) six := make(chan bool) seven := make(chan bool) go func() { defer wg.Done() conn, err := driver.OpenPool() if err != nil { t.Fatalf("An error occurred opening conn: %s", err) } defer conn.Close() data, _, _, err := conn.QueryNeoAll(`MATCH (n) RETURN n`, nil) if err != nil { t.Fatalf("An error occurred querying neo: %s", err) } log.Info("1") one <- true <-two if len(data) != 0 { t.Fatalf("Expected no data: %#v", data) } data, _, _, err = conn.QueryNeoAll(`MATCH (n) RETURN n`, nil) if err != nil { t.Fatalf("An error occurred querying neo: %s", err) } log.Infof("data: %#v", data) if len(data) != 1 { t.Fatalf("Expected no data: %#v", data) } log.Info("3") three <- true <-four data, _, _, err = conn.QueryNeoAll(`MATCH path=(:FOO)-[:BAR]->(:BAZ) RETURN path`, nil) if err != nil { t.Fatalf("An error occurred querying neo: %s", err) } if len(data) != 1 { t.Fatalf("Expected no data: %#v", data) } log.Info("5") five <- true <-six data, _, _, err = conn.QueryNeoAll(`MATCH path=(:FOO)-[:BAR]->(:BAZ) RETURN path`, nil) if err != nil { t.Fatalf("An error occurred querying neo: %s", err) } if len(data) != 0 { t.Fatalf("Expected no data: %#v", data) } log.Info("7") seven <- true }() go func() { <-one defer wg.Done() conn, err := driver.OpenPool() if err != nil { t.Fatalf("An error occurred opening conn: %s", err) } defer conn.Close() _, err = conn.ExecNeo(`CREATE (f:FOO)`, nil) if err != nil { t.Fatalf("An error occurred creating f neo: %s", err) } log.Info("2") two <- true <-three _, err = conn.ExecNeo(`MATCH (f:FOO) CREATE UNIQUE (f)-[b:BAR]->(c:BAZ)`, nil) if err != nil { t.Fatalf("An error occurred creating f neo: %s", err) } log.Info("4") four <- true <-five _, err = conn.ExecNeo(`MATCH (:FOO)-[b:BAR]->(:BAZ) DELETE b`, nil) if err != nil { t.Fatalf("An error occurred creating f neo: %s", err) } _, err = conn.ExecNeo(`MATCH (n) DETACH DELETE n`, nil) if err != nil { t.Fatalf("An error occurred creating f neo: %s", err) } log.Info("6") six <- true <-seven }() wg.Wait() }