redisgraph 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cbcd7f55a9dd3006c0f4a050171da9987e753f75fd20a99a9e22a1a7094c370b
4
+ data.tar.gz: 3b0094755a897bb7d8c8ddaed7a97ac741efb59cbd36983d67caa5a743142b3b
5
+ SHA512:
6
+ metadata.gz: 22b67bea86caecf0c5dc9822a96395f452d3043dc79975b6c6c0e05d02cdb0e7523d65500f5c3ce4bacd56f7b81b25a556c50707db25c02e5bab007ead7dae34
7
+ data.tar.gz: 8152ef37023f1ecc2dc3bf4688c3a4e21b3a56bad16af9dcdfaaf11f2f671fe754cedcd81b9d68d9e62f01643535d5594dad52750f947d870676dfe51431e44f
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'redis', '~> 4'
6
+ gem 'terminal-table', '~> 1', '>= 1.8'
7
+
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2018, Redis Labs
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # redisgraph-rb
2
+
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.
4
+
5
+ ## Usage
6
+ ```
7
+ require 'redisgraph'
8
+
9
+ graphname = "sample"
10
+
11
+ r = RedisGraph.new(graphname)
12
+
13
+ cmd = """CREATE (:person {name: 'Jim', age: 29})-[:works]->(:employer {name: 'Dunder Mifflin'})"""
14
+ response = r.query(cmd)
15
+ response.stats
16
+ => {:labels_added=>2, :nodes_created=>2, :properties_set=>3, :relationships_created=>1, :internal_execution_time=>0.705541}
17
+
18
+ cmd = """MATCH ()-[:works]->(e:employer) RETURN e"""
19
+
20
+ response = r.query(cmd)
21
+
22
+ response.print_resultset
23
+ +----------------+
24
+ | e.name |
25
+ +----------------+
26
+ | Dunder Mifflin |
27
+ +----------------+
28
+
29
+ r.delete
30
+ => "Graph removed, internal execution time: 0.416024 milliseconds"
31
+ ```
32
+
33
+ ## Specifying Redis options
34
+ RedisGraph connects to an active Redis server, defaulting to `host: localhost, port: 6379`. To provide custom connection parameters, instantiate a RedisGraph object with a `redis_options` hash:
35
+
36
+ `r = RedisGraph.new("graphname", redis_options= { host: "127.0.0.1", port: 26380 })`
37
+
38
+ These parameters are described fully in the documentation for https://github.com/redis/redis-rb
39
+
40
+ ## Running tests
41
+ A simple test suite is provided, and can be run with:
42
+ `ruby test/test_suite.rb`
43
+ These tests expect a Redis server with the Graph module loaded to be available at localhost:6379
44
+
@@ -0,0 +1,16 @@
1
+ class RedisGraph
2
+ def connect_to_server(options)
3
+ @connection = Redis.new(options)
4
+ self.verify_module()
5
+ end
6
+
7
+ # Ensure that the connected Redis server supports modules
8
+ # and has loaded the RedisGraph module
9
+ def verify_module()
10
+ redis_version = @connection.info["redis_version"]
11
+ major_version = redis_version.split('.').first.to_i
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")
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ class RedisGraph
2
+ class RedisGraphError < RuntimeError
3
+ end
4
+
5
+ class ServerError < RedisGraphError
6
+ end
7
+
8
+ class QueryError < RedisGraphError
9
+ end
10
+ end
@@ -0,0 +1,59 @@
1
+ class QueryResult
2
+ attr_accessor :columns
3
+ attr_accessor :resultset
4
+ attr_accessor :stats
5
+
6
+ def print_resultset
7
+ pretty = Terminal::Table.new headings: columns do |t|
8
+ resultset.each { |record| t << record }
9
+ end
10
+ puts pretty
11
+ end
12
+
13
+ def parse_resultset(response)
14
+ # 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]
20
+ end
21
+
22
+ # Read metrics about internal query handling
23
+ def parse_stats(response)
24
+ return nil unless response[1]
25
+
26
+ stats = {}
27
+
28
+ response[1].each do |stat|
29
+ line = stat.split(': ')
30
+ val = line[1].split(' ')[0]
31
+
32
+ case line[0]
33
+ when /^Labels added/
34
+ stats[:labels_added] = val.to_i
35
+ when /^Nodes created/
36
+ stats[:nodes_created] = val.to_i
37
+ when /^Nodes deleted/
38
+ stats[:nodes_deleted] = val.to_i
39
+ when /^Relationships deleted/
40
+ stats[:relationships_deleted] = val.to_i
41
+ when /^Properties set/
42
+ stats[:properties_set] = val.to_i
43
+ when /^Relationships created/
44
+ stats[:relationships_created] = val.to_i
45
+ when /^Query internal execution time/
46
+ stats[:internal_execution_time] = val.to_f
47
+ end
48
+ end
49
+ stats
50
+ 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
+ end
59
+
@@ -0,0 +1,3 @@
1
+ class RedisGraph
2
+ VERSION = '1.0.0'
3
+ end
data/lib/redisgraph.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'redis'
2
+ require 'terminal-table'
3
+
4
+ require_relative 'redisgraph/errors.rb'
5
+ require_relative 'redisgraph/query_result.rb'
6
+ require_relative 'redisgraph/connection.rb'
7
+
8
+ class RedisGraph
9
+ attr_accessor :connection
10
+ attr_accessor :graphname
11
+
12
+ # The RedisGraph constructor instantiates a Redis connection
13
+ # and validates that the graph module is loaded
14
+ def initialize(graph, redis_options = {})
15
+ @graphname = graph
16
+ connect_to_server(redis_options)
17
+ end
18
+
19
+ # Execute a command and return its parsed result
20
+ 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)
28
+ end
29
+
30
+ # Return the execution plan for a given command
31
+ def explain(command)
32
+ begin
33
+ resp = @connection.call("GRAPH.EXPLAIN", @graphname, command)
34
+ rescue Redis::CommandError => e
35
+ raise QueryError, e
36
+ end
37
+ end
38
+
39
+ # Delete the graph and all associated keys
40
+ def delete
41
+ resp = @connection.call("GRAPH.DELETE", @graphname)
42
+ end
43
+ end
44
+
@@ -0,0 +1,24 @@
1
+ require "./lib/redisgraph/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "redisgraph"
5
+
6
+ s.version = RedisGraph::VERSION
7
+
8
+ s.license = 'BSD-3-Clause'
9
+
10
+ s.homepage = 'https://github.com/redislabs/redisgraph-rb'
11
+
12
+ s.summary = 'A client for RedisGraph'
13
+
14
+ s.description = 'A client that extends redis-rb to provide explicit support for the RedisGraph module.'
15
+
16
+ s.authors = ['Redis Labs']
17
+
18
+ s.email = 'jeffrey@redislabs.com'
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+
22
+ s.add_runtime_dependency('redis', '~> 4')
23
+ s.add_runtime_dependency('terminal-table', '~> 1', '>= 1.8')
24
+ end
@@ -0,0 +1,89 @@
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
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redisgraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Redis Labs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
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
+ description: A client that extends redis-rb to provide explicit support for the RedisGraph
48
+ module.
49
+ email: jeffrey@redislabs.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - Gemfile
55
+ - LICENSE
56
+ - README.md
57
+ - lib/redisgraph.rb
58
+ - lib/redisgraph/connection.rb
59
+ - lib/redisgraph/errors.rb
60
+ - lib/redisgraph/query_result.rb
61
+ - lib/redisgraph/version.rb
62
+ - redisgraph.gemspec
63
+ - test/test_suite.rb
64
+ homepage: https://github.com/redislabs/redisgraph-rb
65
+ licenses:
66
+ - BSD-3-Clause
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.7.7
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: A client for RedisGraph
88
+ test_files: []