neo4j-core 3.0.0.alpha.1
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 +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
|