neo4j-core 3.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +22 -0
  3. data/README.md +332 -0
  4. data/lib/neo4j-core.rb +27 -0
  5. data/lib/neo4j-core/cypher_translator.rb +34 -0
  6. data/lib/neo4j-core/hash_with_indifferent_access.rb +165 -0
  7. data/lib/neo4j-core/helpers.rb +25 -0
  8. data/lib/neo4j-core/label.rb +8 -0
  9. data/lib/neo4j-core/version.rb +5 -0
  10. data/lib/neo4j-embedded.rb +18 -0
  11. data/lib/neo4j-embedded/embedded_database.rb +29 -0
  12. data/lib/neo4j-embedded/embedded_label.rb +80 -0
  13. data/lib/neo4j-embedded/embedded_node.rb +163 -0
  14. data/lib/neo4j-embedded/embedded_relationship.rb +44 -0
  15. data/lib/neo4j-embedded/embedded_session.rb +151 -0
  16. data/lib/neo4j-embedded/property.rb +43 -0
  17. data/lib/neo4j-embedded/to_java.rb +49 -0
  18. data/lib/neo4j-server.rb +10 -0
  19. data/lib/neo4j-server/cypher_label.rb +28 -0
  20. data/lib/neo4j-server/cypher_node.rb +140 -0
  21. data/lib/neo4j-server/cypher_node_uncommited.rb +12 -0
  22. data/lib/neo4j-server/cypher_relationship.rb +82 -0
  23. data/lib/neo4j-server/cypher_response.rb +113 -0
  24. data/lib/neo4j-server/cypher_session.rb +156 -0
  25. data/lib/neo4j-server/cypher_transaction.rb +81 -0
  26. data/lib/neo4j-server/resource.rb +73 -0
  27. data/lib/neo4j/entity_equality.rb +9 -0
  28. data/lib/neo4j/jars/concurrentlinkedhashmap-lru-1.3.1.jar +0 -0
  29. data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  30. data/lib/neo4j/jars/lucene-core-3.6.2.jar +0 -0
  31. data/lib/neo4j/jars/neo4j-cypher-2.0.0-M06.jar +0 -0
  32. data/lib/neo4j/jars/neo4j-kernel-2.0-SNAPSHOT-tests.jar +0 -0
  33. data/lib/neo4j/jars/neo4j-kernel-2.0.0-M06.jar +0 -0
  34. data/lib/neo4j/jars/neo4j-lucene-index-2.0.0-M06.jar +0 -0
  35. data/lib/neo4j/jars/neo4j-management-2.0.0-M06.jar +0 -0
  36. data/lib/neo4j/jars/org.apache.servicemix.bundles.jline-0.9.94_1.jar +0 -0
  37. data/lib/neo4j/jars/parboiled-core-1.1.6.jar +0 -0
  38. data/lib/neo4j/jars/parboiled-scala_2.10-1.1.6.jar +0 -0
  39. data/lib/neo4j/jars/scala-library-2.10.2.jar +0 -0
  40. data/lib/neo4j/label.rb +88 -0
  41. data/lib/neo4j/node.rb +185 -0
  42. data/lib/neo4j/property_container.rb +22 -0
  43. data/lib/neo4j/property_validator.rb +23 -0
  44. data/lib/neo4j/relationship.rb +84 -0
  45. data/lib/neo4j/session.rb +124 -0
  46. data/lib/neo4j/tasks/neo4j_server.rb +131 -0
  47. data/lib/neo4j/transaction.rb +52 -0
  48. data/neo4j-core.gemspec +35 -0
  49. metadata +144 -0
