redgraph 0.1.2 → 0.1.3

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: ba5feef26cde1b47f2da53277c18c3c234ce12f85f315e761c865135b60857b4
4
- data.tar.gz: 1cb82a481b616ee4347a52347405155da600c176696de4ccb3efea97076b196d
3
+ metadata.gz: 1a432a1b2f9b30601701af0c1464c3c93c14c94da044bad69e531bd3a9f6b26d
4
+ data.tar.gz: 42f9ce782a67034c5d64c28c02e44d31272945073a1a868c521d7f4213346fc1
5
5
  SHA512:
6
- metadata.gz: 60ee0fdcb153873a42acfd40534c76db63dae469e680ed949544f15301f51ed3027a87d2fa7c1583a781c7d8077ec3fec4baf62746e86f5937f56bb9355f6941
7
- data.tar.gz: cfa1a6bdc454adb49bb6537d7e1b4662de0e5e68f71611045f9d11b3719531f37c883df6f7fd4aa41061bea464d1d78e4f7b559bc19bfa7aa14031d97d8f0bb2
6
+ metadata.gz: fc2c9b7a5d6653fcc997807c994ec55f9be3f514f510c20d9b4569e9d14faedf112462c68e7127e5c7310b7b444d84dfe14b2b8d0332f5a884d3d7c096d5e85e
7
+ data.tar.gz: 7e031759d2edc7d7577b8df4db41ae055a35f5f46fdb434cf11b36aa7874e3f297c5512164c5e739a56f3ce5eba5200f0a18af9b9eeb0a8feeda09f61e8e73f3
@@ -5,6 +5,12 @@ on: [push,pull_request]
5
5
  jobs:
6
6
  build:
7
7
  runs-on: ubuntu-latest
8
+
9
+ services:
10
+ redis:
11
+ image: redislabs/redisgraph
12
+ ports: ["6379:6379"]
13
+
8
14
  steps:
9
15
  - uses: actions/checkout@v2
10
16
  - name: Set up Ruby
@@ -12,6 +18,8 @@ jobs:
12
18
  with:
13
19
  ruby-version: 3.0.0
14
20
  - name: Run the default task
21
+ env:
22
+ TEST_REDIS_URL: redis://localhost:6379/0
15
23
  run: |
16
24
  gem install bundler -v 2.2.15
17
25
  bundle install
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [0.1.3] - 2021-04-13
2
+
3
+ - allow custom queries
4
+ - nodes and edges query now allow the `order` option
5
+
1
6
  ## [0.1.2] - 2021-04-12
2
7
 
