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 +4 -4
- data/.circleci/config.yml +54 -0
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/README.md +34 -3
- data/lib/redisgraph.rb +50 -14
- data/lib/redisgraph/connection.rb +7 -4
- data/lib/redisgraph/errors.rb +6 -6
- data/lib/redisgraph/query_result.rb +102 -23
- data/lib/redisgraph/version.rb +1 -1
- data/spec/helper.rb +5 -0
- data/spec/redisgraph_quickstart_spec.rb +73 -0
- data/spec/redisgraph_spec.rb +79 -0
- metadata +7 -3
- data/test/test_suite.rb +0 -89
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 732b9f9b3c2faa832739c1be5ef607e5f84621351244a604c9addd4b54957410
|
4
|
+
data.tar.gz: 37374c73ca02f5e59b29bba44b77405b460a743fca092fc6172cf67a46a3dc53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
coverage/*
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,31 @@
|
|
1
|
+
[](https://github.com/RedisGraph/redisgraph-rb)
|
2
|
+
[](https://circleci.com/gh/RedisGraph/redisgraph-rb/tree/master)
|
3
|
+
[](https://github.com/RedisGraph/redisgraph-rb/releases/latest)
|
4
|
+
[](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
|
-
|
42
|
-
`
|
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`
|
data/lib/redisgraph.rb
CHANGED
@@ -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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
14
|
-
|
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
|
data/lib/redisgraph/errors.rb
CHANGED
@@ -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
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
|
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
|
-
|
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
|
-
|
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
|
121
|
+
stats[:labels_added] = val
|
35
122
|
when /^Nodes created/
|
36
|
-
stats[:nodes_created] = val
|
123
|
+
stats[:nodes_created] = val
|
37
124
|
when /^Nodes deleted/
|
38
|
-
stats[:nodes_deleted] = val
|
125
|
+
stats[:nodes_deleted] = val
|
39
126
|
when /^Relationships deleted/
|
40
|
-
stats[:relationships_deleted] = val
|
127
|
+
stats[:relationships_deleted] = val
|
41
128
|
when /^Properties set/
|
42
|
-
stats[:properties_set] = val
|
129
|
+
stats[:properties_set] = val
|
43
130
|
when /^Relationships created/
|
44
|
-
stats[:relationships_created] = val
|
131
|
+
stats[:relationships_created] = val
|
45
132
|
when /^Query internal execution time/
|
46
|
-
stats[:internal_execution_time] = val
|
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
|
-
|
data/lib/redisgraph/version.rb
CHANGED
data/spec/helper.rb
ADDED
@@ -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:
|
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:
|
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
|
-
-
|
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
|
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
|