@@ -0,0 +1,43 @@
1
+
2
+ # TODO code duplication with the Neo4j::PropertyContainer,
3
+ # This module should extend that module by adding transaction around methods
4
+ module Neo4j::Embedded::Property
5
+ include Neo4j::PropertyValidator
6
+ include Neo4j::PropertyContainer
7
+ extend Neo4j::Core::TxMethods
8
+
9
+ # inherit the []= method but add auto transaction around it
10
+ tx_methods :[]=
11
+
12
+ def [](key)
13
+ return nil unless has_property?(key.to_s)
14
+ get_property(key.to_s)
15
+ end
16
+ tx_methods :[]
17
+
18
+ def props
19
+ property_keys.inject({}) do |ret, key|
20
+ ret[key.to_sym] = get_property(key)
21
+ ret
22
+ end
23
+ end
24
+ tx_methods :props
25
+
26
+
27
+ def props=(hash)
28
+ property_keys.each do |key|
29
+ remove_property(key)
30
+ end
31
+
32
+ hash.each_pair do |k,v|
33
+ set_property(k,v)
34
+ end
35
+ hash
36
+ end
37
+ tx_methods :props=
38
+
39
+
40
+ def neo_id
41
+ get_id
42
+ end
43
+ end
@@ -0,0 +1,49 @@
1
+ module Neo4j::Embedded
2
+ # A Utility class for translating Ruby object to Neo4j Java types
3
+ # @private
4
+ module ToJava
5
+ def type_to_java(type)
6
+ Java::OrgNeo4jGraphdb::DynamicRelationshipType.withName(type.to_s)
7
+ end
8
+
9
+ module_function :type_to_java
10
+
11
+ def types_to_java(types)
12
+ types.inject([]) { |result, type| result << type_to_java(type) }.to_java(Java::OrgNeo4jGraphdb::RelationshipType)
13
+ end
14
+
15
+ module_function :types_to_java
16
+
17
+
18
+ def dir_from_java(dir)
19
+ case dir
20
+ when Java::OrgNeo4jGraphdb::Direction::OUTGOING then
21
+ :outgoing
22
+ when Java::OrgNeo4jGraphdb::Direction::BOTH then
23
+ :both
24
+ when Java::OrgNeo4jGraphdb::Direction::INCOMING then
25
+ :incoming
26
+ else
27
+ raise "unknown direction '#{dir} / #{dir.class}'"
28
+ end
29
+ end
30
+
31
+ module_function :dir_from_java
32
+
33
+ def dir_to_java(dir)
34
+ case dir
35
+ when :outgoing then
36
+ Java::OrgNeo4jGraphdb::Direction::OUTGOING
37
+ when :both then
38
+ Java::OrgNeo4jGraphdb::Direction::BOTH
39
+ when :incoming then
40
+ Java::OrgNeo4jGraphdb::Direction::INCOMING
41
+ else
42
+ raise "unknown direction '#{dir}', expects argument: outgoing, :incoming or :both"
43
+ end
44
+ end
45
+
46
+ module_function :dir_to_java
47
+
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'neo4j-server/resource'
4
+ require 'neo4j-server/cypher_node'
5
+ require 'neo4j-server/cypher_label'
6
+ require 'neo4j-server/cypher_session'
7
+ require 'neo4j-server/cypher_node_uncommited'
8
+ require 'neo4j-server/cypher_relationship'
9
+ require 'neo4j-server/cypher_response'
10
+ require 'neo4j-server/cypher_transaction'
@@ -0,0 +1,28 @@
1
+ module Neo4j::Server
2
+ class CypherLabel
3
+ extend Forwardable
4
+ def_delegator :@session, :query_cypher_for
5
+ attr_reader :name
6
+
7
+ def initialize(session, name)
8
+ @name = name
9
+ @session = session
10
+ end
11
+
12
+ def create_index(*properties)
13
+ response = @session._query("CREATE INDEX ON :`#{@name}`(#{properties.join(',')})")
14
+ response.raise_error if response.error?
15
+ end
16
+
17
+ def drop_index(*properties)
18
+ properties.each do |property|
19
+ response = @session._query("DROP INDEX ON :`#{@name}`(#{property})")
20
+ response.raise_error if response.error? && !response.error_msg.match(/No such INDEX ON/)
21
+ end
22
+ end
23
+
24
+ def indexes
25
+ @session.indexes(@name)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,140 @@
1
+ module Neo4j::Server
2
+ class CypherNode < Neo4j::Node
3
+ include Neo4j::Server::Resource
4
+ include Neo4j::Core::CypherTranslator
5
+
6
+ def initialize(session, id)
7
+ @session = session
8
+ @id = id
9
+ end
10
+
11
+ def neo_id
12
+ @id
13
+ end
14
+
15
+ def inspect
16
+ "CypherNode #{neo_id} (#{object_id})"
17
+ end
18
+
19
+ # TODO, needed by neo4j-cypher
20
+ def _java_node
21
+ self
22
+ end
23
+
24
+ # (see Neo4j::Node#create_rel)
25
+ def create_rel(type, other_node, props = nil)
26
+ q = "START a=node(#{neo_id}), b=node(#{other_node.neo_id}) CREATE (a)-[r:`#{type}` #{cypher_prop_list(props)}]->(b) RETURN ID(r)"
27
+ id = @session._query_or_fail(q, true)
28
+ CypherRelationship.new(@session, id)
29
+ end
30
+
31
+ # (see Neo4j::Node#props)
32
+ def props
33
+ props = @session._query_or_fail("START n=node(#{neo_id}) RETURN n", true)['data']
34
+ props.keys.inject({}){|hash,key| hash[key.to_sym] = props[key]; hash}
35
+ end
36
+
37
+ # (see Neo4j::Node#remove_property)
38
+ def remove_property(key)
39
+ @session._query_or_fail("START n=node(#{neo_id}) REMOVE n.`#{key}`")
40
+ end
41
+
42
+ # (see Neo4j::Node#set_property)
43
+ def set_property(key,value)
44
+ @session._query_or_fail("START n=node(#{neo_id}) SET n.`#{key}` = { value }", false, value: value)
45
+ value
46
+ end
47
+
48
+ # (see Neo4j::Node#props=)
49
+ def props=(properties)
50
+ @session._query_or_fail("START n=node(#{neo_id}) SET n = { props }", false, {props: properties})
51
+ properties
52
+ end
53
+
54
+ # (see Neo4j::Node#get_property)
55
+ def get_property(key)
56
+ @session._query_or_fail("START n=node(#{neo_id}) RETURN n.`#{key}`", true)
57
+ end
58
+
59
+ # (see Neo4j::Node#labels)
60
+ def labels
61
+ r = @session._query_or_fail("START n=node(#{neo_id}) RETURN labels(n) as labels", true)
62
+ r.map(&:to_sym)
63
+ end
64
+
65
+ # (see Neo4j::Node#del)
66
+ def del
67
+ @session._query_or_fail("START n = node(#{neo_id}) MATCH n-[r]-() DELETE r")
68
+ @session._query_or_fail("START n = node(#{neo_id}) DELETE n")
69
+ end
70
+
71
+ # (see Neo4j::Node#exist?)
72
+ def exist?
73
+ response = @session._query("START n=node(#{neo_id}) RETURN ID(n)")
74
+ if (!response.error?)
75
+ return true
76
+ elsif (response.error_status == 'EntityNotFoundException')
77
+ return false
78
+ else
79
+ response.raise_error
80
+ end
81
+ end
82
+
83
+
84
+ # (see Neo4j::Node#node)
85
+ def node(match={})
86
+ result = match(CypherNode, "ID(p)", match)
87
+ raise "Expected to only find one relationship from node #{neo_id} matching #{match.inspect} but found #{result.count}" if result.count > 1
88
+ result.first
89
+ end
90
+
91
+ # (see Neo4j::Node#rel)
92
+ def rel(match={})
93
+ result = match(CypherRelationship, "ID(r)", match)
94
+ raise "Expected to only find one relationship from node #{neo_id} matching #{match.inspect} but found #{result.count}" if result.count > 1
95
+ result.first
96
+ end
97
+
98
+ # (see Neo4j::Node#rel?)
99
+ def rel?(match={})
100
+ result = match(CypherRelationship, "ID(r)", match)
101
+ !!result.first
102
+ end
103
+
104
+ # (see Neo4j::Node#nodes)
105
+ def nodes(match={})
106
+ match(CypherNode, "ID(p)", match)
107
+ end
108
+
109
+
110
+ # (see Neo4j::Node#rels)
111
+ def rels(match = {dir: :both})
112
+ match(CypherRelationship, "ID(r)", match)
113
+ end
114
+
115
+ # @private
116
+ def match(clazz, returns, match={})
117
+ to_dir = {outgoing: ->(rel) {"-#{rel}->"},
118
+ incoming: ->(rel) {"<-#{rel}-"},
119
+ both: ->(rel) {"-#{rel}-"} }
120
+
121
+ cypher_rel = match[:type] ? "[r:`#{match[:type]}`]" : '[r]'
122
+ between_id = match[:between] ? ",p=node(#{match[:between].neo_id}) " : ""
123
+ dir_func = to_dir[match[:dir] || :both]
124
+ cypher = "START n=node(#{neo_id}) #{between_id} MATCH (n)#{dir_func.call(cypher_rel)}(p) RETURN #{returns}"
125
+ r = @session._query(cypher)
126
+ r.raise_error if r.error?
127
+ _map_result(r, clazz)
128
+ end
129
+
130
+ # @private
131
+ def _map_result(r, clazz)
132
+ r.data.map do |rel|
133
+ next if r.uncommited? ? rel['row'].first.nil? : rel.first.nil?
134
+ id = r.uncommited? ? rel['row'].first : rel.first
135
+ clazz.new(@session, id)
136
+ end.compact
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,12 @@
1
+ module Neo4j::Server
2
+ class CypherNodeUncommited
3
+ def initialize(db, data)
4
+ @db = db
5
+ @data = data
6
+ end
7
+
8
+ def [](key)
9
+ @data[key.to_s]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,82 @@
1
+ module Neo4j::Server
2
+
3
+ class CypherRelationship < Neo4j::Relationship
4
+ include Neo4j::Server::Resource
5
+
6
+ def initialize(session, id)
7
+ @session = session
8
+ @id = id
9
+ end
10
+
11
+ def ==(o)
12
+ o.class == self.class && o.neo_id == neo_id
13
+ end
14
+ alias_method :eql?, :==
15
+
16
+ def neo_id
17
+ @id
18
+ end
19
+
20
+ def inspect
21
+ "CypherRelationship #{neo_id}"
22
+ end
23
+
24
+ def load_resource
25
+ id = neo_id
26
+ unless @resource_data
27
+ r = @session._query_internal{ rel(id) }
28
+ @resource_data = r.first_data
29
+ end
30
+ end
31
+
32
+ def start_node
33
+ load_resource
34
+ id = resource_url_id(resource_url(:start))
35
+ Neo4j::Node.load(id)
36
+ end
37
+
38
+ def end_node
39
+ load_resource
40
+ id = resource_url_id(resource_url(:end))
41
+ Neo4j::Node.load(id)
42
+ end
43
+
44
+ def get_property(key)
45
+ id = neo_id
46
+ r = @session._query_internal{rel(id)[key]}
47
+ expect_response_code(r.response, 200)
48
+ r.first_data
49
+ end
50
+
51
+ def set_property(key,value)
52
+ id = neo_id
53
+ r = @session._query_internal{rel(id)[key]=value}
54
+ expect_response_code(r.response, 200)
55
+ end
56
+
57
+ def remove_property(key)
58
+ id = neo_id
59
+ r = @session._query_internal{rel(id)[key]=:NULL}
60
+ expect_response_code(r.response, 200)
61
+ end
62
+
63
+ def del
64
+ id = neo_id
65
+ @session._query_internal{rel(id).del}.raise_unless_response_code(200)
66
+ end
67
+
68
+ def exist?
69
+ id = neo_id
70
+ response = @session._query_internal{rel(id)}
71
+
72
+ if (!response.error?)
73
+ return true
74
+ elsif (response.error_status == 'BadInputException') # TODO see github issue neo4j/1061
75
+ return false
76
+ else
77
+ response.raise_error
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,113 @@
1
+ module Neo4j::Server
2
+ class CypherResponse
3
+ attr_reader :data, :columns, :error_msg, :error_status, :error_code, :response
4
+
5
+ class ResponseError < StandardError
6
+ attr_reader :status, :code
7
+
8
+ def initialize(msg, status, code)
9
+ super(msg)
10
+ @status = status
11
+ @code = code
12
+ end
13
+ end
14
+
15
+
16
+ class HashEnumeration
17
+ include Enumerable
18
+ extend Forwardable
19
+ def_delegator :@response, :error_msg
20
+ def_delegator :@response, :error_status
21
+ def_delegator :@response, :error_code
22
+ def_delegator :@response, :data
23
+ def_delegator :@response, :columns
24
+
25
+ def initialize(response)
26
+ @response = response
27
+ end
28
+
29
+ def each()
30
+ data.each do |row|
31
+ row.each_with_index do |row, i|
32
+ yield columns[i].to_sym => row[i]
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def to_hash_enumeration
39
+ HashEnumeration.new(self)
40
+ end
41
+
42
+ def initialize(response, uncommited = false)
43
+ @response = response
44
+ @uncommited = uncommited
45
+ end
46
+
47
+
48
+ def first_data
49
+ if uncommited?
50
+ @data.first['row'].first
51
+ else
52
+ @data[0][0]
53
+ end
54
+ end
55
+
56
+ def error?
57
+ !!@error
58
+ end
59
+
60
+ def uncommited?
61
+ @uncommited
62
+ end
63
+
64
+ def raise_unless_response_code(code)
65
+ raise "Response code #{response.code}, expected #{code} for #{response.request.path}, #{response.body}" unless response.code == code
66
+ end
67
+
68
+ def set_data(data, columns)
69
+ @data = data
70
+ @columns = columns
71
+ self
72
+ end
73
+
74
+ def set_error(error_msg, error_status, error_core)
75
+ @error = true
76
+ @error_msg = error_msg
77
+ @error_status = error_status
78
+ @error_code = error_core
79
+ self
80
+ end
81
+
82
+ def raise_error
83
+ raise "Tried to raise error without an error" unless @error
84
+ raise ResponseError.new(@error_msg, @error_status, @error_code)
85
+ end
86
+
87
+ def self.create_with_no_tx(response)
88
+ case response.code
89
+ when 200
90
+ CypherResponse.new(response).set_data(response['data'], response['columns'])
91
+ when 400
92
+ CypherResponse.new(response).set_error(response['message'], response['exception'], response['fullname'])
93
+ else
94
+ raise "Unknown response code #{response.code} for #{response.request.path.to_s}"
95
+ end
96
+ end
97
+
98
+ def self.create_with_tx(response)
99
+ raise "Unknown response code #{response.code} for #{response.request.path.to_s}" unless response.code == 200
100
+
101
+ first_result = response['results'][0]
102
+ cr = CypherResponse.new(response, true)
103
+
104
+ if (response['errors'].empty?)
105
+ cr.set_data(first_result['data'], first_result['columns'])
106
+ else
107
+ first_error = response['errors'].first
108
+ cr.set_error(first_error['message'], first_error['status'], first_error['code'])
109
+ end
110
+ cr
111
+ end
112
+ end
113
+ end