redgraph 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0171849182c559f603bf4e09d1cb17eb5a3a4e3d95ddf8e6f1853434c0fc9380'
4
- data.tar.gz: 7bdff0a775ebeffffe6b842b07a990292d351ee477c4aa2ef8ec492806a0b048
3
+ metadata.gz: ba5feef26cde1b47f2da53277c18c3c234ce12f85f315e761c865135b60857b4
4
+ data.tar.gz: 1cb82a481b616ee4347a52347405155da600c176696de4ccb3efea97076b196d
5
5
  SHA512:
6
- metadata.gz: 53de618a85999bd837e1fabb458c204ac7e0f783425cef8f1d0c07e810c33f580c41d9fd8bf124015970618d6c905c8945020c06a382ae2f692384d0df856482
7
- data.tar.gz: 66866ac946e4b7456684e26587bfcec17eec93de0e4c84bc6827ee77f1f712adce2b895fc38138522c0635fe2ade930a5606e3f638eaec8ab10afc71879e612e
6
+ metadata.gz: 60ee0fdcb153873a42acfd40534c76db63dae469e680ed949544f15301f51ed3027a87d2fa7c1583a781c7d8077ec3fec4baf62746e86f5937f56bb9355f6941
7
+ data.tar.gz: cfa1a6bdc454adb49bb6537d7e1b4662de0e5e68f71611045f9d11b3719531f37c883df6f7fd4aa41061bea464d1d78e4f7b559bc19bfa7aa14031d97d8f0bb2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.1.2] - 2021-04-12
2
+
3
+ - Add Graph#relationship_types
4
+ - Add Graph#count_nodes
5
+ - Add Graph#edges
6
+
1
7
  ## [0.1.1] - 2021-04-11
2
8
 
3
9
  - Graph#nodes:
data/Gemfile CHANGED
@@ -9,3 +9,5 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "minitest", "~> 5.0"
11
11
  gem "pry", "~> 0.14.0"
12
+ gem "simplecov", "~> 0.21.2"
13
+
data/Gemfile.lock CHANGED
@@ -1,13 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redgraph (0.1.1)
4
+ redgraph (0.1.2)
5
5
  redis (~> 4)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  coderay (1.1.3)
11
+ docile (1.3.5)
11
12
  method_source (1.0.0)
12
13
  minitest (5.14.4)
13
14
  pry (0.14.0)
@@ -15,6 +16,12 @@ GEM
15
16
  method_source (~> 1.0)
16
17
  rake (13.0.3)
17
18
  redis (4.2.5)
19
+ simplecov (0.21.2)
20
+ docile (~> 1.1)
21
+ simplecov-html (~> 0.11)
22
+ simplecov_json_formatter (~> 0.1)
23
+ simplecov-html (0.12.3)
24
+ simplecov_json_formatter (0.1.2)
18
25
 
19
26
  PLATFORMS
20
27
  x86_64-darwin-20
@@ -24,6 +31,7 @@ DEPENDENCIES
24
31
  pry (~> 0.14.0)
25
32
  rake (~> 13.0)
26
33
  redgraph!
34
+ simplecov (~> 0.21.2)
27
35
 
28
36
  BUNDLED WITH
29
37
  2.2.15
