module Neo4j module Server # The CypherTransaction object lifecycle is as follows: # * It is initialized with the transactional endpoint URL and the connection object to use for communication. It does not communicate with the server to create this. # * The first query within the transaction sets the commit and execution addresses, :commit_url and :query_url. # * At any time, `failure` can be called to mark a transaction failed and trigger a rollback upon closure. # * `close` is called to end the transaction. It calls `commit` or `delete`. # # If a transaction is created and then closed without performing any queries, an OpenStruct is returned that behaves like a successfully closed query. class CypherTransaction include Neo4j::Transaction::Instance include Resource attr_reader :commit_url, :query_url, :base_url, :connection def initialize(url, session_connection) @base_url = url @connection = session_connection register_instance end ROW_REST = %w(row REST) def _query(cypher_query, params = nil) fail 'Transaction expired, unable to perform query' if expired? statement = {statement: cypher_query, parameters: params, resultDataContents: ROW_REST} body = {statements: [statement]} response = @query_url ? query(body) : start(body) create_cypher_response(response) end def start(body) request(:post, @base_url, 201, body).tap do |response| @commit_url = response.body[:commit] @query_url = response.headers[:Location] fail "NO ENDPOINT URL #{connection} : HEAD: #{response.headers.inspect}" if !@query_url || @query_url.empty? init_resource_data(response.body, @base_url) end end def query(body) request(:post, @query_url, 200, body) end def delete return empty_response if !@commit_url || expired? request(:delete, @query_url, 200, nil, resource_headers) end def commit return empty_response if !@commit_url || expired? request(:post, @commit_url, 200, nil, resource_headers) end private def request(action, endpoint_url, expected_code = 200, body = nil, headers = {}) connection.send(action, endpoint_url, body, headers).tap do |response| expect_response_code!(response, expected_code) end end def create_cypher_response(response) first_result = response.body[:results][0] CypherResponse.new(response, true).tap do |cypher_response| if response.body[:errors].empty? cypher_response.set_data(first_result) else first_error = response.body[:errors].first tx_cleanup!(first_error) cypher_response.set_error(first_error) end end end def tx_cleanup!(first_error) autoclosed! mark_expired if first_error[:message].match(/Unrecognized transaction id/) end def empty_response OpenStruct.new(status: 200, body: '') end end end end