diff --git a/lib/neography/rest.rb b/lib/neography/rest.rb index d33235a..b1bd302 100644 --- a/lib/neography/rest.rb +++ b/lib/neography/rest.rb @@ -27,6 +27,7 @@ require 'neography/rest/extensions' require 'neography/rest/batch' require 'neography/rest/clean' +require 'neography/rest/transactions' require 'neography/errors' @@ -67,6 +68,7 @@ def initialize(options = ENV['NEO4J_URL'] || {}) @extensions = Extensions.new(@connection) @batch = Batch.new(@connection) @clean = Clean.new(@connection) + @transactions = Transactions.new(@connection) end # meta-data @@ -118,6 +120,32 @@ def create_schema_index(label, properties) def delete_schema_index(label, property) @schema_indexes.drop(label, property) end + + # transactions + + def begin_transaction(statements=nil) + @transactions.begin(statements) + end + + def in_transaction(tx, statements) + @transactions.add(tx, statements) + end + + def keep_transaction(tx) + @transactions.add(tx) + end + + def commit_transaction(tx, statements=[]) + if tx.is_a?(Array) + @transactions.begin(tx, "commit") + else + @transactions.commit(tx, statements) + end + end + + def rollback_transaction(tx) + @transactions.add(tx) + end # nodes diff --git a/lib/neography/rest/transactions.rb b/lib/neography/rest/transactions.rb index 7c4a67d..b1f5791 100644 --- a/lib/neography/rest/transactions.rb +++ b/lib/neography/rest/transactions.rb @@ -4,99 +4,81 @@ class Transactions extend Neography::Rest::Paths include Neography::Rest::Helpers - add_path :index, "/node" - add_path :base, "/node/:id" - + add_path :base, "/transaction" + add_path :tx, "/transaction/:id" + add_path :commit, "/transaction/:id/commit" + def initialize(connection) @connection = connection end - def get(id) - @connection.get(base_path(:id => get_id(id))) - end - - def get_each(*nodes) - gotten_nodes = [] - Array(nodes).flatten.each do |node| - gotten_nodes << get(node) - end - gotten_nodes - end - - def root - root_node = @connection.get('/')["reference_node"] - @connection.get(base_path(:id => get_id(root_node))) + def begin(statements = [], commit = "") + options = { + :body => ( + convert_cypher(statements) + ).to_json, + :headers => json_content_type + } + @connection.post(base_path + commit, options) end - def create(*args) - if args[0].respond_to?(:each_pair) && args[0] - create_with_attributes(args[0]) - else - create_empty - end + def add(tx, statements = []) + options = { + :body => ( + convert_cypher(statements) + ).to_json, + :headers => json_content_type + } + @connection.post(tx_path(:id => get_id(tx)), options) end - def create_with_attributes(attributes) + def commit(tx, statements = []) options = { - :body => attributes.delete_if { |k, v| v.nil? }.to_json, + :body => ( + convert_cypher(statements) + ).to_json, :headers => json_content_type } - @connection.post(index_path, options) + @connection.post(commit_path(:id => get_id(tx)), options) end - - def create_empty - @connection.post(index_path) + + def rollback(tx) + @connection.delete(tx_path(:id => get_id(tx)), options) end - def delete(id) - @connection.delete(base_path(:id => get_id(id))) - end - def create_multiple(nodes) - nodes = Array.new(nodes) if nodes.kind_of? Fixnum - created_nodes = [] - nodes.each do |node| - created_nodes << create(node) - end - created_nodes + private + + def get_id(tx) + return tx if tx.is_a?(Integer) + return tx.split("/")[-2] if tx.is_a?(String) + return tx["commit"].split("/")[-2] if tx["commit"] + raise NeographyError.new("Could not determine transaction id", nil, tx) end - - def create_multiple_threaded(nodes) - nodes = Array.new(nodes) if nodes.kind_of? Fixnum - - node_queue = Queue.new - thread_pool = [] - responses = Queue.new - - nodes.each do |node| - node_queue.push node - end - - [nodes.size, @connection.max_threads].min.times do - thread_pool << Thread.new do - until node_queue.empty? do - node = node_queue.pop - if node.respond_to?(:each_pair) - responses.push( @connection.post(index_path, { - :body => node.to_json, - :headers => json_content_type - } ) ) - else - responses.push( @connection.post(index_path) ) - end - end - self.join + + def convert_cypher(statements) + array = [] + query = nil + parameters = nil + Array(statements).each do |statement| + if query & parameters + array << {:statement => query, :parameters => {:props => parameters}} + query = statement + parameters = nil + elsif query & statement.is_a?(String) + array << {:statement => query} + query = statement + parameters = nil + elsif query & statement.is_a?(Hash) + array << {:statement => query, :parameters => {:props => parameters}} + query = nil + parameters = nil end + query = statement end - - created_nodes = [] - - while created_nodes.size < nodes.size - created_nodes << responses.pop - end - created_nodes + array << {:statement => query} if query + { :statements => array } end - end end end diff --git a/spec/integration/rest_plugin_spec.rb b/spec/integration/rest_plugin_spec.rb index 34d6203..3a44a4c 100644 --- a/spec/integration/rest_plugin_spec.rb +++ b/spec/integration/rest_plugin_spec.rb @@ -6,13 +6,13 @@ end describe "execute gremlin script" do - it "can get the root node id", :gremlin => true do + it "can get the root node id", :gremlin => true do root_node = @neo.execute_script("g.v(0)") root_node.should have_key("self") root_node["self"].split('/').last.should == "0" end - it "can get the a node", :gremlin => true do + it "can get the a node", :gremlin => true do new_node = @neo.create_node id = new_node["self"].split('/').last existing_node = @neo.execute_script("g.v(#{id})") @@ -21,7 +21,7 @@ existing_node["self"].split('/').last.should == id end - it "can get the a node with a variable", :gremlin => true do + it "can get the a node with a variable", :gremlin => true do new_node = @neo.create_node id = new_node["self"].split('/').last existing_node = @neo.execute_script("g.v(id)", {:id => id.to_i}) diff --git a/spec/integration/rest_transaction_spec.rb b/spec/integration/rest_transaction_spec.rb new file mode 100644 index 0000000..7197122 --- /dev/null +++ b/spec/integration/rest_transaction_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe Neography::Rest do + before(:each) do + @neo = Neography::Rest.new + end + + describe "start a transaction" do + it "can start a transaction" do + tx = @neo.begin_transaction + tx.should have_key("transaction") + tx["results"].should be_empty + end + + it "can start a transaction with statements" do + tx = @neo.begin_transaction("start n=node(0) return n") + tx.should have_key("transaction") + tx.should have_key("results") + tx["results"].should_not be_empty + end + end + + describe "keep a transaction" do + it "can keep a transaction" do + tx = @neo.begin_transaction + tx.should have_key("transaction") + tx["results"].should be_empty + sleep(1) + existing_tx = @neo.keep_transaction(tx) + existing_tx.should have_key("transaction") + existing_tx["transaction"]["expires"].should > tx["transaction"]["expires"] + end + end + + + describe "add to a transaction" do + it "can add to a transaction" do + tx = @neo.begin_transaction + tx.should have_key("transaction") + tx["results"].should be_empty + existing_tx = @neo.in_transaction(tx, "start n=node(0) return n") + existing_tx.should have_key("transaction") + existing_tx.should have_key("results") + existing_tx["results"].should_not be_empty + end + end + + describe "commit a transaction" do + it "can commit an opened empty transaction" do + tx = @neo.begin_transaction + tx.should have_key("transaction") + tx["results"].should be_empty + existing_tx = @neo.commit_transaction(tx) + existing_tx.should have_key("results") + existing_tx["results"].should be_empty + end + + it "can commit an opened transaction" do + tx = @neo.begin_transaction("start n=node(0) return n") + tx.should have_key("transaction") + tx["results"].should_not be_empty + existing_tx = @neo.commit_transaction(tx) + existing_tx.should_not have_key("transaction") + existing_tx.should have_key("results") + existing_tx["results"].should be_empty + end + + it "can commit an opened transaction with new statements" do + tx = @neo.begin_transaction + tx.should have_key("transaction") + tx["results"].should be_empty + existing_tx = @neo.commit_transaction(tx, "start n=node(0) return n") + existing_tx.should_not have_key("transaction") + existing_tx.should have_key("results") + existing_tx["results"].should_not be_empty + end + end + + describe "rollback a transaction" do + it "can rollback an opened empty transaction" do + tx = @neo.begin_transaction + tx.should have_key("transaction") + tx["results"].should be_empty + existing_tx = @neo.rollback_transaction(tx) + existing_tx.should have_key("results") + existing_tx["results"].should be_empty + end + + it "can rollback an opened transaction" do + tx = @neo.begin_transaction("start n=node(0) return n") + tx.should have_key("transaction") + tx["results"].should_not be_empty + existing_tx = @neo.rollback_transaction(tx) + existing_tx.should have_key("transaction") + existing_tx.should have_key("results") + existing_tx["results"].should be_empty + end + end + +end \ No newline at end of file