redisgraph 1.0.0 → 2.0.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: cbcd7f55a9dd3006c0f4a050171da9987e753f75fd20a99a9e22a1a7094c370b
4
- data.tar.gz: 3b0094755a897bb7d8c8ddaed7a97ac741efb59cbd36983d67caa5a743142b3b
3
+ metadata.gz: 6d49963c89e143060931e2fc7a20c73a0fe41a06f0002ec84494e6182ad28728
4
+ data.tar.gz: 90b9a339ae932fcea45bbf355518e54db7ddcad137739d5d2ad95bcb72b5bd99
5
5
  SHA512:
6
- metadata.gz: 22b67bea86caecf0c5dc9822a96395f452d3043dc79975b6c6c0e05d02cdb0e7523d65500f5c3ce4bacd56f7b81b25a556c50707db25c02e5bab007ead7dae34
7
- data.tar.gz: 8152ef37023f1ecc2dc3bf4688c3a4e21b3a56bad16af9dcdfaaf11f2f671fe754cedcd81b9d68d9e62f01643535d5594dad52750f947d870676dfe51431e44f
6
+ metadata.gz: d4b3bc2e45a843c4328786fc1ce99e07873f9f0d09de3773ca572491867ccdc9b0f06653830167160fcf4e036349150166262890cc783e180569faecfd43bc6b
7
+ data.tar.gz: 8a0f6138c785a35ede21619c98960e3bdf96a557b98cdb3a183f1ee41ad3f99890c1e011ac6588e49efab2bcaa4f998b44cd0a44aff222155b3d0ece2b10f803
@@ -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,21 @@
1
+ name-template: 'Version $NEXT_PATCH_VERSION'
2
+ tag-template: 'v$NEXT_PATCH_VERSION'
3
+ categories:
4
+ - title: 'Features'
5
+ labels:
6
+ - 'feature'
7
+ - 'enhancement'
8
+ - title: 'Bug Fixes'
9
+ labels:
10
+ - 'fix'
11
+ - 'bugfix'
12
+ - 'bug'
13
+ - title: 'Maintenance'
14
+ label: 'chore'
15
+ change-template: '- $TITLE (#$NUMBER)'
16
+ exclude-labels:
17
+ - 'skip-changelog'
18
+ template: |
19
+ ## Changes
20
+
21
+ $CHANGES
@@ -0,0 +1,20 @@
1
+ name: Release Drafter
2
+
3
+ on:
4
+ push:
5
+ # branches to consider in the event; optional, defaults to all
6
+ branches:
7
+ - master
8
+
9
+ jobs:
10
+ update_release_draft:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ # Drafts your next Release notes as Pull Requests are merged into "master"
14
+ - uses: release-drafter/release-drafter@v5
15
+ with:
16
+ # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
17
+ config-name: release-drafter-config.yml
18
+ env:
19
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20
+
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ coverage/*
data/Gemfile CHANGED
@@ -3,5 +3,6 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem 'redis', '~> 4'
6
- gem 'terminal-table', '~> 1', '>= 1.8'
6
+ gem 'rspec'
7
+ gem 'codecov', :require => false, :group => :test
7
8
 
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
+ [![Gem Version](https://badge.fury.io/rb/redisgraph.svg)](https://badge.fury.io/rb/redisgraph)
6
+
1
7
  # redisgraph-rb
8
+ [![Forum](https://img.shields.io/badge/Forum-RedisGraph-blue)](https://forum.redislabs.com/c/modules/redisgraph)
9
+ [![Discord](https://img.shields.io/discord/697882427875393627?style=flat-square)](https://discord.gg/gWBRT6P)
10
+
11
+ `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.
12
+
13
+ ## RedisGraph compatibility
14
+ The current version of `redisgraph-rb` is compatible with RedisGraph versions >= 1.99 (module version: 19900).
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:
2
25
 
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.
26
+ `$ gem install redisgraph`
27
+
28
+ Or include `redisgraph` as a dependency in your Gemfile.
4
29
 
5
30
  ## Usage
6
31
  ```
@@ -20,11 +45,11 @@ cmd = """MATCH ()-[:works]->(e:employer) RETURN e"""
20
45
  response = r.query(cmd)
21
46
 
22
47
  response.print_resultset
23
- +----------------+
24
- | e.name |
25
- +----------------+
26
- | Dunder Mifflin |
27
- +----------------+
48
+ --------------------------------
49
+ | e |
50
+ --------------------------------
51
+ | [{"name"=>"Dunder Mifflin"}] |
52
+ --------------------------------
28
53
 
29
54
  r.delete
30
55
  => "Graph removed, internal execution time: 0.416024 milliseconds"
@@ -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`
@@ -1,16 +1,26 @@
1
1
  class RedisGraph
2
2
  def connect_to_server(options)
3
3
  @connection = Redis.new(options)
4
- self.verify_module()
4
+ check_module_version
5
5
  end
6
6
 
7
7
  # Ensure that the connected Redis server supports modules
8
8
  # and has loaded the RedisGraph module
9
- def verify_module()
9
+ def check_module_version()
10
10
  redis_version = @connection.info["redis_version"]
11
11
  major_version = redis_version.split('.').first.to_i
12
12
  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")
13
+
14
+ begin
15
+ modules = @connection.call("MODULE", "LIST")
16
+ rescue Redis::CommandError
17
+ # Ignore check if the connected server does not support the "MODULE LIST" command
18
+ return
19
+ end
20
+
21
+ module_graph = modules.detect { |_name_key, name, _ver_key, _ver| name == 'graph' }
22
+ module_version = module_graph[3] if module_graph
23
+ raise ServerError, "RedisGraph module not loaded." if module_version.nil?
24
+ raise ServerError, "RedisGraph module incompatible, expecting >= 1.99." if module_version < 19900
15
25
  end
16
26
  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
@@ -1,59 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Data types that can be returned in result sets
4
+ module ValueType
5
+ UNKNOWN = 0
6
+ NULL = 1
7
+ STRING = 2
8
+ INTEGER = 3
9
+ BOOLEAN = 4
10
+ DOUBLE = 5
11
+ ARRAY = 6
12
+ EDGE = 7
13
+ NODE = 8
14
+ PATH = 9 # TODO: not yet implemented
15
+ end
16
+
1
17
  class QueryResult
2
18
  attr_accessor :columns
3
19
  attr_accessor :resultset
4
20
  attr_accessor :stats
5
21
 
22
+ def initialize(response, opts = {})
23
+ # The response for any query is expected to be a nested array.
24
+ # If compact (RedisGraph protocol v2)
25
+ # The resultset is an array w/ three elements:
26
+ # 0] Node/Edge key names w/ the ordinal position used in [1]
27
+ # en lieu of the name to compact the result set.
28
+ # 1] Node/Edge key/value pairs as an array w/ two elements:
29
+ # 0] node/edge name id from [0]
30
+ # 1..matches] node/edge values
31
+ # 2] Statistics as an array of strings
32
+
33
+ @metadata = opts[:metadata]
34
+
35
+ @resultset = parse_resultset(response)
36
+ @stats = parse_stats(response)
37
+ end
38
+
6
39
  def print_resultset
7
- pretty = Terminal::Table.new headings: columns do |t|
8
- resultset.each { |record| t << record }
40
+ return unless columns
41
+
42
+ # Compute max length of each column
43
+ column_sizes = resultset.reduce([]) do |lengths, row|
44
+ row.each_with_index.map{|iterand, index| [lengths[index] || 0, iterand.to_s.length].max}
45
+ end
46
+
47
+ # Print column headers
48
+ puts head = '-' * (column_sizes.inject(&:+) + (3 * column_sizes.count) + 1)
49
+ row = columns.fill(nil, columns.size..(column_sizes.size - 1))
50
+ row = row.each_with_index.map{|v, i| v = v.to_s + ' ' * (column_sizes[i] - v.to_s.length)}
51
+ puts '| ' + row.join(' | ') + ' |'
52
+ puts head
53
+
54
+ # Print result set rows
55
+ resultset.each do |row|
56
+ row = row.fill(nil, row.size..(column_sizes.size - 1))
57
+ row = row.each_with_index.map{|v, i| v = v.to_s + ' ' * (column_sizes[i] - v.to_s.length)}
58
+ puts '| ' + row.join(' | ') + ' |'
9
59
  end
10
- puts pretty
60
+ puts head
11
61
  end
12
62
 
13
63
  def parse_resultset(response)
64
+ # In the v2 protocol, CREATE does not contain an empty row preceding statistics
65
+ return unless response.length > 1
66
+
14
67
  # 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]
68
+
69
+ # First row is header describing the returned records, corresponding
70
+ # precisely in order and naming to the RETURN clause of the query.
71
+ header = response[0]
72
+ @columns = header.map { |(_type, name)| name }
73
+
74
+ # Second row is the actual data returned by the query
75
+ # note handling for encountering an id for propertyKey that is out of
76
+ # the cached set.
77
+ data = response[1].map do |row|
78
+ i = -1
79
+ header.reduce([]) do |agg, (type, _it)|
80
+ i += 1
81
+ el = row[i]
82
+ case type
83
+ when 1 # Column of scalars
84
+ agg << map_scalar(el[0], el[1])
85
+ when 2 # node
86
+ props = el[2]
87
+ agg << props.sort_by { |prop| prop[0] }.map { |prop| map_prop(prop) }
88
+ when 3 # Column of relations
89
+ props = el[4]
90
+ agg << props.sort_by { |prop| prop[0] }.map { |prop| map_prop(prop) }
91
+ end
92
+ end
93
+ end
94
+
95
+ data
96
+ end
97
+
98
+ def map_scalar(type, val)
99
+ map_func = case type
100
+ when ValueType::NULL
101
+ return nil
102
+ when ValueType::STRING
103
+ :to_s
104
+ when ValueType::INTEGER
105
+ :to_i
106
+ when ValueType::BOOLEAN
107
+ # no :to_b
108
+ return val == 'true'
109
+ when ValueType::DOUBLE
110
+ :to_f
111
+ when ValueType::ARRAY
112
+ return val.map { |it| map_scalar(it[0], it[1]) }
113
+ when ValueType::EDGE
114
+ props = val[4]
115
+ return props.sort_by { |prop| prop[0] }.map { |prop| map_prop(prop) }
116
+ when ValueType::NODE
117
+ props = val[2]
118
+ return props.sort_by { |prop| prop[0] }.map { |prop| map_prop(prop) }
119
+ end
120
+ val.send(map_func)
121
+ end
122
+
123
+ def map_prop(prop)
124
+ # maximally a single @metadata.invalidate should occur
125
+
126
+ property_keys = @metadata.property_keys
127
+ prop_index = prop[0]
128
+ if prop_index >= property_keys.length
129
+ @metadata.invalidate
130
+ property_keys = @metadata.property_keys
131
+ end
132
+ { property_keys[prop_index] => map_scalar(prop[1], prop[2]) }
20
133
  end
21
134
 
22
135
  # Read metrics about internal query handling
23
136
  def parse_stats(response)
24
- return nil unless response[1]
137
+ # In the v2 protocol, CREATE does not contain an empty row preceding statistics
138
+ stats_offset = response.length == 1 ? 0 : 2
139
+
140
+ return nil unless response[stats_offset]
25
141
 
142
+ parse_stats_row(response[stats_offset])
143
+ end
144
+
145
+ def parse_stats_row(response_row)
26
146
  stats = {}
27
147
 
28
- response[1].each do |stat|
148
+ response_row.each do |stat|
29
149
  line = stat.split(': ')
30
- val = line[1].split(' ')[0]
150
+ val = line[1].split(' ')[0].to_i
31
151
 
32
152
  case line[0]
33
153
  when /^Labels added/
34
- stats[:labels_added] = val.to_i
154
+ stats[:labels_added] = val
35
155
  when /^Nodes created/
36
- stats[:nodes_created] = val.to_i
156
+ stats[:nodes_created] = val
37
157
  when /^Nodes deleted/
38
- stats[:nodes_deleted] = val.to_i
158
+ stats[:nodes_deleted] = val
39
159
  when /^Relationships deleted/
40
- stats[:relationships_deleted] = val.to_i
160
+ stats[:relationships_deleted] = val
41
161
  when /^Properties set/
42
- stats[:properties_set] = val.to_i
162
+ stats[:properties_set] = val
43
163
  when /^Relationships created/
44
- stats[:relationships_created] = val.to_i
164
+ stats[:relationships_created] = val
45
165
  when /^Query internal execution time/
46
- stats[:internal_execution_time] = val.to_f
166
+ stats[:internal_execution_time] = val
47
167
  end
48
168
  end
49
169
  stats
50
170
  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
171
  end
59
-
@@ -1,3 +1,3 @@
1
1
  class RedisGraph
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.3'
3
3
  end
data/lib/redisgraph.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'redis'
2
- require 'terminal-table'
3
2
 
4
3
  require_relative 'redisgraph/errors.rb'
5
4
  require_relative 'redisgraph/query_result.rb'
@@ -8,37 +7,73 @@ require_relative 'redisgraph/connection.rb'
8
7
  class RedisGraph
9
8
  attr_accessor :connection
10
9
  attr_accessor :graphname
10
+ attr_accessor :metadata
11
+
12
+ class Metadata
13
+ def initialize(opts = {})
14
+ @graphname = opts[:graphname]
15
+ @connection = opts[:connection]
16
+
17
+ # cache semantics around these labels, propertyKeys, and relationshipTypes
18
+ # defers first read and is invalidated when changed.
19
+ @labels_proc = -> { call_procedure('db.labels') }
20
+ @property_keys_proc = -> { call_procedure('db.propertyKeys') }
21
+ @relationship_types_proc = -> { call_procedure('db.relationshipTypes') }
22
+ end
23
+
24
+ def invalidate
25
+ @labels = @property_keys = @relationship_types = nil
26
+ end
27
+
28
+ def labels
29
+ @labels ||= @labels_proc.call
30
+ end
31
+
32
+ def property_keys
33
+ @property_keys ||= @property_keys_proc.call
34
+ end
35
+
36
+ def relationship_types
37
+ @relationship_types ||= @relationship_types_proc.call
38
+ end
39
+
40
+ def call_procedure(procedure)
41
+ res = @connection.call("GRAPH.QUERY", @graphname, "CALL #{procedure}()")
42
+ res[1].flatten
43
+ rescue Redis::CommandError => e
44
+ raise CallError, e
45
+ end
46
+ end
11
47
 
12
48
  # The RedisGraph constructor instantiates a Redis connection
13
49
  # and validates that the graph module is loaded
14
50
  def initialize(graph, redis_options = {})
15
51
  @graphname = graph
16
52
  connect_to_server(redis_options)
53
+ @metadata = Metadata.new(graphname: @graphname,
54
+ connection: @connection)
17
55
  end
18
56
 
19
57
  # Execute a command and return its parsed result
20
58
  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)
59
+ resp = @connection.call('GRAPH.QUERY', @graphname, command, '--compact')
60
+ QueryResult.new(resp,
61
+ metadata: @metadata)
62
+ rescue Redis::CommandError => e
63
+ raise QueryError, e
28
64
  end
29
65
 
30
66
  # Return the execution plan for a given command
31
67
  def explain(command)
32
- begin
33
- resp = @connection.call("GRAPH.EXPLAIN", @graphname, command)
34
- rescue Redis::CommandError => e
35
- raise QueryError, e
36
- end
68
+ @connection.call('GRAPH.EXPLAIN', @graphname, command)
69
+ rescue Redis::CommandError => e
70
+ raise ExplainError, e
37
71
  end
38
72
 
39
73
  # Delete the graph and all associated keys
40
74
  def delete
41
- resp = @connection.call("GRAPH.DELETE", @graphname)
75
+ @connection.call('GRAPH.DELETE', @graphname)
76
+ rescue Redis::CommandError => e
77
+ raise DeleteError, e
42
78
  end
43
79
  end
44
-
data/redisgraph.gemspec CHANGED
@@ -20,5 +20,4 @@ Gem::Specification.new do |s|
20
20
  s.files = `git ls-files`.split("\n")
21
21
 
22
22
  s.add_runtime_dependency('redis', '~> 4')
23
- s.add_runtime_dependency('terminal-table', '~> 1', '>= 1.8')
24
23
  end
data/spec/helper.rb ADDED
@@ -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,95 @@
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
+
80
+ context "update" do
81
+ it "should support adding new properties" do
82
+ q = """MATCH (a {name: 'src1'}) SET a.newval = true"""
83
+ plan = @r.explain(q)
84
+ expect(plan.detect { |row| row.include?("Update") }).to_not be_nil
85
+ res = @r.query(q)
86
+ expect(res.stats[:properties_set]).to eq(1)
87
+ end
88
+
89
+ it "should print property strings correctly after updates" do
90
+ q = """MATCH (a {name: 'src1'}) RETURN a"""
91
+ res = @r.query(q)
92
+ expect(res.resultset).to eq([[[{"name"=>"src1"}, {"color"=>"cyan"}, {"newval"=>true}]]])
93
+ end
94
+ end
95
+ 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.3
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: 2021-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -24,26 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4'
27
- - !ruby/object:Gem::Dependency
28
- name: terminal-table
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1'
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: '1.8'
37
- type: :runtime
38
- prerelease: false
39
- version_requirements: !ruby/object:Gem::Requirement
40
- requirements:
41
- - - "~>"
42
- - !ruby/object:Gem::Version
43
- version: '1'
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '1.8'
47
27
  description: A client that extends redis-rb to provide explicit support for the RedisGraph
48
28
  module.
49
29
  email: jeffrey@redislabs.com
@@ -51,6 +31,10 @@ executables: []
51
31
  extensions: []
52
32
  extra_rdoc_files: []
53
33
  files:
34
+ - ".circleci/config.yml"
35
+ - ".github/release-drafter-config.yml"
36
+ - ".github/workflows/release-drafter.yml"
37
+ - ".gitignore"
54
38
  - Gemfile
55
39
  - LICENSE
56
40
  - README.md
@@ -60,7 +44,9 @@ files:
60
44
  - lib/redisgraph/query_result.rb
61
45
  - lib/redisgraph/version.rb
62
46
  - redisgraph.gemspec
63
- - test/test_suite.rb
47
+ - spec/helper.rb
48
+ - spec/redisgraph_quickstart_spec.rb
49
+ - spec/redisgraph_spec.rb
64
50
  homepage: https://github.com/redislabs/redisgraph-rb
65
51
  licenses:
66
52
  - BSD-3-Clause
@@ -80,8 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
66
  - !ruby/object:Gem::Version
81
67
  version: '0'
82
68
  requirements: []
83
- rubyforge_project:
84
- rubygems_version: 2.7.7
69
+ rubygems_version: 3.2.22
85
70
  signing_key:
86
71
  specification_version: 4
87
72
  summary: A client for RedisGraph
data/test/test_suite.rb DELETED
@@ -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