diff --git a/CHANGELOG.md b/CHANGELOG.md index 48e16e2..7ee1127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Changelog ========= +## v1.2.0 09/15/2022 +* Adds a new batch client. See README for details. + ## v1.1.0 07/26/2022 * Adds ability for relationships to function with a primary key attr diff --git a/Gemfile.lock b/Gemfile.lock index ac21d72..6ddfcb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - neo4j-http (1.1.0) + neo4j-http (1.2.0) activesupport (>= 5.2) faraday (< 2) faraday-retry @@ -11,7 +11,7 @@ PATH GEM remote: https://rubygems.org/ specs: - activesupport (7.0.3.1) + activesupport (7.0.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -20,7 +20,7 @@ GEM coderay (1.1.3) concurrent-ruby (1.1.10) diff-lcs (1.5.0) - faraday (1.10.0) + faraday (1.10.2) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -48,7 +48,7 @@ GEM i18n (1.12.0) concurrent-ruby (~> 1.0) method_source (1.0.0) - minitest (5.16.2) + minitest (5.16.3) multipart-post (2.2.3) parallel (1.22.1) parser (3.1.1.0) @@ -92,7 +92,7 @@ GEM standard (1.9.1) rubocop (= 1.26.1) rubocop-performance (= 1.13.3) - tzinfo (2.0.4) + tzinfo (2.0.5) concurrent-ruby (~> 1.0) unicode-display_width (2.1.0) diff --git a/README.md b/README.md index 6ffb6a7..5f8bf21 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,29 @@ cypher_client = Neo4j::Http::CypherClient.new(config) node_client = Neo4j::Http::NodeClient.new(cypher_client) ``` +## Batch operations + +The `Neo4j::Http::Client.in_batch` will yield a batch client. It can be used like: + +```ruby +Neo4j::Http::Client.in_batch do |tx| + [ + tx.upsert_node(node), + tx.upsert_node(node2), + tx.upsert_relationship(relationship: relationship, from: from, to: to) + ] +end +``` + +All of the commands need to chain off of the variable exposed by the block in order to +prepare the operations for the batch. These are not immediately invoked like their +single operation counterparts. The syntax and arguments are identical. + +The array of statements will be passed into a batch client that will +prepare the statements and the parameters and issue a single +request to the Neo4j HTTP API. Note that the size of the batch is +determined by the caller's array length. + ## Versioning This project follows [semantic versioning](https://semver.org). diff --git a/lib/neo4j/http.rb b/lib/neo4j/http.rb index 0c41027..9c44bf8 100644 --- a/lib/neo4j/http.rb +++ b/lib/neo4j/http.rb @@ -8,13 +8,17 @@ require "neo4j/http/auth_token" require "neo4j/http/client" +require "neo4j/http/batch_client" require "neo4j/http/configuration" require "neo4j/http/cypher_client" +require "neo4j/http/batch_cypher_client" require "neo4j/http/object_wrapper" require "neo4j/http/node" require "neo4j/http/node_client" +require "neo4j/http/batch_node_client" require "neo4j/http/relationship" require "neo4j/http/relationship_client" +require "neo4j/http/batch_relationship_client" require "neo4j/http/results" require "neo4j/http/errors" @@ -24,7 +28,7 @@ module Http extend self def config - @congiguration ||= Configuration.new + @configuration ||= Configuration.new end def configure diff --git a/lib/neo4j/http/batch_client.rb b/lib/neo4j/http/batch_client.rb new file mode 100644 index 0000000..800e550 --- /dev/null +++ b/lib/neo4j/http/batch_client.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Neo4j + module Http + class BatchClient < Client + class << self + def default + cypher_client = Http::BatchCypherClient.new(Neo4j::Http.config) + node_client = Http::BatchNodeClient.new(cypher_client) + relationship_client = Http::BatchRelationshipClient.new(cypher_client) + @default ||= new(cypher_client, node_client, relationship_client) + end + end + + attr_accessor :cypher_client, :node_client, :relationship_client + + def initialize(cypher_client, node_client, relationship_client) + @cypher_client = cypher_client + @node_client = node_client + @relationship_client = relationship_client + end + + delegate(*CYPHER_CLIENT_METHODS, to: :cypher_client) + delegate(*NODE_CLIENT_METHODS, to: :node_client) + delegate(*RELATIONSHIP_CLIENT_METHODS, to: :relationship_client) + end + end +end diff --git a/lib/neo4j/http/batch_cypher_client.rb b/lib/neo4j/http/batch_cypher_client.rb new file mode 100644 index 0000000..2e57112 --- /dev/null +++ b/lib/neo4j/http/batch_cypher_client.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "forwardable" +require "faraday" +require "faraday/retry" +require "faraday_middleware" + +module Neo4j + module Http + class BatchCypherClient < CypherClient + # Each statement should be Hash with statement and parameters keys e.g. + # { + # statement: "MATCH (n:User { name: $name }) RETURN n", + # parameters: { name: "Ben" } + # } + # https://neo4j.com/docs/http-api/current/actions/execute-multiple-statements/ + def execute_cypher(statements = []) + statements = [statements] if statements.is_a?(Hash) # equivalent to Array.wrap + + request_body = { + statements: statements.map do |statement| + { + statement: statement[:statement], + parameters: statement[:parameters].as_json + } + end + } + + @connection = @injected_connection || connection("WRITE") + response = @connection.post(transaction_path, request_body) + results = check_errors!(statements, response) + + results.map do |result| + Neo4j::Http::Results.parse(result || {}) + end + end + end + end +end diff --git a/lib/neo4j/http/batch_node_client.rb b/lib/neo4j/http/batch_node_client.rb new file mode 100644 index 0000000..ec7da8a --- /dev/null +++ b/lib/neo4j/http/batch_node_client.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Neo4j + module Http + class BatchNodeClient < NodeClient + protected + + def process_upsert_node(cypher:, node:) + { + statement: cypher, + parameters: {key_value: node.key_value, attributes: node.attributes} + } + end + + def process_delete_node(cypher:, node:) + { + statement: cypher, + parameters: {key_value: node.key_value} + } + end + end + end +end diff --git a/lib/neo4j/http/batch_relationship_client.rb b/lib/neo4j/http/batch_relationship_client.rb new file mode 100644 index 0000000..80e4463 --- /dev/null +++ b/lib/neo4j/http/batch_relationship_client.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Neo4j + module Http + class BatchRelationshipClient < RelationshipClient + protected + + def process_upsert_relationship(cypher:, from:, to:, relationship:) + { + statement: cypher, + parameters: { + from: from, + to: to, + relationship: relationship, + relationship_attributes: relationship.attributes + } + } + end + + def process_delete_relationship(cypher:, from:, to:) + { + statement: cypher, + parameters: { + from: from, + to: to + } + } + end + + def process_delete_relationship_on_primary_key(cypher:, relationship:) + { + statement: cypher, + parameters: { + relationship: relationship + } + } + end + end + end +end diff --git a/lib/neo4j/http/client.rb b/lib/neo4j/http/client.rb index 188efc7..0fb4726 100644 --- a/lib/neo4j/http/client.rb +++ b/lib/neo4j/http/client.rb @@ -17,6 +17,11 @@ def default relationship_client = Http::RelationshipClient.new(cypher_client) @default ||= new(cypher_client, node_client, relationship_client) end + + def in_batch &block + batch_client = Neo4j::Http::BatchClient + batch_client.execute_cypher yield(batch_client) + end end attr_accessor :cypher_client, :node_client, :relationship_client diff --git a/lib/neo4j/http/cypher_client.rb b/lib/neo4j/http/cypher_client.rb index cad3935..62f0f8b 100644 --- a/lib/neo4j/http/cypher_client.rb +++ b/lib/neo4j/http/cypher_client.rb @@ -47,7 +47,7 @@ def connection(access_mode) protected delegate :auth_token, :transaction_path, to: :@configuration - def check_errors!(cypher, response, parameters) + def check_errors!(cypher, response, parameters = {}) raise Neo4j::Http::Errors::InvalidConnectionUrl, response.status if response.status == 404 if response.body["errors"].any? { |error| error["message"][/Routing WRITE queries is not supported/] } raise Neo4j::Http::Errors::ReadOnlyError diff --git a/lib/neo4j/http/node_client.rb b/lib/neo4j/http/node_client.rb index 33003bd..ca58a61 100644 --- a/lib/neo4j/http/node_client.rb +++ b/lib/neo4j/http/node_client.rb @@ -21,9 +21,7 @@ def upsert_node(node) return node CYPHER - results = @cypher_client.execute_cypher(cypher, key_value: node.key_value, attributes: node.attributes) - - results.first&.fetch("node") + process_upsert_node(cypher: cypher, node: node) end def delete_node(node) @@ -34,8 +32,7 @@ def delete_node(node) RETURN node CYPHER - results = @cypher_client.execute_cypher(cypher, key_value: node.key_value) - results.first&.fetch("node") + process_delete_node(cypher: cypher, node: node) end def find_node_by(label:, **attributes) @@ -56,6 +53,16 @@ def find_nodes_by(label:, attributes:, limit: 100) protected + def process_upsert_node(cypher:, node:) + results = @cypher_client.execute_cypher(cypher, key_value: node.key_value, attributes: node.attributes) + results.first&.fetch("node") + end + + def process_delete_node(cypher:, node:) + results = @cypher_client.execute_cypher(cypher, key_value: node.key_value) + results.first&.fetch("node") + end + def build_selectors(attributes, node_name: :node) attributes.map do |key, value| if value.is_a?(Array) diff --git a/lib/neo4j/http/relationship_client.rb b/lib/neo4j/http/relationship_client.rb index 45eb868..3841517 100644 --- a/lib/neo4j/http/relationship_client.rb +++ b/lib/neo4j/http/relationship_client.rb @@ -33,14 +33,7 @@ def upsert_relationship(relationship:, from:, to:, create_nodes: false) RETURN from, to, relationship CYPHER - results = @cypher_client.execute_cypher( - cypher, - from: from, - to: to, - relationship: relationship, - relationship_attributes: relationship.attributes - ) - results&.first + process_upsert_relationship(cypher: cypher, from: from, to: to, relationship: relationship) end def find_relationships(from:, relationship:, to:) @@ -84,8 +77,7 @@ def delete_relationship(relationship:, from:, to:) RETURN from, to CYPHER - results = @cypher_client.execute_cypher(cypher, from: from, to: to) - results&.first + process_delete_relationship(cypher: cypher, from: from, to: to) end def delete_relationship_on_primary_key(relationship:) @@ -101,6 +93,28 @@ def delete_relationship_on_primary_key(relationship:) RETURN relationship CYPHER + process_delete_relationship_on_primary_key(cypher: cypher, relationship: relationship) + end + + protected + + def process_upsert_relationship(cypher:, from:, to:, relationship:) + results = @cypher_client.execute_cypher( + cypher, + from: from, + to: to, + relationship: relationship, + relationship_attributes: relationship.attributes + ) + results&.first + end + + def process_delete_relationship(cypher:, from:, to:) + results = @cypher_client.execute_cypher(cypher, from: from, to: to) + results&.first + end + + def process_delete_relationship_on_primary_key(cypher:, relationship:) results = @cypher_client.execute_cypher(cypher, relationship: relationship) results&.first end diff --git a/lib/neo4j/http/version.rb b/lib/neo4j/http/version.rb index 8dc5026..c3c76f8 100644 --- a/lib/neo4j/http/version.rb +++ b/lib/neo4j/http/version.rb @@ -1,5 +1,5 @@ module Neo4j module Http - VERSION = "1.1.0" + VERSION = "1.2.0" end end diff --git a/spec/neo4j/http/batch_client_spec.rb b/spec/neo4j/http/batch_client_spec.rb new file mode 100644 index 0000000..92dee66 --- /dev/null +++ b/spec/neo4j/http/batch_client_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Neo4j::Http::BatchClient, type: :uses_neo4j do + let(:cypher_client) { double(Neo4j::Http::CypherClient) } + let(:node_client) { double(Neo4j::Http::NodeClient) } + let(:relationship_client) { double(Neo4j::Http::RelationshipClient) } + subject(:client) { described_class.new(cypher_client, node_client, relationship_client) } + + describe "class methods" do + described_class::CLIENT_METHODS.each do |method| + it "delegates the #{method} to the default instance" do + client_double = double(described_class) + allow(described_class).to receive(:default).and_return(client_double) + allow(client_double).to receive(method).and_return("received") + expect(described_class.public_send(method)).to eq("received") + end + end + end + + describe "cypher methods" do + it "delegates to the cypher client" do + allow(cypher_client).to receive(:execute_cypher).with(:args) + client.execute_cypher(:args) + expect(cypher_client).to have_received(:execute_cypher).with(:args) + end + end + + describe "node methods" do + described_class::NODE_CLIENT_METHODS.each do |method| + it "delegates #{method} to the node client" do + allow(node_client).to receive(method).with(:args) + client.public_send(method, :args) + expect(node_client).to have_received(method).with(:args) + end + end + end + + describe "relationship methods" do + described_class::RELATIONSHIP_CLIENT_METHODS.each do |method| + it "delegates #{method} to the relationship client" do + allow(relationship_client).to receive(method).with(:args) + client.public_send(method, :args) + expect(relationship_client).to have_received(method).with(:args) + end + end + end +end diff --git a/spec/neo4j/http/batch_cypher_client_spec.rb b/spec/neo4j/http/batch_cypher_client_spec.rb new file mode 100644 index 0000000..14ff11d --- /dev/null +++ b/spec/neo4j/http/batch_cypher_client_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "spec_helper" +require "faraday" + +RSpec.describe Neo4j::Http::BatchCypherClient, type: :uses_neo4j do + subject(:client) { described_class.default } + + describe "execute_batch_cypher" do + it "executes all statments" do + statement1 = "MERGE (node:Test {uuid: 'Uuid1', name: 'Foo'}) return node" + statement2 = "MERGE (node:Test {uuid: 'Uuid2', name: 'Bar'}) return node" + statement3 = "MERGE (node:Test {uuid: 'Uuid3', name: $name}) return node" + statement4 = "MERGE (node:Test {uuid: 'Uuid4', name: $name}) return node" + + results = client.execute_cypher([ + { + statement: statement1, + parameters: nil + }, + { + statement: statement2, + parameters: {} + }, + { + statement: statement3, + parameters: {name: "Baz"} + }, + { + statement: statement4, + parameters: {name: "Qux"} + } + ]) + + expect(results[0][0]["node"]["name"]).to eq("Foo") + expect(results[1][0]["node"]["name"]).to eq("Bar") + expect(results[2][0]["node"]["name"]).to eq("Baz") + expect(results[3][0]["node"]["name"]).to eq("Qux") + end + + it "handles error and rolls back" do + good_statement = "MERGE (node:Test {uuid: 'Uuid1', name: 'Foo'}) return node" + bad_statement = "MERGE (node:Test {uuid: 'Uuid2', name: 'Bar'}) BAD SYNTAX" + good_statement2 = "MERGE (node:Test {uuid: 'Uuid3', name: 'Baz'}) return node" + + expect { + client.execute_cypher([ + { + statement: good_statement, + parameters: {} + }, + { + statement: bad_statement, + parameters: {} + }, + { + statement: good_statement2, + parameters: {} + } + ]) + }.to raise_error Neo4j::Http::Errors::Neo::ClientError::Statement::SyntaxError + + results = Neo4j::Http::Client.execute_cypher("MATCH (node:Test { uuid: 'Uuid1' }) return node") + expect(results).to be_empty + + results = Neo4j::Http::Client.execute_cypher("MATCH (node:Test { uuid: 'Uuid3' }) return node") + expect(results).to be_empty + end + end +end diff --git a/spec/neo4j/http/batch_node_client_spec.rb b/spec/neo4j/http/batch_node_client_spec.rb new file mode 100644 index 0000000..e8d74af --- /dev/null +++ b/spec/neo4j/http/batch_node_client_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Neo4j::Http::BatchNodeClient, type: :uses_neo4j do + subject(:client) { described_class.default } + let(:cypher_client) { Neo4j::Http::CypherClient.default } + + describe "upsert_node" do + it "creates a node" do + uuid = "MyUuid" + # Inspect the statement payload before executing + node_in = Neo4j::Http::Node.new(label: "Test", uuid: uuid, name: "Foo") + node = client.upsert_node(node_in) + + expect(node[:statement]).to match(/MERGE \(node:Test \{uuid: \$key_value\}\)/) + expect(node[:parameters][:key_value]).to eq("MyUuid") + expect(node[:parameters][:attributes][:name]).to eq("Foo") + + # Insert the node + Neo4j::Http::Client.in_batch do |tx| + tx.upsert_node(node_in) + end + + # Verify the insert + results = cypher_client.execute_cypher("MATCH (node:Test {uuid: $uuid}) RETURN node", uuid: uuid) + expect(results.length).to eq(1) + node = results&.first&.fetch("node", nil) + expect(node).not_to be_nil + expect(node["name"]).to eq("Foo") + end + + it "updates the existing node" do + uuid = "MyUuid" + # Insert the node + node1 = create_node({uuid: uuid, name: "Foo"}) + + expect(node1["uuid"]).to eq(uuid) + expect(node1["name"]).to eq("Foo") + expect(node1["_neo4j_meta_data"]).not_to be_nil + expect(node1["_neo4j_meta_data"]["id"]).not_to be_nil + + # Batch upsert the node + node_in = Neo4j::Http::Node.new(label: "Test", uuid: uuid, name: "Bar") + Neo4j::Http::Client.in_batch do |tx| + tx.upsert_node(node_in) + end + + # Verify the change + results = cypher_client.execute_cypher("MATCH (node:Test {uuid: $uuid}) RETURN node", uuid: uuid) + expect(results.length).to eq(1) + expect(results.first["node"]["name"]).to eq("Bar") + end + end + + describe "delete_node" do + it "deletes a node" do + uuid = "MyUuid" + # Inspect the statement payload before executing + create_node({uuid: uuid, name: "Foo"}) + node_in = Neo4j::Http::Node.new(label: "Test", uuid: uuid) + + # Batch delete the node + Neo4j::Http::Client.in_batch do |tx| + tx.delete_node(node_in) + end + + # Verify deletion + results = cypher_client.execute_cypher("MATCH (node:Test {uuid: $uuid}) RETURN node", uuid: uuid) + expect(results.length).to eq(0) + end + end + + def create_node(attributes = {}) + node = Neo4j::Http::Node.new(label: "Test", **attributes) + Neo4j::Http::Client.upsert_node(node) + end +end diff --git a/spec/neo4j/http/batch_relationship_client_spec.rb b/spec/neo4j/http/batch_relationship_client_spec.rb new file mode 100644 index 0000000..f07f3f6 --- /dev/null +++ b/spec/neo4j/http/batch_relationship_client_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Neo4j::Http::BatchRelationshipClient do + subject(:client) { described_class.default } + before(:each) { clean_neo4j_database } + + let(:from) { Neo4j::Http::Node.new(label: "Bot", uuid: "FromUuid", name: "Foo") } + let(:to) { Neo4j::Http::Node.new(label: "Bot", uuid: "ToUuid", name: "Bar") } + let(:relationship) { Neo4j::Http::Relationship.new(label: "KNOWS") } + + describe "upsert_relationship" do + it "creates a relationship between two nodes" do + create_node(from) + create_node(to) + result = client.upsert_relationship(relationship: relationship, from: from, to: to) + + # Inspect the statement payload before executing + expect(result[:statement]).to match(/MATCH \(from:Bot \{uuid: \$from\.uuid\}\)/) + expect(result[:parameters][:from]).to eq(from) + expect(result[:parameters][:to]).to eq(to) + expect(result[:parameters][:relationship]).to eq(relationship) + expect(result[:parameters][:relationship_attributes]).to eq({}) + + # Batch upsert + Neo4j::Http::Client.in_batch do |tx| + tx.upsert_relationship(relationship: relationship, from: from, to: to) + end + + # Verify insert + result = Neo4j::Http::Client.execute_cypher "MATCH (:Bot)-[r:KNOWS]-(:Bot) RETURN r LIMIT 1" + expect(result.first["r"]).to be_present + end + end + + describe "delete_relationship" do + it "Removes the relationship between the nodes" do + relationship = Neo4j::Http::Relationship.new(label: "KNOWS") + + # Insert the relationship + Neo4j::Http::Client.upsert_relationship(relationship: relationship, from: from, to: to, create_nodes: true) + + # Verify insert + result = Neo4j::Http::Client.execute_cypher "MATCH (:Bot)-[r:KNOWS]-(:Bot) RETURN r LIMIT 1" + expect(result.length).to eq(1) + + # Batch delete the relationship + Neo4j::Http::Client.in_batch do |tx| + tx.delete_relationship(relationship: relationship, from: from, to: to) + end + + # Verify delete + result = Neo4j::Http::Client.execute_cypher "MATCH (:Bot)-[r:KNOWS]-(:Bot) RETURN r LIMIT 1" + expect(result.length).to eq(0) + end + end + + describe "delete_relationship_on_primary_key" do + it "removes the correct relationship" do + relationship1 = Neo4j::Http::Relationship.new(label: "KNOWS", primary_key_name: "how", how: "friend") + relationship2 = Neo4j::Http::Relationship.new(label: "KNOWS", primary_key_name: "how", how: "colleague") + # Insert the relationships + Neo4j::Http::Client.upsert_relationship(relationship: relationship1, from: from, to: to, create_nodes: true) + Neo4j::Http::Client.upsert_relationship(relationship: relationship2, from: from, to: to, create_nodes: true) + + # Verify the inserts + expect(client.find_relationships(relationship: relationship, from: from, to: to).count).to eq(2) + + # Inspect the statement payload before executing + result = client.delete_relationship_on_primary_key(relationship: relationship2) + expect(result[:statement]).to match(/MATCH \(\) - \[relationship:KNOWS \{how: \$relationship\.how\}\] - \(\)/) + expect(result[:parameters][:relationship]).to eq(relationship2) + + # Batch delete the relationship + Neo4j::Http::Client.in_batch do |tx| + tx.delete_relationship_on_primary_key(relationship: relationship2) + end + + # Verify the delete + result = Neo4j::Http::Client.execute_cypher "MATCH (:Bot)-[r:KNOWS { how: 'friend'}]-(:Bot) RETURN r LIMIT 1" + expect(result.length).to eq(1) + + result = Neo4j::Http::Client.execute_cypher "MATCH (:Bot)-[r:KNOWS { how: 'colleague'}]-(:Bot) RETURN r LIMIT 1" + expect(result.length).to eq(0) + end + end + + def create_node(node) + Neo4j::Http::NodeClient.default.upsert_node(node) + end + + def create_relationship(from, relationship, to) + Neo4j::Http::RelationshipClient.default.upsert_relationship(from: from, relationship: relationship, to: to) + end +end diff --git a/spec/neo4j/http/client_spec.rb b/spec/neo4j/http/client_spec.rb index 5201cd5..0054fec 100644 --- a/spec/neo4j/http/client_spec.rb +++ b/spec/neo4j/http/client_spec.rb @@ -10,7 +10,7 @@ describe "class methods" do it "has the expected set of methods" do - expected_methods = described_class::CLIENT_METHODS + [:default] + expected_methods = described_class::CLIENT_METHODS + [:default, :in_batch] expect(described_class.methods(false)).to match_array(expected_methods) end @@ -51,4 +51,37 @@ end end end + + describe "in_batch" do + it "runs single statement in batch client" do + described_class.in_batch do |tx| + tx.upsert_node(Neo4j::Http::Node.new(label: "Foo", uuid: "0abbf43f-6fbd-4176-8db4-a3c7fdd8bb17")) + end + + result = described_class.execute_cypher("MATCH (n:Foo { uuid: '0abbf43f-6fbd-4176-8db4-a3c7fdd8bb17'}) RETURN n LIMIT 1") + expect(result.first["n"]["uuid"]).to eq("0abbf43f-6fbd-4176-8db4-a3c7fdd8bb17") + end + + it "runs statements in batch client" do + described_class.in_batch do |tx| + [ + tx.upsert_node(Neo4j::Http::Node.new(label: "Foo", uuid: "0abbf43f-6fbd-4176-8db4-a3c7fdd8bb17")), + tx.upsert_node(Neo4j::Http::Node.new(label: "Bar", uuid: "05d30cd1-020f-4693-8487-28018862690e")) + ] + end + + result = described_class.execute_cypher("MATCH (n:Foo { uuid: '0abbf43f-6fbd-4176-8db4-a3c7fdd8bb17'}) RETURN n LIMIT 1") + expect(result.first["n"]["uuid"]).to eq("0abbf43f-6fbd-4176-8db4-a3c7fdd8bb17") + + result = described_class.execute_cypher("MATCH (n:Bar { uuid: '05d30cd1-020f-4693-8487-28018862690e'}) RETURN n LIMIT 1") + expect(result.first["n"]["uuid"]).to eq("05d30cd1-020f-4693-8487-28018862690e") + end + + it "yields batch client" do + described_class.in_batch do |tx| + expect(tx).to eq(::Neo4j::Http::BatchClient) + [] # this is a no op - just want to check the yielded object + end + end + end end diff --git a/spec/neo4j/http/node_client_spec.rb b/spec/neo4j/http/node_client_spec.rb index 5fbacf2..52e2daa 100644 --- a/spec/neo4j/http/node_client_spec.rb +++ b/spec/neo4j/http/node_client_spec.rb @@ -45,6 +45,19 @@ end end + describe "delete_node" do + it "deletes the existing node" do + uuid = "MyUuid" + node_in = Neo4j::Http::Node.new(label: "Test", uuid: uuid, name: "Foo") + client.upsert_node(node_in) + + client.delete_node(node_in) + + results = cypher_client.execute_cypher("MATCH (node:Test {uuid: $uuid}) RETURN node", uuid: uuid) + expect(results.length).to eq(0) + end + end + describe "find_node_by" do it "finds a node by the attributes given" do create_node(uuid: "Uuid2", name: "Bar") diff --git a/vendor/cache/activesupport-7.0.3.1.gem b/vendor/cache/activesupport-7.0.3.1.gem deleted file mode 100644 index 0c3757a..0000000 Binary files a/vendor/cache/activesupport-7.0.3.1.gem and /dev/null differ diff --git a/vendor/cache/activesupport-7.0.4.gem b/vendor/cache/activesupport-7.0.4.gem new file mode 100644 index 0000000..3c823a0 Binary files /dev/null and b/vendor/cache/activesupport-7.0.4.gem differ diff --git a/vendor/cache/faraday-1.10.0.gem b/vendor/cache/faraday-1.10.0.gem deleted file mode 100644 index 6b9e00c..0000000 Binary files a/vendor/cache/faraday-1.10.0.gem and /dev/null differ diff --git a/vendor/cache/faraday-1.10.2.gem b/vendor/cache/faraday-1.10.2.gem new file mode 100644 index 0000000..712f0cb Binary files /dev/null and b/vendor/cache/faraday-1.10.2.gem differ diff --git a/vendor/cache/minitest-5.16.2.gem b/vendor/cache/minitest-5.16.2.gem deleted file mode 100644 index 78b6273..0000000 Binary files a/vendor/cache/minitest-5.16.2.gem and /dev/null differ diff --git a/vendor/cache/minitest-5.16.3.gem b/vendor/cache/minitest-5.16.3.gem new file mode 100644 index 0000000..ebdb92e Binary files /dev/null and b/vendor/cache/minitest-5.16.3.gem differ diff --git a/vendor/cache/tzinfo-2.0.4.gem b/vendor/cache/tzinfo-2.0.4.gem deleted file mode 100644 index a3f9820..0000000 Binary files a/vendor/cache/tzinfo-2.0.4.gem and /dev/null differ diff --git a/vendor/cache/tzinfo-2.0.5.gem b/vendor/cache/tzinfo-2.0.5.gem new file mode 100644 index 0000000..1b28f07 Binary files /dev/null and b/vendor/cache/tzinfo-2.0.5.gem differ