data/README.md CHANGED
@@ -20,6 +20,8 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
+ The gem assumes you have a recent version of [RedisGraph](https://oss.redislabs.com/redisgraph/) up and running.
24
+
23
25
  Basic usage:
24
26
 
25
27
  graph = Redgraph::Graph.new('movies', url: "redis://localhost:6379/1")
@@ -44,10 +46,21 @@ To get all nodes:
44
46
 
45
47
  @graph.nodes
46
48
 
47
- Optional filters:
49
+ Optional filters that can be combined:
48
50
 
49
51
  @graph.nodes(label: 'actor')
50
52
  @graph.nodes(properties: {name: "Al Pacino"})
53
+ @graph.nodes(limit: 10, skip: 20)
54
+
55
+ Counting nodes
56
+
57
+ @graph.count_nodes(label: 'actor')
58
+
59
+ Getting edges:
60
+
61
+ @graph.edges
62
+ @graph.edges(src: actor, dest: film)
63
+ @graph.edges(kind: 'FRIEND_OF', limit: 10, skip: 20)
51
64
 
52
65
  ## Development
53
66
 
@@ -55,7 +68,7 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
55
68
 
56
69
  TEST_REDIS_URL=YOUR-REDIS-URL rake test
57
70
 
58
- to run the tests.
71
+ to run the tests. Test coverage will be enabled if you set the `COVERAGE` environment variable to any value.
59
72
 
60
73
  You can use a `TEST_REDIS_URL` such as `redis://localhost:6379/1`. Make sure you're not overwriting important databases.
61
74
 
@@ -63,8 +76,6 @@ You can also run `bin/console` for an interactive prompt that will allow you to
63
76
 
64
77
  To install this gem onto your local machine, run `bundle exec rake install`.
65
78
 
66
- Run `bin/console` for an interactive prompt.
67
-
68
79
  ## Contributing
69
80
 
70
81
  Bug reports and pull requests are welcome on GitHub at https://github.com/pzac/redgraph.
data/lib/redgraph/edge.rb CHANGED
@@ -4,7 +4,7 @@ module Redgraph
4
4
  class Edge
5
5
  attr_accessor :id, :src, :dest, :type, :properties
6
6
 
7
- def initialize(src:, dest:, type:, properties: {})
7
+ def initialize(src: nil, dest: nil, type: nil, properties: {})
8
8
  @src = src
9
9
  @dest = dest
10
10
  @type = type
@@ -14,5 +14,9 @@ module Redgraph
14
14
  def persisted?
15
15
  !id.nil?
16
16
  end
17
+
18
+ def ==(other)
19
+ super || other.instance_of?(self.class) && !id.nil? && other.id == id
20
+ end
17
21
  end
18
22
  end
@@ -49,6 +49,13 @@ module Redgraph
49
49
  result.resultset.map(&:values).flatten
50
50
  end
51
51
 
52
+ # Returns an array of existing relationship types
53
+ #
54
+ def relationship_types
55
+ result = query("CALL db.relationshipTypes()")
56
+ result.resultset.map(&:values).flatten
57
+ end
58
+
52
59
  # Adds a node. If successul it returns the created object, otherwise false
53
60
  #
54
61
  def add_node(node)
@@ -90,18 +97,25 @@ module Redgraph
90
97
  result = query(cmd)
91
98
 
92
99
  result.resultset.map do |item|
93
- (node_id, labels, props) = item["node"]
94
- attrs = {}
95
-
96
- props.each do |(index, type, value)|
97
- attrs[get_property(index)] = value
98
- end
99
- Node.new(label: get_label(labels.first), properties: attrs).tap do |node|
100
- node.id = node_id
101
- end
100
+ node_from_resultset_item(item["node"])
102
101
  end
103
102
  end
104
103
 
104
+ # Counts nodes. Options:
105
+ #
106
+ # - label: filter by label
107
+ # - properties: filter by properties
108
+ #
109
+ def count_nodes(label: nil, properties: nil)
110
+ _label = ":`#{label}`" if label
111
+ _props = quote_hash(properties) if properties
112
+
113
+ cmd = "MATCH (node#{_label} #{_props}) RETURN COUNT(node)"
114
+ result = query(cmd)
115
+
116
+ result.resultset.first["COUNT(node)"]
117
+ end
118
+
105
119
  # Adds an edge. If successul it returns the created object, otherwise false
106
120
  #
107
121
  def add_edge(edge)
@@ -114,6 +128,45 @@ module Redgraph
114
128
  edge
115
129
  end
116
130
 
131
+ # Finds edges. Options:
132
+ #
133
+ # - type
134
+ # - src
135
+ # - dest
136
+ # - properties
137
+ # - limit
138
+ # - skip
139
+ #
140
+ def edges(type: nil, src: nil, dest: nil, properties: nil, limit: nil, skip: nil)
141
+ _type = ":`#{type}`" if type
142
+ _props = quote_hash(properties) if properties
143
+ _limit = "LIMIT #{limit}" if limit
144
+ _skip = "SKIP #{skip}" if skip
145
+
146
+ _where = if src || dest
147
+ clauses = [
148
+ ("ID(src) = #{src.id}" if src),
149
+ ("ID(dest) = #{dest.id}" if dest)
150
+ ].compact.join(" AND ")
151
+ "WHERE #{clauses}"
152
+ end
153
+
154
+ cmd = "MATCH (src)-[edge#{_type} #{_props}]->(dest) #{_where}
155
+ RETURN src, edge, dest #{_skip} #{_limit}"
156
+ result = query(cmd)
157
+
158
+ result.resultset.map do |item|
159
+ src = node_from_resultset_item(item["src"])
160
+ dest = node_from_resultset_item(item["dest"])
161
+ edge = edge_from_resultset_item(item["edge"])
162
+
163
+ edge.src = src
164
+ edge.dest = dest
165
+
166
+ edge
167
+ end
168
+ end
169
+
117
170
  private
118
171
 
119
172
  def query(cmd)
@@ -130,8 +183,9 @@ module Redgraph
130
183
  def escape_value(x)
131
184
  case x
132
185
  when Integer then x
186
+ when NilClass then "''"
133
187
  else
134
- "'#{x}'"
188
+ '"' + x.gsub('"', '\"') + '"'
135
189
  end
136
190
  end
137
191
 
@@ -144,5 +198,39 @@ module Redgraph
144
198
  @properties ||= properties
145
199
  @properties[id] || (@properties = properties)[id]
146
200
  end
201
+
202
+ def get_relationship_type(id)
203
+ @relationship_types ||= relationship_types
204
+ @relationship_types[id] || (@relationship_types = relationship_types)[id]
205
+ end
206
+
207
+ # Builds a Node object from the raw data
208
+ #
209
+ def node_from_resultset_item(item)
210
+ (node_id, labels, props) = item
211
+ attrs = {}
212
+
213
+ props.each do |(index, type, value)|
214
+ attrs[get_property(index)] = value
215
+ end
216
+ Node.new(label: get_label(labels.first), properties: attrs).tap do |node|
217
+ node.id = node_id
218
+ end
219
+ end
220
+
221
+ def edge_from_resultset_item(item)
222
+ (edge_id, type_id, _src_id, _dest_id, props) = item
223
+ attrs = {}
224
+
225
+ props.each do |(index, type, value)|
226
+ attrs[get_property(index)] = value
227
+ end
228
+
229
+ Edge.new.tap do |edge|
230
+ edge.id = edge_id
231
+ edge.type = get_relationship_type(type_id)
232
+ edge.properties = attrs
233
+ end
234
+ end
147
235
  end
148
236
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Redgraph
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/redgraph.gemspec CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
17
  spec.metadata["source_code_uri"] = spec.homepage
18
- spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
18
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
19
19
 
20
20
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
21
  `git ls-files -z`.split("\x0")
@@ -17,6 +17,35 @@ class GraphManipulationTest < Minitest::Test
17
17
  assert_predicate result, :persisted?
18
18
  end
19
19
 
20
+ def test_add_node_with_special_chars
21
+ [
22
+ "apo'str",
23
+ "two''apos",
24
+ "Foø'bÆ®",
25
+ "aa\nbb",
26
+ 'aaa "bbb" ccc'
27
+ ].each do |name|
28
+
29
+ node = Redgraph::Node.new(label: 'actor', properties: {name: name})
30
+ result = @graph.add_node(node)
31
+ assert_predicate result, :persisted?
32
+
33
+ item = @graph.find_node_by_id(node.id)
34
+
35
+ assert_equal(name, item.properties["name"])
36
+ end
37
+ end
38
+
39
+ def test_add_node_with_nil_value
40
+ node = Redgraph::Node.new(label: 'actor', properties: {name: nil})
41
+ result = @graph.add_node(node)
42
+ assert_predicate result, :persisted?
43
+
44
+ item = @graph.find_node_by_id(node.id)
45
+
46
+ assert_equal("", item.properties["name"])
47
+ end
48
+
20
49
  def test_add_edge
21
50
  actor = Redgraph::Node.new(label: 'actor', properties: {name: "Al Pacino"})
22
51
  @graph.add_node(actor)
@@ -62,6 +62,17 @@ class GraphQueriesTest < Minitest::Test
62
62
  assert_includes(dramas, casino)
63
63
  end
64
64
 
65
+ def test_count_nodes
66
+ quick_add_node(label: 'film', properties: {name: "Scarface", genre: "drama"})
67
+ quick_add_node(label: 'film', properties: {name: "Casino", genre: "drama"})
68
+ quick_add_node(label: 'film', properties: {name: "Mamma Mia", genre: "musical"})
69
+
70
+
71
+ assert_equal(5, @graph.count_nodes)
72
+ assert_equal(3, @graph.count_nodes(label: 'film'))
73
+ assert_equal(2, @graph.count_nodes(properties: {genre: "drama"}))
74
+ end
75
+
65
76
  def test_limit_nodes
66
77
  10.times do |i|
67
78
  quick_add_node(label: 'token', properties: {number: i})
@@ -82,9 +93,73 @@ class GraphQueriesTest < Minitest::Test
82
93
  assert_equal([3,4,5], items.map{|item| item.properties["number"]})
83
94
  end
84
95
 
96
+ def test_find_edge
97
+ quick_add_edge(type: 'FRIEND_OF', src: @al, dest: @john, properties: {since: 1980})
98
+ edge = @graph.edges.first
99
+
100
+ assert_equal('FRIEND_OF', edge.type)
101
+ assert_equal(1980, edge.properties["since"])
102
+ assert_equal(@al, edge.src)
103
+ assert_equal(@john, edge.dest)
104
+
105
+ end
106
+
107
+ def test_find_all_edges
108
+ marlon = quick_add_node(label: 'actor', properties: {name: "Marlon Brando"})
109
+ film = quick_add_node(label: 'film', properties: {name: "The Godfather"})
110
+ quick_add_edge(type: 'ACTOR_IN', src: marlon, dest: film, properties: {role: 'Don Vito'})
111
+ quick_add_edge(type: 'ACTOR_IN', src: @al, dest: film, properties: {role: 'Michael'})
112
+
113
+ edges = @graph.edges
114
+ assert_equal(2, edges.size)
115
+ end
116
+
117
+ def test_filter_edges
118
+ marlon = quick_add_node(label: 'actor', properties: {name: "Marlon Brando"})
119
+ film = quick_add_node(label: 'film', properties: {name: "The Godfather"})
120
+ other_film = quick_add_node(label: 'film', properties: {name: "Carlito's Way"})
121
+ e_donvito = quick_add_edge(type: 'ACTOR_IN', src: marlon, dest: film, properties: {role: 'Don Vito'})
122
+ e_michael = quick_add_edge(type: 'ACTOR_IN', src: @al, dest: film, properties: {role: 'Michael'})
123
+ e_carlito = quick_add_edge(type: 'ACTOR_IN', src: @al, dest: other_film, properties: {role: 'Carlito'})
124
+ quick_add_edge(type: 'FRIEND_OF', src: @al, dest: marlon, properties: {since: 1980})
125
+
126
+ edges = @graph.edges(type: "FRIEND_OF")
127
+ assert_equal(1, edges.size)
128
+
129
+ edges = @graph.edges(type: "ACTOR_IN")
130
+ assert_equal(3, edges.size)
131
+
132
+ edges = @graph.edges(type: "ACTOR_IN", limit: 2)
133
+ assert_equal(2, edges.size)
134
+
135
+ edges = @graph.edges(type: "ACTOR_IN", skip: 2, limit: 10)
136
+ assert_equal(1, edges.size)
137
+
138
+ edges = @graph.edges(properties: {role: "Carlito"})
139
+ assert_equal([e_carlito], edges)
140
+
141
+ edges = @graph.edges(src: marlon)
142
+ assert_equal([e_donvito], edges)
143
+
144
+ edges = @graph.edges(type: 'ACTOR_IN', dest: film)
145
+ assert_equal(2, edges.size)
146
+ assert_includes(edges, e_donvito)
147
+ assert_includes(edges, e_michael)
148
+
149
+ edges = @graph.edges(src: @al, dest: marlon)
150
+ assert_equal(1, edges.size)
151
+ edge = edges[0]
152
+ assert_equal('FRIEND_OF', edge.type)
153
+ assert_equal(1980, edge.properties["since"])
154
+ end
155
+
85
156
  private
86
157
 
87
158
  def quick_add_node(label:, properties:)
88
159
  @graph.add_node(Redgraph::Node.new(label: label, properties: properties))
89
160
  end
161
+
162
+ def quick_add_edge(type:, src:, dest:, properties:)
163
+ @graph.add_edge(Redgraph::Edge.new(type: type, src: src, dest: dest, properties: properties))
164
+ end
90
165
  end
data/test/graph_test.rb CHANGED
@@ -42,6 +42,19 @@ class GraphTest < Minitest::Test
42
42
  assert_equal(["name", "age"], @graph.properties)
43
43
  end
44
44
 
45
+ def test_relationship_types
46
+ @graph = create_sample_graph("foobar")
47
+
48
+ actor = Redgraph::Node.new(label: "actor", properties: {"name": "Harrison Ford"})
49
+ @graph.add_node(actor)
50
+ film = Redgraph::Node.new(label: "film", properties: {"name": "Star Wars"})
51
+ @graph.add_node(film)
52
+ edge = Redgraph::Edge.new(type: "ACTED_IN", src: actor, dest: film)
53
+ @graph.add_edge(edge)
54
+
55
+ assert_equal(["ACTED_IN"], @graph.relationship_types)
56
+ end
57
+
45
58
  private
46
59
 
47
60
  def create_sample_graph(name)
data/test/test_helper.rb CHANGED
@@ -1,5 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if ENV['COVERAGE']
4
+ require 'simplecov'
5
+
6
+ SimpleCov.start do
7
+ enable_coverage :branch
8
+ add_filter %r{^/test/}
9
+ end
10
+ end
11
+
3
12
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
13
  require "redgraph"
5
14
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redgraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paolo Zaccagnini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-11 00:00:00.000000000 Z
11
+ date: 2021-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -60,7 +60,7 @@ licenses:
60
60
  metadata:
61
61
  homepage_uri: https://github.com/pzac/redgraph
62
62
  source_code_uri: https://github.com/pzac/redgraph
63
- changelog_uri: https://github.com/pzac/redgraph/CHANGELOG.md
63
+ changelog_uri: https://github.com/pzac/redgraph/blob/master/CHANGELOG.md
64
64
  post_install_message:
65
65
  rdoc_options: []
66
66
  require_paths: