redisgraph 1.0.0 → 2.0.0

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: cbcd7f55a9dd3006c0f4a050171da9987e753f75fd20a99a9e22a1a7094c370b
4
- data.tar.gz: 3b0094755a897bb7d8c8ddaed7a97ac741efb59cbd36983d67caa5a743142b3b
3
+ metadata.gz: 732b9f9b3c2faa832739c1be5ef607e5f84621351244a604c9addd4b54957410
4
+ data.tar.gz: 37374c73ca02f5e59b29bba44b77405b460a743fca092fc6172cf67a46a3dc53
5
5
  SHA512:
6
- metadata.gz: 22b67bea86caecf0c5dc9822a96395f452d3043dc79975b6c6c0e05d02cdb0e7523d65500f5c3ce4bacd56f7b81b25a556c50707db25c02e5bab007ead7dae34
7
- data.tar.gz: 8152ef37023f1ecc2dc3bf4688c3a4e21b3a56bad16af9dcdfaaf11f2f671fe754cedcd81b9d68d9e62f01643535d5594dad52750f947d870676dfe51431e44f
6
+ metadata.gz: 1e79f75391e9c6160f60ec6dfa053804011e17441f64368badfb6974412c40b548f06e8c050d0364722681dc98bb093b41d1f664684c04548d9c5ca6f490e1e6
7
+ data.tar.gz: fb4e0d21066b7331f9f149e96b4bb02d1987ddce2d5ad4b02c710734f8b9bbae1a250dbd7aa2de07598fbfe79de3e004fff51b37ef42006f91eea71e5816f729
@@ -0,0 +1,54 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ - image: circleci/ruby:2.4.1-node-browsers
10
+
11
+ - image: redislabs/redisgraph:edge
12
+ port: 6379:6379
13
+
14
+ working_directory: ~/repo
15
+
16
+ steps:
17
+ - checkout
18
+
19
+ - restore_cache:
20
+ keys:
21
+ - v1-dependencies-{{ checksum "Gemfile" }}
22
+ # fallback to using the latest cache if no exact match is found
23
+ - v1-dependencies-
24
+
25
+ - run:
26
+ name: install dependencies
27
+ command: |
28
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
29
+
30
+ - save_cache:
31
+ paths:
32
+ - ./vendor/bundle
33
+ key: v1-dependencies-{{ checksum "Gemfile" }}
34
+
35
+ # run tests!
36
+ - run:
37
+ name: run tests
38
+ command: bundle exec rspec
39
+
40
+ workflows:
41
+ version: 2
42
+ commit:
43
+ jobs:
44
+ - build
45
+ nightly:
46
+ triggers:
47
+ - schedule:
48
+ cron: "0 0 * * *"
49
+ filters:
50
+ branches:
51
+ only:
52
+ - master
53
+ jobs:
54
+ - build
@@ -0,0 +1 @@
1
+ coverage/*
data/Gemfile CHANGED
@@ -4,4 +4,6 @@ gemspec
4
4
 
5
5
  gem 'redis', '~> 4'
6
6
  gem 'terminal-table', '~> 1', '>= 1.8'
7
+ gem 'rspec'
8
+ gem 'codecov', :require => false, :group => :test
7
9
 
data/README.md CHANGED
@@ -1,6 +1,31 @@
1
+ [![license](https://img.shields.io/github/license/RedisGraph/redisgraph-rb.svg)](https://github.com/RedisGraph/redisgraph-rb)
2
+ [![CircleCI](https://circleci.com/gh/RedisGraph/redisgraph-rb/tree/master.svg?style=svg)](https://circleci.com/gh/RedisGraph/redisgraph-rb/tree/master)
3
+ [![GitHub issues](https://img.shields.io/github/release/RedisGraph/redisgraph-rb.svg)](https://github.com/RedisGraph/redisgraph-rb/releases/latest)
4
+ [![Codecov](https://codecov.io/gh/RedisGraph/redisgraph-rb/branch/master/graph/badge.svg)](https://codecov.io/gh/RedisGraph/redisgraph-rb)
5
+
6
+
1
7
  # redisgraph-rb
2
8
 
3
- `redisgraph-rb` is a Ruby gem client for the RedisGraph module. It relies on `redis-rb` for Redis connection management and provides support for graph QUERY, EXPLAIN, and DELETE commands.
9
+ `redisgraph-rb` is a Ruby gem client for the [RedisGraph](https://github.com/RedisLabsModules/RedisGraph) module. It relies on `redis-rb` for Redis connection management and provides support for graph QUERY, EXPLAIN, and DELETE commands.
10
+
11
+ ## RedisGraph compatibility
12
+ `redisgraph-rb` is currently compatible with RedisGraph versions >= 1.99 (module version: 19900)
13
+
14
+ The result set structure introduced by RedisGraph 2.0 requires some modifications to this client. If you are interested in using this client with the latest RedisGraph, please inform us by commenting on [the corresponding issue](https://github.com/RedisGraph/redisgraph-rb/issues/1)!
15
+
16
+ ### Previous Version
17
+ For RedisGraph versions >= 1.0 and < 2.0 (ie module version: 10202), instead use and refer to
18
+ the redisgraph gem version ~> 1.0.0
19
+
20
+ which corresponds to the following docker image
21
+ `docker run -p 6379:6379 -it --rm redislabs/redisgraph:1.2.2`
22
+
23
+ ## Installation
24
+ To install, run:
25
+
26
+ `$ gem install redisgraph`
27
+
28
+ Or include `redisgraph` as a dependency in your Gemfile.
4
29
 
5
30
  ## Usage
6
31
  ```
@@ -38,7 +63,13 @@ RedisGraph connects to an active Redis server, defaulting to `host: localhost, p
38
63
  These parameters are described fully in the documentation for https://github.com/redis/redis-rb
39
64
 
40
65
  ## Running tests
41
- A simple test suite is provided, and can be run with:
42
- `ruby test/test_suite.rb`
66
+ To ensure prerequisites are installed, run the following:
67
+ `bundle install`
68
+
43
69
  These tests expect a Redis server with the Graph module loaded to be available at localhost:6379
44
70
 
71
+ The currently compatible version of the RedisGraph module may be run as follows:
72
+ `docker run -p 6379:6379 -it --rm redislabs/redisgraph:2.0-edge`
73
+
74
+ A simple test suite is provided, and can be run with:
75
+ `rspec`
@@ -8,37 +8,73 @@ require_relative 'redisgraph/connection.rb'
8
8
  class RedisGraph
9
9
  attr_accessor :connection
10
10
  attr_accessor :graphname
11
+ attr_accessor :metadata
12
+
13
+ class Metadata
14
+ def initialize(opts = {})
15
+ @graphname = opts[:graphname]
16
+ @connection = opts[:connection]
17
+
18
+ # cache semantics around these labels, propertyKeys, and relationshipTypes
19
+ # defers first read and is invalidated when changed.
20
+ @labels_proc = -> { call_procedure('db.labels') }
21
+ @property_keys_proc = -> { call_procedure('db.propertyKeys') }
22
+ @relationship_types_proc = -> { call_procedure('db.relationshipTypes') }
23
+ end
24
+
25
+ def invalidate
26
+ @labels = @property_key = @relationship_types
27
+ end
28
+
29
+ def labels
30
+ @labels ||= @labels_proc.call
31
+ end
32
+
33
+ def property_keys
34
+ @property_keys ||= @property_keys_proc.call
35
+ end
36
+
37
+ def relationship_types
38
+ @relationship_types ||= @relationship_types_proc.call
39
+ end
40
+
41
+ def call_procedure(procedure)
42
+ res = @connection.call("GRAPH.QUERY", @graphname, "CALL #{procedure}()")
43
+ res[1].flatten
44
+ rescue Redis::CommandError => e
45
+ raise CallError, e
46
+ end
47
+ end
11
48
 
12
49
  # The RedisGraph constructor instantiates a Redis connection
13
50
  # and validates that the graph module is loaded
14
51
  def initialize(graph, redis_options = {})
15
52
  @graphname = graph
16
53
  connect_to_server(redis_options)
54
+ @metadata = Metadata.new(graphname: @graphname,
55
+ connection: @connection)
17
56
  end
18
57
 
19
58
  # Execute a command and return its parsed result
20
59
  def query(command)
21
- begin
22
- resp = @connection.call("GRAPH.QUERY", @graphname, command)
23
- rescue Redis::CommandError => e
24
- raise QueryError, e
25
- end
26
-
27
- QueryResult.new(resp)
60
+ resp = @connection.call("GRAPH.QUERY", @graphname, command, '--compact')
61
+ QueryResult.new(resp,
62
+ metadata: @metadata)
63
+ rescue Redis::CommandError => e
64
+ raise QueryError, e
28
65
  end
29
66
 
30
67
  # Return the execution plan for a given command
31
68
  def explain(command)
32
- begin
33
- resp = @connection.call("GRAPH.EXPLAIN", @graphname, command)
34
- rescue Redis::CommandError => e
35
- raise QueryError, e
36
- end
69
+ @connection.call("GRAPH.EXPLAIN", @graphname, command)
70
+ rescue Redis::CommandError => e
71
+ raise ExplainError, e
37
72
  end
38
73
 
39
74
  # Delete the graph and all associated keys
40
75
  def delete
41
- resp = @connection.call("GRAPH.DELETE", @graphname)
76
+ @connection.call("GRAPH.DELETE", @graphname)
77
+ rescue Redis::CommandError => e
78
+ raise DeleteError, e
42
79
  end
43
80
  end
44
-
@@ -1,16 +1,19 @@
1
1
  class RedisGraph
2
2
  def connect_to_server(options)
3
3
  @connection = Redis.new(options)
4
- self.verify_module()
4
+ @module_version = module_version()
5
+ raise ServerError, "RedisGraph module not loaded." if @module_version.nil?
6
+ raise ServerError, "RedisGraph module incompatible, expecting >= 1.99." if @module_version < 19900
5
7
  end
6
8
 
7
9
  # Ensure that the connected Redis server supports modules
8
10
  # and has loaded the RedisGraph module
9
- def verify_module()
11
+ def module_version()
10
12
  redis_version = @connection.info["redis_version"]
11
13
  major_version = redis_version.split('.').first.to_i
12
14
  raise ServerError, "Redis 4.0 or greater required for RedisGraph support." unless major_version >= 4
13
- resp = @connection.call("MODULE", "LIST")
14
- raise ServerError, "RedisGraph module not loaded." unless resp.first && resp.first.include?("graph")
15
+ modules = @connection.call("MODULE", "LIST")
16
+ module_graph = modules.detect { |_name_key, name, _ver_key, _ver| name == 'graph' }
17
+ module_graph[3] if module_graph
15
18
  end
16
19
  end
@@ -1,10 +1,10 @@
1
1
  class RedisGraph
2
- class RedisGraphError < RuntimeError
3
- end
2
+ class RedisGraphError < RuntimeError; end
4
3
 
5
- class ServerError < RedisGraphError
6
- end
4
+ class ServerError < RedisGraphError; end
7
5
 
8
- class QueryError < RedisGraphError
9
- end
6
+ class CallError < RedisGraphError; end
7
+ class QueryError < RedisGraphError; end
8
+ class ExplainError < RedisGraphError; end
9
+ class DeleteError < RedisGraphError; end
10
10
  end
@@ -3,6 +3,23 @@ class QueryResult
3
3
  attr_accessor :resultset
4
4
  attr_accessor :stats
5
5
 
6
+ def initialize(response, opts = {})
7
+ # The response for any query is expected to be a nested array.
8
+ # If compact (RedisGraph protocol v2)
9
+ # The resultset is an array w/ three elements:
10
+ # 0] Node/Edge key names w/ the ordinal position used in [1]
11
+ # en lieu of the name to compact the result set.
12
+ # 1] Node/Edge key/value pairs as an array w/ two elements:
13
+ # 0] node/edge name id from [0]
14
+ # 1..matches] node/edge values
15
+ # 2] Statistics as an array of strings
16
+
17
+ @metadata = opts[:metadata]
18
+
19
+ @resultset = parse_resultset(response)
20
+ @stats = parse_stats(response)
21
+ end
22
+
6
23
  def print_resultset
7
24
  pretty = Terminal::Table.new headings: columns do |t|
8
25
  resultset.each { |record| t << record }
@@ -11,49 +28,111 @@ class QueryResult
11
28
  end
12
29
 
13
30
  def parse_resultset(response)
31
+ # In the v2 protocol, CREATE does not contain an empty row preceding statistics
32
+ return unless response.length > 1
33
+
14
34
  # Any non-empty result set will have multiple rows (arrays)
15
- return nil unless response[0].length > 1
16
- # First row is return elements / properties
17
- @columns = response[0].shift
18
- # Subsequent rows are records
19
- @resultset = response[0]
35
+
36
+
37
+ # First row is header describing the returned records, corresponding
38
+ # precisely in order and naming to the RETURN clause of the query.
39
+ header = response[0]
40
+ @columns = header.map { |(_type, name)| name }
41
+
42
+ # Second row is the actual data returned by the query
43
+ # note handling for encountering an id for propertyKey that is out of
44
+ # the cached set.
45
+ data = response[1].map do |row|
46
+ i = -1
47
+ header.reduce([]) do |agg, (type, _it)|
48
+ i += 1
49
+ el = row[i]
50
+
51
+ case type
52
+ when 1 # scalar
53
+ agg << map_scalar(el[0], el[1])
54
+ when 2 # node
55
+ props = el[2]
56
+ agg << props.sort_by { |prop| prop[0] }.map { |prop| map_prop(prop) }
57
+ when 3 # relation
58
+ props = el[4]
59
+ agg << props.sort_by { |prop| prop[0] }.map { |prop| map_prop(prop) }
60
+ end
61
+
62
+ agg
63
+ end
64
+ end
65
+
66
+ data
67
+ end
68
+
69
+ def map_scalar(type, val)
70
+ map_func = case type
71
+ when 1 # null
72
+ return nil
73
+ when 2 # string
74
+ :to_s
75
+ when 3 # integer
76
+ :to_i
77
+ when 4 # boolean
78
+ # no :to_b
79
+ return val == "true"
80
+ when 5 # double
81
+ :to_f
82
+ # TODO: when in the distro packages and docker images,
83
+ # the following _should_ work
84
+ # when 6 # array
85
+ # val.map { |it| map_scalar(it[0], it[1]) }
86
+ end
87
+ val.send(map_func)
88
+ end
89
+
90
+ def map_prop(prop)
91
+ # maximally a single @metadata.invalidate should occur
92
+
93
+ property_keys = @metadata.property_keys
94
+ prop_index = prop[0]
95
+ if prop_index > property_keys.length
96
+ @metadata.invalidate
97
+ property_keys = @metadata.property_keys
98
+ end
99
+ { property_keys[prop_index] => map_scalar(prop[1], prop[2]) }
20
100
  end
21
101
 
22
102
  # Read metrics about internal query handling
23
103
  def parse_stats(response)
24
- return nil unless response[1]
104
+ # In the v2 protocol, CREATE does not contain an empty row preceding statistics
105
+ stats_offset = response.length == 1 ? 0 : 2
25
106
 
107
+ return nil unless response[stats_offset]
108
+
109
+ parse_stats_row(response[stats_offset])
110
+ end
111
+
112
+ def parse_stats_row(response_row)
26
113
  stats = {}
27
114
 
28
- response[1].each do |stat|
115
+ response_row.each do |stat|
29
116
  line = stat.split(': ')
30
- val = line[1].split(' ')[0]
117
+ val = line[1].split(' ')[0].to_i
31
118
 
32
119
  case line[0]
33
120
  when /^Labels added/
34
- stats[:labels_added] = val.to_i
121
+ stats[:labels_added] = val
35
122
  when /^Nodes created/
36
- stats[:nodes_created] = val.to_i
123
+ stats[:nodes_created] = val
37
124
  when /^Nodes deleted/
38
- stats[:nodes_deleted] = val.to_i
125
+ stats[:nodes_deleted] = val
39
126
  when /^Relationships deleted/
40
- stats[:relationships_deleted] = val.to_i
127
+ stats[:relationships_deleted] = val
41
128
  when /^Properties set/
42
- stats[:properties_set] = val.to_i
129
+ stats[:properties_set] = val
43
130
  when /^Relationships created/
44
- stats[:relationships_created] = val.to_i
131
+ stats[:relationships_created] = val
45
132
  when /^Query internal execution time/
46
- stats[:internal_execution_time] = val.to_f
133
+ stats[:internal_execution_time] = val
47
134
  end
48
135
  end
49
136
  stats
50
137
  end
51
-
52
- def initialize(response)
53
- # The response for any query is expected to be a nested array.
54
- # The only top-level values will be the result set and the statistics.
55
- @resultset = parse_resultset(response)
56
- @stats = parse_stats(response)
57
- end
58
138
  end
59
-
@@ -1,3 +1,3 @@
1
1
  class RedisGraph
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -0,0 +1,5 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'codecov'
5
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
@@ -0,0 +1,73 @@
1
+ require 'helper.rb'
2
+
3
+ require_relative '../lib/redisgraph.rb'
4
+
5
+ # based on queries extracted from
6
+ describe RedisGraph do
7
+ before(:all) do
8
+ begin
9
+ @r = RedisGraph.new("#{described_class}_test")
10
+ create_graph
11
+ rescue Redis::BaseError => e
12
+ $stderr.puts(e)
13
+ exit 1
14
+ end
15
+ end
16
+
17
+ after(:all) do
18
+ @r.delete if @r
19
+ end
20
+
21
+ def create_graph()
22
+ q = "CREATE (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'})," \
23
+ "(:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'})," \
24
+ "(:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})"
25
+
26
+ res = @r.query(q)
27
+
28
+ expect(res.resultset).to be_nil
29
+ stats = res.stats
30
+ expect(stats).to include(:internal_execution_time)
31
+ stats.delete(:internal_execution_time)
32
+ expect(stats).to eq({
33
+ labels_added: 2,
34
+ nodes_created: 6,
35
+ properties_set: 6,
36
+ relationships_created: 3
37
+ })
38
+ end
39
+
40
+ context 'quickstart' do
41
+ it 'should query relations, with a predicate' do
42
+ q = "MATCH (r:Rider)-[:rides]->(t:Team) WHERE t.name = 'Yamaha' RETURN r.name, t.name"
43
+
44
+ res = @r.query(q)
45
+
46
+ expect(res.columns).to eq(["r.name", "t.name"])
47
+ expect(res.resultset).to eq([["Valentino Rossi", "Yamaha"]])
48
+ end
49
+
50
+ # not in the quickstart, but demonstrates multiple rows
51
+ it 'should query relations, without a predicate' do
52
+ q = "MATCH (r:Rider)-[:rides]->(t:Team) RETURN r.name, t.name ORDER BY r.name"
53
+
54
+ res = @r.query(q)
55
+
56
+ expect(res.columns).to eq(["r.name", "t.name"])
57
+ expect(res.resultset).to eq([
58
+ ["Andrea Dovizioso", "Ducati"],
59
+ ["Dani Pedrosa", "Honda"],
60
+ ["Valentino Rossi", "Yamaha"]
61
+ ])
62
+ end
63
+
64
+ it 'should query relations, with an aggregate function' do
65
+ q = "MATCH (r:Rider)-[:rides]->(t:Team {name:'Ducati'}) RETURN count(r)"
66
+
67
+ res = @r.query(q)
68
+
69
+ expect(res.columns).to eq(["count(r)"])
70
+ expect(res.resultset).to eq([[1]])
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,79 @@
1
+ require 'helper.rb'
2
+
3
+ require_relative '../lib/redisgraph.rb'
4
+
5
+ describe RedisGraph do
6
+ # TODO it would be nice to have something like DisposableRedis
7
+ # Connect to a Redis server on localhost:6379
8
+ before(:all) do
9
+ begin
10
+ @r = RedisGraph.new("#{described_class}_test")
11
+ create_graph
12
+ rescue Redis::BaseError => e
13
+ $stderr.puts(e)
14
+ end
15
+ end
16
+
17
+ # Ensure that the graph "rubytest" does not exist
18
+ after(:all) do
19
+ @r.delete if @r
20
+ end
21
+
22
+ def create_graph()
23
+ q = """CREATE (t:node {name: 'src'})"""
24
+
25
+ res = @r.query(q)
26
+ expect(res.resultset).to be_nil
27
+
28
+ plan = @r.explain(q)
29
+ expect(plan).to include("Create")
30
+
31
+ expect(res.stats[:labels_added]).to eq(1)
32
+ expect(res.stats[:nodes_created]).to eq(1)
33
+ expect(res.stats[:properties_set]).to eq(1)
34
+ end
35
+
36
+ # Test functions - each validates one or more EXPLAIN and QUERY calls
37
+
38
+ context "bare return" do
39
+ it "should map values properly" do
40
+ q = """UNWIND [1, 1.5, null, 'strval', true, false] AS a RETURN a"""
41
+ res = @r.query(q)
42
+ expect(res.resultset).to eq([[1], [1.5], [nil], ["strval"], [true], [false]])
43
+ end
44
+ end
45
+
46
+ context "nodes" do
47
+ it "should delete nodes properly" do
48
+ q = """MATCH (t:node) WHERE t.name = 'src' DELETE t"""
49
+ plan = @r.explain(q)
50
+ expect(plan).to include("Delete")
51
+ res = @r.query(q)
52
+ expect(res.resultset).to be_nil
53
+ expect(res.stats[:nodes_deleted]).to eq(1)
54
+ end
55
+ end
56
+
57
+ context "edges" do
58
+ it "should create edges properly" do
59
+ q = "CREATE (p:node {name: 'src1', color: 'cyan'})-[:edge { weight: 7.8 }]->(:node {name: 'dest1', color: 'magenta'})," \
60
+ " (:node {name: 'src2'})-[:edge { weight: 12 }]->(q:node_type_2 {name: 'dest2'})"
61
+ plan = @r.explain(q)
62
+ expect(plan).to include("Create")
63
+ res = @r.query(q)
64
+ expect(res.resultset).to be_nil
65
+ expect(res.stats[:nodes_created]).to eq(4)
66
+ expect(res.stats[:properties_set]).to eq(8)
67
+ expect(res.stats[:relationships_created]).to eq(2)
68
+ end
69
+
70
+ it "should traverse edges properly" do
71
+ q = """MATCH (a)-[e:edge]->(b:node) RETURN a.name, b, e"""
72
+ plan = @r.explain(q)
73
+ expect(plan.detect { |row| row.include?("Traverse") }).to_not be_nil
74
+ res = @r.query(q)
75
+ expect(res.columns).to eq(["a.name", "b", "e"])
76
+ expect(res.resultset).to eq([["src1", [{"name"=>"dest1"}, {"color"=>"magenta"}], [{"weight"=>7.8}]]])
77
+ end
78
+ end
79
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redisgraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Redis Labs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-13 00:00:00.000000000 Z
11
+ date: 2019-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -51,6 +51,8 @@ executables: []
51
51
  extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
+ - ".circleci/config.yml"
55
+ - ".gitignore"
54
56
  - Gemfile
55
57
  - LICENSE
56
58
  - README.md
@@ -60,7 +62,9 @@ files:
60
62
  - lib/redisgraph/query_result.rb
61
63
  - lib/redisgraph/version.rb
62
64
  - redisgraph.gemspec
63
- - test/test_suite.rb
65
+ - spec/helper.rb
66
+ - spec/redisgraph_quickstart_spec.rb
67
+ - spec/redisgraph_spec.rb
64
68
  homepage: https://github.com/redislabs/redisgraph-rb
65
69
  licenses:
66
70
  - BSD-3-Clause
@@ -1,89 +0,0 @@
1
- require_relative '../lib/redisgraph.rb'
2
- require "test/unit"
3
- include Test::Unit::Assertions
4
-
5
- # Helper functions
6
- # TODO it would be nice to have something like DisposableRedis
7
-
8
- # Connect to a Redis server on localhost:6379
9
- def connect_test
10
- begin
11
- @r = RedisGraph.new("rubytest")
12
- rescue Redis::BaseError => e
13
- puts e
14
- puts "RedisGraph tests require that a Redis server with the graph module loaded be running on localhost:6379"
15
- exit 1
16
- end
17
- end
18
-
19
- # Ensure that the graph "rubytest" does not exist
20
- def delete_graph
21
- @r.delete
22
- end
23
-
24
- # Test functions - each validates one or more EXPLAIN and QUERY calls
25
-
26
- def validate_node_creation
27
- query_str = """CREATE (t:node {name: 'src'})"""
28
- x = @r.query(query_str)
29
- plan = @r.explain(query_str)
30
- assert(plan =~ /Create/)
31
- assert(x.resultset.nil?)
32
- assert(x.stats[:labels_added] == 1)
33
- assert(x.stats[:nodes_created] == 1)
34
- assert(x.stats[:properties_set] == 1)
35
- puts "Create node - PASSED"
36
- end
37
-
38
- def validate_node_deletion
39
- query_str = """MATCH (t:node) WHERE t.name = 'src' DELETE t"""
40
- plan = @r.explain(query_str)
41
- assert(plan =~ /Delete/)
42
- x = @r.query(query_str)
43
- assert(x.resultset.nil?)
44
- assert(x.stats[:nodes_deleted] == 1)
45
- query_str = """MATCH (t:node) WHERE t.name = 'src' RETURN t"""
46
- assert(x.resultset.nil?)
47
- puts "Delete node - PASSED"
48
- end
49
-
50
- def validate_edge_creation
51
- query_str = """CREATE (p:node {name: 'src1'})-[:edge]->(:node {name: 'dest1'}), (:node {name: 'src2'})-[:edge]->(q:node_type_2 {name: 'dest2'})"""
52
- plan = @r.explain(query_str)
53
- assert(plan =~ /Create/)
54
- x = @r.query(query_str)
55
- assert(x.resultset.nil?)
56
- assert(x.stats[:nodes_created] == 4)
57
- assert(x.stats[:properties_set] == 4)
58
- assert(x.stats[:relationships_created] == 2)
59
- puts "Add edges - PASSED"
60
- end
61
-
62
- def validate_edge_traversal
63
- query_str = """MATCH (a)-[:edge]->(b:node) RETURN a, b"""
64
- plan = @r.explain(query_str)
65
- assert(plan.include?("Traverse"))
66
- x = @r.query(query_str)
67
- assert(x.resultset)
68
- assert(x.columns.length == 2)
69
- assert(x.resultset.length == 1)
70
- assert(x.resultset[0] == ["src1", "dest1"])
71
- puts "Traverse edge - PASSED"
72
- end
73
-
74
- def test_suite
75
- puts "Running RedisGraph tests..."
76
- connect_test
77
- delete_graph # Clear the graph
78
-
79
- # Test basic functionalities
80
- validate_node_creation
81
- validate_node_deletion
82
- validate_edge_creation
83
- validate_edge_traversal
84
-
85
- delete_graph # Clear the graph again
86
- puts "RedisGraph tests passed!"
87
- end
88
-
89
- test_suite