3
8
  - Add Graph#relationship_types
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redgraph (0.1.2)
4
+ redgraph (0.1.3)
5
5
  redis (~> 4)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -25,26 +25,35 @@ The gem assumes you have a recent version of [RedisGraph](https://oss.redislabs.
25
25
  Basic usage:
26
26
 
27
27
  graph = Redgraph::Graph.new('movies', url: "redis://localhost:6379/1")
28
+ => #<Redgraph::Graph:0x00007f8d5c2b7e38 @connection=#<Redis client v4.2.5 for redis://localhost:6379/1>, @graph_name="movies", @module_version=999999>
28
29
 
29
30
  Create a couple nodes:
30
31
 
31
- actor = Redgraph::Node.new(label: 'actor', attributes: {name: "Al Pacino"})
32
+ actor = Redgraph::Node.new(label: 'actor', properties: {name: "Al Pacino"})
33
+ => #<Redgraph::Node:0x00007f8d5f95cf88 @label="actor", @properties={:name=>"Al Pacino"}>
32
34
  graph.add_node(actor)
33
- film = Redgraph::Node.new(label: 'film', attributes: {name: "Scarface"})
35
+ => #<Redgraph::Node:0x00007f8d5f95cf88 @id=0, @label="actor", @properties={:name=>"Al Pacino"}>
36
+ film = Redgraph::Node.new(label: 'film', properties: {name: "Scarface"})
37
+ => #<Redgraph::Node:0x00007f8d5f85ccc8 @label="film", @properties={:name=>"Scarface"}>
34
38
  graph.add_node(film)
39
+ => #<Redgraph::Node:0x00007f8d5f85ccc8 @id=1, @label="film", @properties={:name=>"Scarface"}>
35
40
 
36
41
  Create an edge between those nodes:
37
42
 
38
43
  edge = Redgraph::Edge.new(src: actor, dest: film, type: 'ACTOR_IN', properties: {role: "Tony Montana"})
39
- result = @graph.add_edge(edge)
44
+ => #<Redgraph::Edge:0x00007f8d5f9ae3d8 @dest=#<Redgraph::Node:0x00007f8d5f85ccc8 @id=1, @label="film", @properties={:name=>"Scarface"}>, @dest_id=1, @properties={:role=>"Tony Montana"}, @src=#<Redgraph::Node:0x00007f8d5f95cf88 @id=0, @label="actor", @properties={:name=>"Al Pacino"}>, @src_id=0, @type="ACTOR_IN">
45
+ @graph.add_edge(edge)
46
+ => #<Redgraph::Edge:0x00007f8d5f9ae3d8 @dest=#<Redgraph::Node:0x00007f8d5f85ccc8 @id=1, @label="film", @properties={:name=>"Scarface"}>, @dest_id=1, @id=0, @properties={:role=>"Tony Montana"}, @src=#<Redgraph::Node:0x00007f8d5f95cf88 @id=0, @label="actor", @properties={:name=>"Al Pacino"}>, @src_id=0, @type="ACTOR_IN">
40
47
 
41
48
  Find a node by id:
42
49
 
43
- @graph.find_node_by_id(123)
50
+ @graph.find_node_by_id(1)
51
+ => #<Redgraph::Node:0x00007f8d5c2c6e88 @id=1, @label="film", @properties={"name"=>"Scarface"}>
44
52
 
45
53
  To get all nodes:
46
54
 
47
55
  @graph.nodes
56
+ => [#<Redgraph::Node:0x00007f8d5c2ee0a0 @id=0, @label="actor", @properties={"name"=>"Al Pacino"}>, #<Redgraph::Node:0x00007f8d5c2edfd8 @id=1, @label="film", @properties={"name"=>"Scarface"}>]
48
57
 
49
58
  Optional filters that can be combined:
50
59
 
@@ -55,6 +64,7 @@ Optional filters that can be combined:
55
64
  Counting nodes
56
65
 
57
66
  @graph.count_nodes(label: 'actor')
67
+ => 1
58
68
 
59
69
  Getting edges:
60
70
 
@@ -62,6 +72,11 @@ Getting edges:
62
72
  @graph.edges(src: actor, dest: film)
63
73
  @graph.edges(kind: 'FRIEND_OF', limit: 10, skip: 20)
64
74
 
75
+ Running custom queries
76
+
77
+ @graph.query("MATCH (src)-[edge:FRIEND_OF]->(dest) RETURN src, edge")
78
+
79
+
65
80
  ## Development
66
81
 
67
82
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
data/lib/redgraph.rb CHANGED
@@ -10,4 +10,9 @@ require_relative "redgraph/query_response"
10
10
  module Redgraph
11
11
  class Error < StandardError; end
12
12
  class ServerError < Error; end
13
+ class MissingAliasPrefixError < Error
14
+ def message
15
+ "The order clause requires the node/edge alias prefix, ie order('node.foo') instead order('foo')"
16
+ end
17
+ end
13
18
  end
data/lib/redgraph/edge.rb CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  module Redgraph
4
4
  class Edge
5
- attr_accessor :id, :src, :dest, :type, :properties
5
+ attr_accessor :id, :src, :dest, :src_id, :dest_id, :type, :properties
6
6
 
7
7
  def initialize(src: nil, dest: nil, type: nil, properties: {})
8
8
  @src = src
9
+ @src_id = @src.id if @src
9
10
  @dest = dest
11
+ @dest_id = @dest.id if @dest
10
12
  @type = type
11
13
  @properties = properties
12
14
  end
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "graph/node_methods"
4
+ require_relative "graph/edge_methods"
5
+
3
6
  module Redgraph
4
7
  class Graph
8
+ include NodeMethods
9
+ include EdgeMethods
10
+
5
11
  attr_accessor :connection, :graph_name
6
12
 
7
13
  def initialize(graph, redis_options = {})
@@ -38,140 +44,49 @@ module Redgraph
38
44
  # Returns an array of existing labels
39
45
  #
40
46
  def labels
41
- result = query("CALL db.labels()")
47
+ result = _query("CALL db.labels()")
42
48
  result.resultset.map(&:values).flatten
43
49
  end
44
50
 
45
51
  # Returns an array of existing properties
46
52
  #
47
53
  def properties
48
- result = query("CALL db.propertyKeys()")
54
+ result = _query("CALL db.propertyKeys()")
49
55
  result.resultset.map(&:values).flatten
50
56
  end
51
57
 
52
58
  # Returns an array of existing relationship types
53
59
  #
54
60
  def relationship_types
55
- result = query("CALL db.relationshipTypes()")
61
+ result = _query("CALL db.relationshipTypes()")
56
62
  result.resultset.map(&:values).flatten
57
63
  end
58
64
 
59
- # Adds a node. If successul it returns the created object, otherwise false
60
- #
61
- def add_node(node)
62
- result = query("CREATE (n:`#{node.label}` #{quote_hash(node.properties)}) RETURN ID(n)")
63
- return false if result.stats[:nodes_created] != 1
64
- id = result.resultset.first["ID(n)"]
65
- node.id = id
66
- node
67
- end
68
-
69
- def find_node_by_id(id)
70
- result = query("MATCH (node) WHERE ID(node) = #{id} RETURN node")
71
- return nil if result.resultset.empty?
72
- (node_id, labels, properties) = result.resultset.first["node"]
73
- attrs = {}
74
-
75
- properties.each do |(index, type, value)|
76
- attrs[get_property(index)] = value
77
- end
78
- Node.new(label: get_label(labels.first), properties: attrs).tap do |node|
79
- node.id = node_id
80
- end
81
- end
82
-
83
- # Returns nodes. Options:
84
- #
85
- # - label: filter by label
86
- # - properties: filter by properties
87
- # - limit: number of items
88
- # - skip: items offset (useful for pagination)
89
- #
90
- def nodes(label: nil, properties: nil, limit: nil, skip: nil)
91
- _label = ":`#{label}`" if label
92
- _props = quote_hash(properties) if properties
93
- _limit = "LIMIT #{limit}" if limit
94
- _skip = "SKIP #{skip}" if skip
95
-
96
- cmd = "MATCH (node#{_label} #{_props}) RETURN node #{_skip} #{_limit}"
97
- result = query(cmd)
98
-
99
- result.resultset.map do |item|
100
- node_from_resultset_item(item["node"])
101
- end
65
+ # You can run custom cypher queries
66
+ def query(cmd)
67
+ _query(cmd).rows
102
68
  end
103
69
 
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)"]
70
+ def get_label(id)
71
+ @labels ||= labels
72
+ @labels[id] || (@labels = labels)[id]
117
73
  end
118
74
 
119
- # Adds an edge. If successul it returns the created object, otherwise false
120
- #
121
- def add_edge(edge)
122
- result = query("MATCH (src), (dest)
123
- WHERE ID(src) = #{edge.src.id} AND ID(dest) = #{edge.dest.id}
124
- CREATE (src)-[e:`#{edge.type}` #{quote_hash(edge.properties)}]->(dest) RETURN ID(e)")
125
- return false if result.stats[:relationships_created] != 1
126
- id = result.resultset.first["ID(e)"]
127
- edge.id = id
128
- edge
75
+ def get_property(id)
76
+ @properties ||= properties
77
+ @properties[id] || (@properties = properties)[id]
129
78
  end
130
79
 
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
80
+ def get_relationship_type(id)
81
+ @relationship_types ||= relationship_types
82
+ @relationship_types[id] || (@relationship_types = relationship_types)[id]
168
83
  end
169
84
 
170
85
  private
171
86
 
172
- def query(cmd)
87
+ def _query(cmd)
173
88
  data = @connection.call("GRAPH.QUERY", graph_name, cmd, "--compact")
174
- QueryResponse.new(data)
89
+ QueryResponse.new(data, self)
175
90
  end
176
91
 
177
92
  def quote_hash(hash)
@@ -189,48 +104,5 @@ module Redgraph
189
104
  end
190
105
  end
191
106
 
192
- def get_label(id)
193
- @labels ||= labels
194
- @labels[id] || (@labels = labels)[id]
195
- end
196
-
197
- def get_property(id)
198
- @properties ||= properties
199
- @properties[id] || (@properties = properties)[id]
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
235
107
  end
236
108
  end
@@ -0,0 +1,78 @@
1
+ module Redgraph
2
+ class Graph
3
+ module EdgeMethods
4
+ # Adds an edge. If successul it returns the created object, otherwise false
5
+ #
6
+ def add_edge(edge)
7
+ result = _query("MATCH (src), (dest)
8
+ WHERE ID(src) = #{edge.src.id} AND ID(dest) = #{edge.dest.id}
9
+ CREATE (src)-[e:`#{edge.type}` #{quote_hash(edge.properties)}]->(dest) RETURN ID(e)")
10
+ return false if result.stats[:relationships_created] != 1
11
+ id = result.resultset.first["ID(e)"]
12
+ edge.id = id
13
+ edge
14
+ end
15
+
16
+ # Finds edges. Options:
17
+ #
18
+ # - type
19
+ # - src
20
+ # - dest
21
+ # - properties
22
+ # - order
23
+ # - limit
24
+ # - skip
25
+ #
26
+ def edges(type: nil, src: nil, dest: nil, properties: nil, order: nil, limit: nil, skip: nil)
27
+ _type = ":`#{type}`" if type
28
+ _props = quote_hash(properties) if properties
29
+ _order = if order
30
+ raise MissingAliasPrefixError unless order.include?("edge.")
31
+ "ORDER BY #{order}"
32
+ end
33
+ _limit = "LIMIT #{limit}" if limit
34
+ _skip = "SKIP #{skip}" if skip
35
+
36
+ _where = if src || dest
37
+ clauses = [
38
+ ("ID(src) = #{src.id}" if src),
39
+ ("ID(dest) = #{dest.id}" if dest)
40
+ ].compact.join(" AND ")
41
+ "WHERE #{clauses}"
42
+ end
43
+
44
+ cmd = "MATCH (src)-[edge#{_type} #{_props}]->(dest) #{_where}
45
+ RETURN src, edge, dest #{_order} #{_skip} #{_limit}"
46
+ result = _query(cmd)
47
+
48
+ result.resultset.map do |item|
49
+ src = node_from_resultset_item(item["src"])
50
+ dest = node_from_resultset_item(item["dest"])
51
+ edge = edge_from_resultset_item(item["edge"])
52
+
53
+ edge.src = src
54
+ edge.dest = dest
55
+
56
+ edge
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def edge_from_resultset_item(item)
63
+ (edge_id, type_id, _src_id, _dest_id, props) = item
64
+ attrs = {}
65
+
66
+ props.each do |(index, type, value)|
67
+ attrs[get_property(index)] = value
68
+ end
69
+
70
+ Edge.new.tap do |edge|
71
+ edge.id = edge_id
72
+ edge.type = get_relationship_type(type_id)
73
+ edge.properties = attrs
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,86 @@
1
+ module Redgraph
2
+ class Graph
3
+ module NodeMethods
4
+ # Adds a node. If successul it returns the created object, otherwise false
5
+ #
6
+ def add_node(node)
7
+ result = _query("CREATE (n:`#{node.label}` #{quote_hash(node.properties)}) RETURN ID(n)")
8
+ return false if result.stats[:nodes_created] != 1
9
+ id = result.resultset.first["ID(n)"]
10
+ node.id = id
11
+ node
12
+ end
13
+
14
+ def find_node_by_id(id)
15
+ result = _query("MATCH (node) WHERE ID(node) = #{id} RETURN node")
16
+ return nil if result.resultset.empty?
17
+ (node_id, labels, properties) = result.resultset.first["node"]
18
+ attrs = {}
19
+
20
+ properties.each do |(index, type, value)|
21
+ attrs[get_property(index)] = value
22
+ end
23
+ Node.new(label: get_label(labels.first), properties: attrs).tap do |node|
24
+ node.id = node_id
25
+ end
26
+ end
27
+
28
+
29
+ # Returns nodes. Options:
30
+ #
31
+ # - label: filter by label
32
+ # - properties: filter by properties
33
+ # - order: node.name ASC, node.year DESC
34
+ # - limit: number of items
35
+ # - skip: items offset (useful for pagination)
36
+ #
37
+ def nodes(label: nil, properties: nil, order: nil, limit: nil, skip: nil)
38
+ _label = ":`#{label}`" if label
39
+ _props = quote_hash(properties) if properties
40
+ _order = if order
41
+ raise MissingAliasPrefixError unless order.include?("node.")
42
+ "ORDER BY #{order}"
43
+ end
44
+ _limit = "LIMIT #{limit}" if limit
45
+ _skip = "SKIP #{skip}" if skip
46
+
47
+ cmd = "MATCH (node#{_label} #{_props}) RETURN node #{_order} #{_skip} #{_limit}"
48
+
49
+ result = _query(cmd)
50
+
51
+ result.resultset.map do |item|
52
+ node_from_resultset_item(item["node"])
53
+ end
54
+ end
55
+
56
+ # Counts nodes. Options:
57
+ #
58
+ # - label: filter by label
59
+ # - properties: filter by properties
60
+ #
61
+ def count_nodes(label: nil, properties: nil)
62
+ _label = ":`#{label}`" if label
63
+ _props = quote_hash(properties) if properties
64
+
65
+ cmd = "MATCH (node#{_label} #{_props}) RETURN COUNT(node)"
66
+ query(cmd).flatten[0]
67
+ end
68
+
69
+ private
70
+
71
+ # Builds a Node object from the raw data
72
+ #
73
+ def node_from_resultset_item(item)
74
+ (node_id, labels, props) = item
75
+ attrs = {}
76
+
77
+ props.each do |(index, type, value)|
78
+ attrs[get_property(index)] = value
79
+ end
80
+ Node.new(label: get_label(labels.first), properties: attrs).tap do |node|
81
+ node.id = node_id
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -10,8 +10,28 @@ module Redgraph
10
10
  # - query stats
11
11
  #
12
12
  class QueryResponse
13
- def initialize(response)
13
+ TYPES = [
14
+ UNKNOWN = 0,
15
+ NULL = 1,
16
+ STRING = 2,
17
+ INTEGER = 3,
18
+ BOOLEAN = 4,
19
+ DOUBLE = 5,
20
+ ARRAY = 6,
21
+ EDGE = 7,
22
+ NODE = 8,
23
+ PATH = 9,
24
+ MAP = 10,
25
+ POINT = 11
26
+ ].freeze
27
+
28
+ def initialize(response, graph)
14
29
  @response = response
30
+ @graph = graph
31
+
32
+ @header_row = @response[0]
33
+ @result_rows = @response[1]
34
+ @query_statistics = @response[2]
15
35
  end
16
36
 
17
37
  def stats
@@ -26,19 +46,76 @@ module Redgraph
26
46
  @resultset ||= parse_resultset
27
47
  end
28
48
 
49
+ # Wraps in custom datatypes if needed
50
+ #
51
+ def rows
52
+ @result_rows.map do |column|
53
+ column.map do |data|
54
+ reify_column_item(data)
55
+ end
56
+
57
+ end
58
+ end
59
+
29
60
  private
30
61
 
62
+ def reify_column_item(data)
63
+ value_type, value = data
64
+
65
+ case value_type
66
+ when STRING, INTEGER, BOOLEAN, DOUBLE then value
67
+ when NODE then reify_node_item(value)
68
+ when EDGE then reify_edge_item(value)
69
+ else
70
+ "other"
71
+ end
72
+ end
73
+
74
+ def reify_node_item(data)
75
+ (node_id, labels, props) = data
76
+
77
+ label = @graph.get_label(labels[0]) # Only one label is currently supported
78
+
79
+ node = Node.new(label: label)
80
+ node.id = node_id
81
+
82
+ props.each do |(prop_id, prop_type, prop_value)|
83
+ prop_name = @graph.get_property(prop_id)
84
+ node.properties[prop_name] = prop_value
85
+ end
86
+
87
+ node
88
+ end
89
+
90
+ def reify_edge_item(data)
91
+ (edge_id, type_id, src_id, dest_id, props) = data
92
+
93
+ type = @graph.get_relationship_type(type_id)
94
+
95
+ edge = Edge.new(type: type)
96
+ edge.id = edge_id
97
+ edge.src_id = src_id
98
+ edge.dest_id = dest_id
99
+
100
+ props.each do |(prop_id, prop_type, prop_value)|
101
+ prop_name = @graph.get_property(prop_id)
102
+ edge.properties[prop_name] = prop_value
103
+ end
104
+
105
+ edge
106
+ end
107
+
31
108
  # The header lists the entities described in the RETURN clause. It is an
32
109
  # array of [ColumnType (enum), name (string)] elements. We can ignore the
33
110
  # enum, it is always 1 (COLUMN_SCALAR).
34
111
  def parse_header
35
- @response[0].map{|item| item[1]}
112
+ @header_row.map{|item| item[1]}
36
113
  end
37
114
 
38
115
  def parse_stats
39
116
  stats = {}
40
117
 
41
- @response[2].each do |item|
118
+ @query_statistics.each do |item|
42
119
  label, value = item.split(":")
43
120
 
44
121
  case label
@@ -58,7 +135,7 @@ module Redgraph
58
135
 
59
136
  # The resultset has one element per entity (as described by the header)
60
137
  def parse_resultset
61
- @response[1].map do |item|
138
+ @result_rows.map do |item|
62
139
  out = {}
63
140
 
64
141
  item.each.with_index do |(type, value), i|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Redgraph
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class GraphEdgeMethodsTest < Minitest::Test
6
+ def setup
7
+ @graph = Redgraph::Graph.new("movies", url: $REDIS_URL)
8
+
9
+ @al = quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
10
+ @john = quick_add_node(label: 'actor', properties: {name: "John Travolta"})
11
+ end
12
+
13
+ def teardown
14
+ @graph.delete
15
+ end
16
+
17
+ def test_find_edge
18
+ quick_add_edge(type: 'FRIEND_OF', src: @al, dest: @john, properties: {since: 1980})
19
+ edge = @graph.edges.first
20
+
21
+ assert_equal('FRIEND_OF', edge.type)
22
+ assert_equal(1980, edge.properties["since"])
23
+ assert_equal(@al, edge.src)
24
+ assert_equal(@john, edge.dest)
25
+
26
+ end
27
+
28
+ def test_find_all_edges
29
+ marlon = quick_add_node(label: 'actor', properties: {name: "Marlon Brando"})
30
+ film = quick_add_node(label: 'film', properties: {name: "The Godfather"})
31
+ quick_add_edge(type: 'ACTOR_IN', src: marlon, dest: film, properties: {role: 'Don Vito'})
32
+ quick_add_edge(type: 'ACTOR_IN', src: @al, dest: film, properties: {role: 'Michael'})
33
+
34
+ edges = @graph.edges
35
+ assert_equal(2, edges.size)
36
+ end
37
+
38
+ def test_filter_edges
39
+ marlon = quick_add_node(label: 'actor', properties: {name: "Marlon Brando"})
40
+ film = quick_add_node(label: 'film', properties: {name: "The Godfather"})
41
+ other_film = quick_add_node(label: 'film', properties: {name: "Carlito's Way"})
42
+ e_donvito = quick_add_edge(type: 'ACTOR_IN', src: marlon, dest: film, properties: {role: 'Don Vito'})
43
+ e_michael = quick_add_edge(type: 'ACTOR_IN', src: @al, dest: film, properties: {role: 'Michael'})
44
+ e_carlito = quick_add_edge(type: 'ACTOR_IN', src: @al, dest: other_film, properties: {role: 'Carlito'})
45
+ quick_add_edge(type: 'FRIEND_OF', src: @al, dest: marlon, properties: {since: 1980})
46
+
47
+ edges = @graph.edges(type: "FRIEND_OF")
48
+ assert_equal(1, edges.size)
49
+
50
+ edges = @graph.edges(type: "ACTOR_IN")
51
+ assert_equal(3, edges.size)
52
+
53
+ edges = @graph.edges(type: "ACTOR_IN", limit: 2)
54
+ assert_equal(2, edges.size)
55
+
56
+ edges = @graph.edges(type: "ACTOR_IN", skip: 2, limit: 10)
57
+ assert_equal(1, edges.size)
58
+
59
+ edges = @graph.edges(properties: {role: "Carlito"})
60
+ assert_equal([e_carlito], edges)
61
+
62
+ edges = @graph.edges(src: marlon)
63
+ assert_equal([e_donvito], edges)
64
+
65
+ edges = @graph.edges(type: 'ACTOR_IN', dest: film)
66
+ assert_equal(2, edges.size)
67
+ assert_includes(edges, e_donvito)
68
+ assert_includes(edges, e_michael)
69
+
70
+ edges = @graph.edges(src: @al, dest: marlon)
71
+ assert_equal(1, edges.size)
72
+ edge = edges[0]
73
+ assert_equal('FRIEND_OF', edge.type)
74
+ assert_equal(1980, edge.properties["since"])
75
+ end
76
+
77
+ def test_order_edges
78
+ marlon = quick_add_node(label: 'actor', properties: {name: "Marlon Brando"})
79
+
80
+ e1 = quick_add_edge(type: 'FRIEND_OF', src: @al, dest: marlon, properties: {since: 1980})
81
+ e2 = quick_add_edge(type: 'FRIEND_OF', src: @al, dest: @john, properties: {since: 2000})
82
+ e3 = quick_add_edge(type: 'FRIEND_OF', src: marlon, dest: @john, properties: {since: 1990})
83
+
84
+ edges = @graph.edges(type: 'FRIEND_OF', order: "edge.since ASC")
85
+ assert_equal([e1, e3, e2], edges)
86
+
87
+ edges = @graph.edges(type: 'FRIEND_OF', order: "edge.since DESC")
88
+ assert_equal([e2, e3, e1], edges)
89
+ end
90
+
91
+ private
92
+
93
+ def quick_add_node(label:, properties:)
94
+ @graph.add_node(Redgraph::Node.new(label: label, properties: properties))
95
+ end
96
+
97
+ def quick_add_edge(type:, src:, dest:, properties:)
98
+ @graph.add_edge(Redgraph::Edge.new(type: type, src: src, dest: dest, properties: properties))
99
+ end
100
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class GraphNodeMethodsTest < Minitest::Test
6
+ def setup
7
+ @graph = Redgraph::Graph.new("movies", url: $REDIS_URL)
8
+
9
+ @al = quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
10
+ @john = quick_add_node(label: 'actor', properties: {name: "John Travolta"})
11
+ end
12
+
13
+ def teardown
14
+ @graph.delete
15
+ end
16
+
17
+ def test_find_node_by_id
18
+ node = @graph.find_node_by_id(@al.id)
19
+
20
+ refute_nil(node)
21
+ assert_equal("actor", node.label)
22
+ assert_equal("Al Pacino", node.properties["name"])
23
+ assert_equal(@al.id, node.id)
24
+ end
25
+
26
+ def test_find_node_by_wrong_id
27
+ node = @graph.find_node_by_id("-1")
28
+
29
+ assert_nil(node)
30
+ end
31
+
32
+ def test_find_all_nodes
33
+ actors = @graph.nodes
34
+
35
+ assert_equal(2, actors.size)
36
+ assert_includes(actors, @al)
37
+ assert_includes(actors, @john)
38
+ end
39
+
40
+ def test_find_all_nodes_by_label
41
+ film = quick_add_node(label: 'film', properties: {name: "Scarface"})
42
+
43
+ actors = @graph.nodes(label: 'actor')
44
+ assert_equal(2, actors.size)
45
+ assert_includes(actors, @al)
46
+ assert_includes(actors, @john)
47
+
48
+ films = @graph.nodes(label: 'film')
49
+ assert_equal(1, films.size)
50
+ assert_includes(films, film)
51
+ end
52
+
53
+ def test_find_all_nodes_by_property
54
+ scarface = quick_add_node(label: 'film', properties: {name: "Scarface", genre: "drama"})
55
+ casino = quick_add_node(label: 'film', properties: {name: "Casino", genre: "drama"})
56
+ _mamma_mia = quick_add_node(label: 'film', properties: {name: "Mamma Mia", genre: "musical"})
57
+
58
+ dramas = @graph.nodes(properties: {genre: "drama"})
59
+
60
+ assert_equal(2, dramas.size)
61
+ assert_includes(dramas, scarface)
62
+ assert_includes(dramas, casino)
63
+ end
64
+
65
+ def test_order_nodes_by_property
66
+ scarface = quick_add_node(label: 'film', properties: {name: "Scarface", genre: "drama"})
67
+ casino = quick_add_node(label: 'film', properties: {name: "Casino", genre: "drama"})
68
+ mamma_mia = quick_add_node(label: 'film', properties: {name: "Mamma Mia", genre: "musical"})
69
+
70
+ items = @graph.nodes(label: 'film', order: "node.name")
71
+ assert_equal([casino, mamma_mia, scarface], items)
72
+
73
+ items = @graph.nodes(label: 'film', order: "node.name ASC")
74
+ assert_equal([casino, mamma_mia, scarface], items)
75
+
76
+ items = @graph.nodes(label: 'film', order: "node.name DESC")
77
+ assert_equal([scarface, mamma_mia, casino], items)
78
+ end
79
+
80
+ def test_count_nodes
81
+ quick_add_node(label: 'film', properties: {name: "Scarface", genre: "drama"})
82
+ quick_add_node(label: 'film', properties: {name: "Casino", genre: "drama"})
83
+ quick_add_node(label: 'film', properties: {name: "Mamma Mia", genre: "musical"})
84
+
85
+
86
+ assert_equal(5, @graph.count_nodes)
87
+ assert_equal(3, @graph.count_nodes(label: 'film'))
88
+ assert_equal(2, @graph.count_nodes(properties: {genre: "drama"}))
89
+ end
90
+
91
+ def test_limit_nodes
92
+ 10.times do |i|
93
+ quick_add_node(label: 'token', properties: {number: i})
94
+ end
95
+
96
+ items = @graph.nodes(label: 'token', limit: 5)
97
+ assert_equal(5, items.size)
98
+ assert_equal([0,1,2,3,4], items.map{|item| item.properties["number"]})
99
+ end
100
+
101
+ def test_skip_nodes
102
+ 10.times do |i|
103
+ quick_add_node(label: 'token', properties: {number: i})
104
+ end
105
+
106
+ items = @graph.nodes(label: 'token', limit: 3, skip: 3)
107
+ assert_equal(3, items.size)
108
+ assert_equal([3,4,5], items.map{|item| item.properties["number"]})
109
+ end
110
+
111
+ private
112
+
113
+ def quick_add_node(label:, properties:)
114
+ @graph.add_node(Redgraph::Node.new(label: label, properties: properties))
115
+ end
116
+ end
@@ -6,151 +6,39 @@ class GraphQueriesTest < Minitest::Test
6
6
  def setup
7
7
  @graph = Redgraph::Graph.new("movies", url: $REDIS_URL)
8
8
 
9
- @al = quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
10
- @john = quick_add_node(label: 'actor', properties: {name: "John Travolta"})
9
+ @al = quick_add_node(label: 'actor', properties: {name: "Al Pacino", born: 1940})
10
+ @john = quick_add_node(label: 'actor', properties: {name: "John Travolta", born: 1954})
11
11
  end
12
12
 
13
13
  def teardown
14
14
  @graph.delete
15
15
  end
16
16
 
17
- def test_find_node_by_id
18
- node = @graph.find_node_by_id(@al.id)
19
-
20
- refute_nil(node)
21
- assert_equal("actor", node.label)
22
- assert_equal("Al Pacino", node.properties["name"])
23
- assert_equal(@al.id, node.id)
24
- end
25
-
26
- def test_find_node_by_wrong_id
27
- node = @graph.find_node_by_id("-1")
28
-
29
- assert_nil(node)
30
- end
31
-
32
- def test_find_all_nodes
33
- actors = @graph.nodes
34
-
35
- assert_equal(2, actors.size)
36
- assert_includes(actors, @al)
37
- assert_includes(actors, @john)
38
- end
39
-
40
- def test_find_all_nodes_by_label
41
- film = quick_add_node(label: 'film', properties: {name: "Scarface"})
42
-
43
- actors = @graph.nodes(label: 'actor')
44
- assert_equal(2, actors.size)
45
- assert_includes(actors, @al)
46
- assert_includes(actors, @john)
47
-
48
- films = @graph.nodes(label: 'film')
49
- assert_equal(1, films.size)
50
- assert_includes(films, film)
51
- end
52
-
53
- def test_find_all_nodes_by_property
54
- scarface = quick_add_node(label: 'film', properties: {name: "Scarface", genre: "drama"})
55
- casino = quick_add_node(label: 'film', properties: {name: "Casino", genre: "drama"})
56
- mamma_mia = quick_add_node(label: 'film', properties: {name: "Mamma Mia", genre: "musical"})
57
-
58
- dramas = @graph.nodes(properties: {genre: "drama"})
59
-
60
- assert_equal(2, dramas.size)
61
- assert_includes(dramas, scarface)
62
- assert_includes(dramas, casino)
63
- end
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"}))
17
+ def test_query_string_attribute
18
+ result = @graph.query("MATCH (n) RETURN n.name ORDER BY n.name")
19
+ assert_equal([["Al Pacino"], ["John Travolta"]], result)
74
20
  end
75
21
 
76
- def test_limit_nodes
77
- 10.times do |i|
78
- quick_add_node(label: 'token', properties: {number: i})
79
- end
80
-
81
- items = @graph.nodes(label: 'token', limit: 5)
82
- assert_equal(5, items.size)
83
- assert_equal([0,1,2,3,4], items.map{|item| item.properties["number"]})
22
+ def test_query_string_and_number_attributes
23
+ result = @graph.query("MATCH (n) RETURN n.name, n.born ORDER BY n.born")
24
+ assert_equal([["Al Pacino", 1940], ["John Travolta", 1954]], result)
84
25
  end
85
26
 
86
- def test_skip_nodes
87
- 10.times do |i|
88
- quick_add_node(label: 'token', properties: {number: i})
89
- end
90
-
91
- items = @graph.nodes(label: 'token', limit: 3, skip: 3)
92
- assert_equal(3, items.size)
93
- assert_equal([3,4,5], items.map{|item| item.properties["number"]})
27
+ def test_query_nodes
28
+ result = @graph.query("MATCH (n) RETURN n ORDER BY n.born")
29
+ assert_equal([[@al], [@john]], result)
94
30
  end
95
31
 
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)
32
+ def test_query_edge
33
+ edge = quick_add_edge(type: 'FRIEND_OF', src: @al, dest: @john, properties: {since: 1980})
34
+ result = @graph.query("MATCH (src)-[edge]->(dest) RETURN edge")
35
+ assert_equal([[edge]], result)
115
36
  end
116
37
 
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"])
38
+ def test_query_node_and_edge
39
+ edge = quick_add_edge(type: 'FRIEND_OF', src: @al, dest: @john, properties: {since: 1980})
40
+ result = @graph.query("MATCH (src)-[edge:FRIEND_OF]->(dest) RETURN src, edge")
41
+ assert_equal([[@al, edge]], result)
154
42
  end
155
43
 
156
44
  private
data/test/graph_test.rb CHANGED
@@ -12,11 +12,13 @@ class GraphTest < Minitest::Test
12
12
  end
13
13
 
14
14
  def test_list
15
+ skip unless graph_list_supported?
15
16
  list = @graph.list
16
17
  assert_includes(list, "foobar")
17
18
  end
18
19
 
19
20
  def test_delete
21
+ skip unless graph_list_supported?
20
22
  assert_includes(@graph.list, "foobar")
21
23
 
22
24
  @graph.delete
@@ -66,4 +68,15 @@ class GraphTest < Minitest::Test
66
68
  )
67
69
  graph
68
70
  end
71
+
72
+ # This command is only supported in the latest version
73
+ def graph_list_supported?
74
+ @graph.list
75
+ rescue Redis::CommandError => e
76
+ if e.message =~ /ERR unknown command `GRAPH.LIST`/
77
+ false
78
+ else
79
+ true
80
+ end
81
+ end
69
82
  end
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.2
4
+ version: 0.1.3
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-12 00:00:00.000000000 Z
11
+ date: 2021-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -44,12 +44,16 @@ files:
44
44
  - lib/redgraph.rb
45
45
  - lib/redgraph/edge.rb
46
46
  - lib/redgraph/graph.rb
47
+ - lib/redgraph/graph/edge_methods.rb
48
+ - lib/redgraph/graph/node_methods.rb
47
49
  - lib/redgraph/node.rb
48
50
  - lib/redgraph/query_response.rb
49
51
  - lib/redgraph/version.rb
50
52
  - redgraph.gemspec
51
53
  - test/graph_connection_test.rb
54
+ - test/graph_edge_methods_test.rb
52
55
  - test/graph_manipulation_test.rb
56
+ - test/graph_node_methods_test.rb
53
57
  - test/graph_queries_test.rb
54
58
  - test/graph_test.rb
55
59
  - test/redgraph_test.rb