neo4j-core 3.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +22 -0
- data/README.md +332 -0
- data/lib/neo4j-core.rb +27 -0
- data/lib/neo4j-core/cypher_translator.rb +34 -0
- data/lib/neo4j-core/hash_with_indifferent_access.rb +165 -0
- data/lib/neo4j-core/helpers.rb +25 -0
- data/lib/neo4j-core/label.rb +8 -0
- data/lib/neo4j-core/version.rb +5 -0
- data/lib/neo4j-embedded.rb +18 -0
- data/lib/neo4j-embedded/embedded_database.rb +29 -0
- data/lib/neo4j-embedded/embedded_label.rb +80 -0
- data/lib/neo4j-embedded/embedded_node.rb +163 -0
- data/lib/neo4j-embedded/embedded_relationship.rb +44 -0
- data/lib/neo4j-embedded/embedded_session.rb +151 -0
- data/lib/neo4j-embedded/property.rb +43 -0
- data/lib/neo4j-embedded/to_java.rb +49 -0
- data/lib/neo4j-server.rb +10 -0
- data/lib/neo4j-server/cypher_label.rb +28 -0
- data/lib/neo4j-server/cypher_node.rb +140 -0
- data/lib/neo4j-server/cypher_node_uncommited.rb +12 -0
- data/lib/neo4j-server/cypher_relationship.rb +82 -0
- data/lib/neo4j-server/cypher_response.rb +113 -0
- data/lib/neo4j-server/cypher_session.rb +156 -0
- data/lib/neo4j-server/cypher_transaction.rb +81 -0
- data/lib/neo4j-server/resource.rb +73 -0
- data/lib/neo4j/entity_equality.rb +9 -0
- data/lib/neo4j/jars/concurrentlinkedhashmap-lru-1.3.1.jar +0 -0
- data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j/jars/lucene-core-3.6.2.jar +0 -0
- data/lib/neo4j/jars/neo4j-cypher-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-2.0-SNAPSHOT-tests.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/neo4j-lucene-index-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/neo4j-management-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/org.apache.servicemix.bundles.jline-0.9.94_1.jar +0 -0
- data/lib/neo4j/jars/parboiled-core-1.1.6.jar +0 -0
- data/lib/neo4j/jars/parboiled-scala_2.10-1.1.6.jar +0 -0
- data/lib/neo4j/jars/scala-library-2.10.2.jar +0 -0
- data/lib/neo4j/label.rb +88 -0
- data/lib/neo4j/node.rb +185 -0
- data/lib/neo4j/property_container.rb +22 -0
- data/lib/neo4j/property_validator.rb +23 -0
- data/lib/neo4j/relationship.rb +84 -0
- data/lib/neo4j/session.rb +124 -0
- data/lib/neo4j/tasks/neo4j_server.rb +131 -0
- data/lib/neo4j/transaction.rb +52 -0
- data/neo4j-core.gemspec +35 -0
- 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
|
data/lib/neo4j-server.rb
ADDED
@@ -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,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
|