neo4j 1.0.0.beta.13 → 1.0.0.beta.14
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.
- data/Gemfile +9 -0
- data/README.rdoc +92 -28
- data/lib/neo4j.rb +9 -3
- data/lib/neo4j/database.rb +12 -2
- data/lib/neo4j/equal.rb +0 -4
- data/lib/neo4j/event_handler.rb +9 -9
- data/lib/neo4j/index/class_methods.rb +61 -0
- data/lib/neo4j/index/index.rb +33 -0
- data/lib/neo4j/index/index_registry.rb +67 -0
- data/lib/neo4j/index/indexer.rb +193 -0
- data/lib/neo4j/index/wrapped_query.rb +59 -0
- data/lib/neo4j/load.rb +6 -3
- data/lib/neo4j/mapping/class_methods/relationship.rb +7 -13
- data/lib/neo4j/mapping/class_methods/rule.rb +6 -7
- data/lib/neo4j/mapping/decl_relationship_dsl.rb +24 -12
- data/lib/neo4j/mapping/has_n.rb +1 -11
- data/lib/neo4j/mapping/node_mixin.rb +2 -2
- data/lib/neo4j/neo4j.rb +5 -0
- data/lib/neo4j/node.rb +54 -5
- data/lib/neo4j/node_relationship.rb +92 -3
- data/lib/neo4j/property.rb +19 -3
- data/lib/neo4j/rails/model.rb +0 -1
- data/lib/neo4j/rails/value.rb +2 -2
- data/lib/neo4j/relationship.rb +16 -0
- data/lib/neo4j/relationship_traverser.rb +10 -0
- data/lib/neo4j/to_java.rb +15 -1
- data/lib/neo4j/version.rb +1 -1
- data/neo4j.gemspec +8 -8
- metadata +13 -51
- data/lib/neo4j/index.rb +0 -248
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -1,40 +1,104 @@
|
|
1
|
-
|
1
|
+
== Welcome to Neo4j.rb
|
2
2
|
|
3
3
|
Neo4j.rb is a graph database for JRuby.
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
It provides:
|
8
|
-
* Mapping of ruby objects to nodes in networks rather than in tables.
|
9
|
-
* Dynamic, and schema-free - no need to declare nodes/properties/relationships in advance.
|
10
|
-
* Storage of ruby object to a file system.
|
11
|
-
* Fast traversal of relationships between nodes in a huge node space.
|
12
|
-
* Transaction with rollbacks support.
|
13
|
-
* Indexing and querying of ruby objects.
|
14
|
-
* Migration and BatchInserter support
|
15
|
-
* Can be used instead of ActiveRecord in Ruby on Rails or Merb
|
16
|
-
* Can be accessible as REST resources.
|
5
|
+
You can think of \Neo4j as a high-performance graph engine with all the features of a mature and robust database.
|
6
|
+
The programmer works with an object-oriented, flexible network structure rather than with strict and static tables — yet enjoys all the benefits of a fully transactional, enterprise-strength database.
|
17
7
|
|
18
8
|
It uses two powerful and mature Java libraries:
|
19
|
-
* Neo4J
|
20
|
-
* Lucene
|
9
|
+
* {Neo4J}[http://www.neo4j.org] - for persistence and traversal of the graph
|
10
|
+
* {Lucene}[http://lucene.apache.org/java/docs/index.html] for querying and indexing.
|
11
|
+
|
12
|
+
=== Why Neo4j.rb or a Graph Database ?
|
13
|
+
|
14
|
+
Here are some of the major benefits of Neo4j.rb
|
15
|
+
|
16
|
+
* Domain Modeling - use the language of a graph (nodes/relationship/properties) to express your domain !
|
17
|
+
* Schema Less and Efficient storage of Semi Structured Information
|
18
|
+
* No {O/R mismatch}[http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch] - very natural to map a graph to an Object Oriented language like Ruby.
|
19
|
+
* {Performance}[http://www.oscon.com/oscon2009/public/schedule/detail/8364]
|
20
|
+
* Embedded Database - no database tier, easier to install, test, deploy and configure. It is run in the same process as your application.
|
21
|
+
* Express Queries as Traversals
|
22
|
+
* Fast deep traversal instead of slow SQL queries that span many table joins.
|
23
|
+
* Very natural to express graph related problem with traversals (recommendation engine, find shortest parth etc..)
|
24
|
+
* Seamless integration with Ruby on Rails.
|
25
|
+
* ACID Transaction with rollbacks support.
|
21
26
|
|
22
27
|
=== Documentation
|
23
|
-
* Wiki: http://github.com/andreasronge/neo4j/wiki
|
24
|
-
* API Documentation - http://neo4j.rubyforge.org/ (of the released version)
|
25
28
|
|
26
|
-
|
27
|
-
*
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
* {Documentation and Getting Started with Neo4j}[http://github.com/andreasronge/neo4j/wiki]
|
30
|
+
* {API Documentation (released version)}[http://neo4j.rubyforge.org]
|
31
|
+
|
32
|
+
=== Some Examples
|
33
|
+
|
34
|
+
==== Neo4j::Node
|
35
|
+
|
36
|
+
Example of creating a Neo4j::Node
|
37
|
+
|
38
|
+
require "rubygems"
|
39
|
+
require 'neo4j'
|
40
|
+
|
41
|
+
Neo4j::Transaction.run do
|
42
|
+
node = Neo4j::Node.new :name => 'andreas'
|
43
|
+
node.outgoing(:friends) << Neo4j::Node.new :name => 'peter'
|
44
|
+
node.outgoing(:friends).each {|node| puts "name #{node[:name]}"}
|
45
|
+
end
|
46
|
+
|
47
|
+
==== Neo4j::Mapping::NodeMixin
|
48
|
+
|
49
|
+
Example of mapping a ruby class to a node and delaring properties and relationships and lucene index.
|
50
|
+
|
51
|
+
class Person
|
52
|
+
include Neo4j::NodeMixin
|
53
|
+
property :name
|
54
|
+
property :city
|
55
|
+
|
56
|
+
has_n :friends
|
57
|
+
has_one :address
|
58
|
+
index :name
|
59
|
+
end
|
60
|
+
|
61
|
+
# assume we have an transaction already running
|
62
|
+
andreas = Person.new :name => 'andreas'
|
63
|
+
andreas.friends << Person.new :name => 'peter'
|
64
|
+
andreas.friends.each {|person| puts "name #{person.name}" }
|
65
|
+
Person.find("name: andreas").first.name # => 'andreas'
|
66
|
+
|
67
|
+
==== Neo4j::Rails::Model
|
68
|
+
|
69
|
+
Example of using Neo4j with Rails 3 (ActiveModel)
|
70
|
+
|
71
|
+
class User < Neo4j::Model
|
72
|
+
attr_accessor :password
|
73
|
+
attr_accessible :email, :password, :password_confirmation, :pending_account
|
74
|
+
|
75
|
+
after_save :encrypt_password
|
76
|
+
|
77
|
+
email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
78
|
+
|
79
|
+
validates :email, :presence => true,:format => { :with => email_regex }
|
80
|
+
validates :email, :uniqueness => true, :unless => :pending_account?
|
81
|
+
accepts_nested_attributes_for :avatar, :allow_destroy => true
|
82
|
+
|
83
|
+
property :email
|
84
|
+
index :email
|
85
|
+
|
86
|
+
has_one(:avatar).to(Avator)
|
87
|
+
|
88
|
+
...
|
89
|
+
end
|
33
90
|
|
34
91
|
=== Presentation Materials and other URLs
|
35
|
-
*
|
36
|
-
* Nordic Ruby 2010
|
37
|
-
* Neo4j wiki
|
92
|
+
* {Intoduction to Neo4j.rb}[http://www.slideshare.net/andreasronge/neo4jrb]
|
93
|
+
* {Nordic Ruby 2010 May 21-23}[http://nordicruby.org/speakers#user_29]
|
94
|
+
* {Neo4j wiki, check the guidelines and domain modeling gallery pages}[http://wiki.neo4j.org/content/Main_Page]
|
95
|
+
|
96
|
+
=== Project information
|
97
|
+
* {GitHub}[http://github.com/andreasronge/neo4j/tree/master]
|
98
|
+
* {Lighthouse Issue Tracking}[http://neo4j.lighthouseapp.com]
|
99
|
+
* {Twitter}[http://twitter.com/ronge]
|
100
|
+
* IRC - #neo4j @ irc.freenode.net
|
101
|
+
* {Mailing list, neo4jrb@googlegroups.com}[http://groups.google.com/group/neo4jrb]
|
38
102
|
|
39
103
|
=== Contributing
|
40
104
|
|
@@ -46,4 +110,4 @@ Please also check/add issues at lighthouse, http://neo4j.lighthouseapp.com
|
|
46
110
|
=== License
|
47
111
|
* Neo4j.rb - MIT, see the LICENSE file http://github.com/andreasronge/neo4j/tree/master/LICENSE.
|
48
112
|
* Lucene - Apache, see http://lucene.apache.org/java/docs/features.html
|
49
|
-
* Neo4j - Dual free software/commercial license, see http://neo4j.org/
|
113
|
+
* \Neo4j - Dual free software/commercial license, see http://neo4j.org/
|
data/lib/neo4j.rb
CHANGED
@@ -12,10 +12,17 @@ require 'neo4j/jars/neo4j-lucene-index-0.1-20101002.153213-102.jar'
|
|
12
12
|
require 'neo4j/to_java'
|
13
13
|
require 'neo4j/version'
|
14
14
|
require 'neo4j/equal'
|
15
|
-
|
16
|
-
require 'neo4j/relationship_traverser'
|
15
|
+
|
17
16
|
require 'neo4j/event_handler'
|
18
17
|
require 'neo4j/database'
|
18
|
+
require 'neo4j/neo4j'
|
19
|
+
|
20
|
+
require 'neo4j/index/index'
|
21
|
+
require 'neo4j/index/class_methods'
|
22
|
+
require 'neo4j/index/index_registry'
|
23
|
+
require 'neo4j/index/indexer'
|
24
|
+
require 'neo4j/index/wrapped_query'
|
25
|
+
require 'neo4j/relationship_traverser'
|
19
26
|
require 'neo4j/node_traverser'
|
20
27
|
require 'neo4j/property'
|
21
28
|
require 'neo4j/transaction'
|
@@ -24,7 +31,6 @@ require 'neo4j/load'
|
|
24
31
|
require 'neo4j/relationship'
|
25
32
|
require 'neo4j/node'
|
26
33
|
require 'neo4j/config'
|
27
|
-
require 'neo4j/neo4j'
|
28
34
|
require 'neo4j/mapping/class_methods/init_node'
|
29
35
|
require 'neo4j/mapping/class_methods/init_rel'
|
30
36
|
require 'neo4j/mapping/class_methods/root'
|
data/lib/neo4j/database.rb
CHANGED
@@ -20,8 +20,11 @@ module Neo4j
|
|
20
20
|
if @running
|
21
21
|
@graph.unregister_transaction_event_handler(@event_handler)
|
22
22
|
@event_handler.neo4j_shutdown(self)
|
23
|
+
@graph.shutdown
|
24
|
+
@graph = nil
|
25
|
+
@lucene = nil
|
23
26
|
end
|
24
|
-
|
27
|
+
|
25
28
|
@running = false
|
26
29
|
end
|
27
30
|
|
@@ -37,7 +40,14 @@ module Neo4j
|
|
37
40
|
def each_node
|
38
41
|
iter = @graph.all_nodes.iterator
|
39
42
|
while (iter.hasNext)
|
40
|
-
yield
|
43
|
+
yield iter.next.wrapper
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def _each_node
|
48
|
+
iter = @graph.all_nodes.iterator
|
49
|
+
while (iter.hasNext)
|
50
|
+
yield iter.next
|
41
51
|
end
|
42
52
|
end
|
43
53
|
|
data/lib/neo4j/equal.rb
CHANGED
data/lib/neo4j/event_handler.rb
CHANGED
@@ -19,9 +19,9 @@ module Neo4j
|
|
19
19
|
data.created_nodes.each{|node| node_created(node)}
|
20
20
|
data.assigned_node_properties.each { |tx_data| property_changed(tx_data.entity, tx_data.key, tx_data.previously_commited_value, tx_data.value) }
|
21
21
|
data.removed_node_properties.each { |tx_data| property_changed(tx_data.entity, tx_data.key, tx_data.previously_commited_value, nil) unless data.deleted_nodes.include?(tx_data.entity) }
|
22
|
-
data.deleted_nodes.each { |node| node_deleted(node, deleted_properties_for(node,data))}
|
23
|
-
data.created_relationships.each {|rel| relationship_created(rel)}
|
24
|
-
data.deleted_relationships.each {|rel| relationship_deleted(rel, deleted_rel_properties_for(rel, data))}
|
22
|
+
data.deleted_nodes.each { |node| node_deleted(node, deleted_properties_for(node,data), data)}
|
23
|
+
data.created_relationships.each {|rel| relationship_created(rel, data)}
|
24
|
+
data.deleted_relationships.each {|rel| relationship_deleted(rel, deleted_rel_properties_for(rel, data), data)}
|
25
25
|
data.assigned_relationship_properties.each { |tx_data| rel_property_changed(tx_data.entity, tx_data.key, tx_data.previously_commited_value, tx_data.value) }
|
26
26
|
data.removed_relationship_properties.each {|tx_data| rel_property_changed(tx_data.entity, tx_data.key, tx_data.previously_commited_value, nil) unless data.deleted_relationships.include?(tx_data.entity) }
|
27
27
|
end
|
@@ -69,16 +69,16 @@ module Neo4j
|
|
69
69
|
@listeners.each {|li| li.on_node_created(node) if li.respond_to?(:on_node_created)}
|
70
70
|
end
|
71
71
|
|
72
|
-
def node_deleted(node,old_properties)
|
73
|
-
@listeners.each {|li| li.on_node_deleted(node,old_properties) if li.respond_to?(:on_node_deleted)}
|
72
|
+
def node_deleted(node,old_properties, data)
|
73
|
+
@listeners.each {|li| li.on_node_deleted(node,old_properties, data) if li.respond_to?(:on_node_deleted)}
|
74
74
|
end
|
75
75
|
|
76
|
-
def relationship_created(relationship)
|
77
|
-
@listeners.each {|li| li.on_relationship_created(relationship) if li.respond_to?(:on_relationship_created)}
|
76
|
+
def relationship_created(relationship, tx_data)
|
77
|
+
@listeners.each {|li| li.on_relationship_created(relationship, tx_data) if li.respond_to?(:on_relationship_created)}
|
78
78
|
end
|
79
79
|
|
80
|
-
def relationship_deleted(relationship, old_props)
|
81
|
-
@listeners.each {|li| li.on_relationship_deleted(relationship, old_props) if li.respond_to?(:on_relationship_deleted)}
|
80
|
+
def relationship_deleted(relationship, old_props, data)
|
81
|
+
@listeners.each {|li| li.on_relationship_deleted(relationship, old_props, data) if li.respond_to?(:on_relationship_deleted)}
|
82
82
|
end
|
83
83
|
|
84
84
|
def property_changed(node, key, old_value, new_value)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Index
|
3
|
+
module ClassMethods
|
4
|
+
attr_reader :_indexer
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
##
|
9
|
+
# See Neo4j::Index::Indexer#index
|
10
|
+
# Forwards to the indexer that should be used.
|
11
|
+
# It is possible to share the same index for several different classes, see #node_indexer.
|
12
|
+
# :singleton-method: index
|
13
|
+
|
14
|
+
##
|
15
|
+
# See Neo4j::Index::Indexer#find
|
16
|
+
# Forwards to the indexer that should be used.
|
17
|
+
# It is possible to share the same index for several different classes, see #node_indexer.
|
18
|
+
# :singleton-method: find
|
19
|
+
|
20
|
+
|
21
|
+
def_delegators :@_indexer, :index, :find, :index?, :index_type?, :clear_index_type, :rm_index_type, :add_index, :rm_index, :index_type_for, :index_name
|
22
|
+
|
23
|
+
|
24
|
+
# Sets which indexer should be used for the given node class.
|
25
|
+
# You can share an indexer between several different classes.
|
26
|
+
#
|
27
|
+
# ==== Example
|
28
|
+
# class Contact
|
29
|
+
# include Neo4j::NodeMixin
|
30
|
+
# index :name
|
31
|
+
# has_one :phone
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class Phone
|
35
|
+
# include Neo4j::NodeMixin
|
36
|
+
# property :phone
|
37
|
+
# index :phone, :indexer => Person, :via => proc{|node| node.incoming(:phone).first}
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Find an contact with a phone number, this works since they share the same index
|
41
|
+
# Contact.find('phone: 12345 AND name: 'pelle'')
|
42
|
+
#
|
43
|
+
# ==== Returns
|
44
|
+
# The indexer that should be used to index the given class
|
45
|
+
def node_indexer(clazz)
|
46
|
+
indexer(clazz, :node)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sets which indexer should be used for the given relationship class
|
50
|
+
# Same as #node_indexer except that it indexes relationships instead of nodes.
|
51
|
+
#
|
52
|
+
def rel_indexer(clazz)
|
53
|
+
indexer(clazz, :rel)
|
54
|
+
end
|
55
|
+
|
56
|
+
def indexer(clazz, type) #:nodoc:
|
57
|
+
@_indexer = IndexerRegistry.create_for(self, clazz, type)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Neo4j
|
2
|
+
|
3
|
+
module Index
|
4
|
+
|
5
|
+
# Adds an index on the given property
|
6
|
+
# Notice that you normally don't have to do that since you simply can declare
|
7
|
+
# that the property and index should be updated automatically by using the class method #index.
|
8
|
+
#
|
9
|
+
# The index operation will take place immediately unlike when using the Neo4j::Index::ClassMethods::index
|
10
|
+
# method which instead will guarantee that the neo4j database and the lucene database will be consistent.
|
11
|
+
# It uses a two phase commit when the transaction is about to be committed.
|
12
|
+
#
|
13
|
+
# ==== See also
|
14
|
+
# Neo4j::Index::ClassMethods::index
|
15
|
+
#
|
16
|
+
def add_index(field, value=self[field])
|
17
|
+
self.class.add_index(wrapped_entity, field.to_s, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Removes an index on the given property.
|
21
|
+
# Just like #add_index this is normally not needed since you instead can declare it with the
|
22
|
+
# #index class method instead.
|
23
|
+
#
|
24
|
+
# ==== See also
|
25
|
+
# Neo4j::Index::ClassMethods::index
|
26
|
+
# Neo4j::Index#add_index
|
27
|
+
#
|
28
|
+
def rm_index(field, value=self[field])
|
29
|
+
self.class.rm_index(wrapped_entity, field.to_s, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Index
|
3
|
+
class IndexerRegistry #:nodoc:
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def clear_all_indexes
|
7
|
+
@@indexers.values.each {|i| i.clear_index_type}
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_for(this_clazz, using_other_clazz, type)
|
11
|
+
@@indexers ||= {}
|
12
|
+
@@indexers[this_clazz.to_s] = @@indexers[using_other_clazz.to_s] || Indexer.new(this_clazz, type)
|
13
|
+
@@indexers[this_clazz.to_s]
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_by_class(classname)
|
17
|
+
@@indexers[classname]
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_node_deleted(node, old_props, tx_data)
|
21
|
+
indexer = find_by_class(old_props['_classname'] || node.class.to_s)
|
22
|
+
indexer && indexer.remove_index_on_fields(node, old_props, tx_data)
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_property_changed(node, field, old_val, new_val)
|
26
|
+
classname = node['_classname'] || node.class.to_s
|
27
|
+
indexer = find_by_class(classname)
|
28
|
+
indexer && indexer.update_index_on(node, field, old_val, new_val)
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_rel_property_changed(rel, field, old_val, new_val)
|
32
|
+
# works exactly like for nodes
|
33
|
+
on_property_changed(rel, field, old_val, new_val)
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_relationship_created(rel, tx_data)
|
37
|
+
end_node = rel._end_node
|
38
|
+
# if end_node was created in this transaction then it will be handled in on_property_changed
|
39
|
+
created = tx_data.created_nodes.find{|n| n.neo_id == end_node.neo_id}
|
40
|
+
unless created
|
41
|
+
indexer = find_by_class(end_node['_classname'])
|
42
|
+
indexer && indexer.update_on_new_relationship(rel)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_relationship_deleted(rel, old_props, tx_data)
|
47
|
+
on_node_deleted(rel, old_props, tx_data)
|
48
|
+
# if only the relationship has been deleted then we have to remove the index
|
49
|
+
# if both the relationship and the node has been deleted then the index will be removed in the
|
50
|
+
# on_node_deleted callback
|
51
|
+
end_node = rel._end_node
|
52
|
+
deleted = tx_data.deleted_nodes.find{|n| n.neo_id == end_node.neo_id}
|
53
|
+
unless deleted
|
54
|
+
indexer = find_by_class(end_node['_classname'])
|
55
|
+
indexer && indexer.update_on_deleted_relationship(rel)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_neo4j_shutdown(*)
|
60
|
+
@@indexers.each_value {|indexer| indexer.on_neo4j_shutdown}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
Neo4j.unstarted_db.event_handler.add(IndexerRegistry)
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Index
|
3
|
+
class Indexer #:nodoc:
|
4
|
+
attr_reader :indexer_for
|
5
|
+
|
6
|
+
def initialize(clazz, type)
|
7
|
+
# part of the unique name of the index
|
8
|
+
@indexer_for = clazz
|
9
|
+
|
10
|
+
# do we want to index nodes or relationships ?
|
11
|
+
@type = type
|
12
|
+
|
13
|
+
@indexes = {} # key = type, value = java neo4j index
|
14
|
+
@field_types = {} # key = field, value = type (e.g. :exact or :fulltext)
|
15
|
+
@via_relationships = {} # key = field, value = relationship
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"Indexer @#{object_id} [index_for:#{@indexer_for}, field_types=#{@field_types.keys.join(', ')}, via=#{@via_relationships.inspect}]"
|
20
|
+
end
|
21
|
+
|
22
|
+
# add an index on a field so that it will be automatically updated by neo4j events.
|
23
|
+
def index(field, conf = {})
|
24
|
+
if conf[:via]
|
25
|
+
rel_dsl = @indexer_for._decl_rels[conf[:via]]
|
26
|
+
raise "No relationship defined for '#{conf[:via]}'. Check class '#{@indexer_for}': index :#{field}, via=>:#{conf[:via]} <-- error. Define it with a has_one or has_n" unless rel_dsl
|
27
|
+
raise "Only incoming relationship are possible to define index on. Check class '#{@indexer_for}': index :#{field}, via=>:#{conf[:via]}" unless rel_dsl.incoming?
|
28
|
+
via_indexer = rel_dsl.to_class._indexer
|
29
|
+
|
30
|
+
field = field.to_s
|
31
|
+
@via_relationships[field] = rel_dsl
|
32
|
+
conf.delete :via # avoid endless recursion
|
33
|
+
via_indexer.index(field, conf)
|
34
|
+
else
|
35
|
+
raise "Already defined an (via?) index on #{field}, Using the same index for from two classes ? Check index :#{field}, :via => :#{@indexer_for}" if @field_types[field.to_s]
|
36
|
+
@field_types[field.to_s] = conf[:type] || :exact
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def remove_index_on_fields(node, props, tx_data)
|
41
|
+
@field_types.keys.each { |field| rm_index(node, field, props[field]) if props[field] }
|
42
|
+
# remove all via indexed fields
|
43
|
+
@via_relationships.each_value do |dsl|
|
44
|
+
indexer = dsl.to_class._indexer
|
45
|
+
tx_data.deleted_relationships.each do |rel|
|
46
|
+
start_node = rel._start_node
|
47
|
+
next if node != rel._end_node
|
48
|
+
indexer.remove_index_on_fields(start_node, props, tx_data)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_on_deleted_relationship(relationship)
|
54
|
+
update_on_relationship(relationship, false)
|
55
|
+
end
|
56
|
+
|
57
|
+
def update_on_new_relationship(relationship)
|
58
|
+
update_on_relationship(relationship, true)
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_on_relationship(relationship, is_created)
|
62
|
+
rel_type = relationship.rel_type
|
63
|
+
end_node = relationship._end_node
|
64
|
+
# find which via relationship match rel_type
|
65
|
+
@via_relationships.each_pair do |field, dsl|
|
66
|
+
# have we declared an index on this changed relationship ?
|
67
|
+
next unless dsl.incoming_dsl.namespace_type == rel_type
|
68
|
+
|
69
|
+
# yes, so find the node and value we should update the index on
|
70
|
+
val = end_node[field]
|
71
|
+
start_node = relationship._start_node
|
72
|
+
|
73
|
+
# find the indexer to use
|
74
|
+
indexer = dsl.to_class._indexer
|
75
|
+
|
76
|
+
# is the relationship created or deleted ?
|
77
|
+
if is_created
|
78
|
+
indexer.update_index_on(start_node, field, nil, val)
|
79
|
+
else
|
80
|
+
indexer.update_index_on(start_node, field, val, nil)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def update_index_on(node, field, old_val, new_val)
|
86
|
+
if @via_relationships.include?(field)
|
87
|
+
dsl = @via_relationships[field]
|
88
|
+
to_class = dsl.to_class
|
89
|
+
|
90
|
+
dsl.all_relationships(node).each do |rel|
|
91
|
+
other = rel._start_node
|
92
|
+
to_class._indexer.update_index_on(other, field, old_val, new_val)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
if @field_types.include?(field)
|
97
|
+
rm_index(node, field, old_val) if old_val
|
98
|
+
add_index(node, field, new_val) if new_val
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def index?(field)
|
103
|
+
@field_types.include?(field.to_s)
|
104
|
+
end
|
105
|
+
|
106
|
+
def index_type_for(field)
|
107
|
+
return nil unless index?(field)
|
108
|
+
@field_types[field.to_s]
|
109
|
+
end
|
110
|
+
|
111
|
+
def index_type?(type)
|
112
|
+
@field_types.values.include?(type)
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_index(entity, field, value)
|
116
|
+
index_for_field(field.to_s).add(entity, field, value)
|
117
|
+
end
|
118
|
+
|
119
|
+
def rm_index(entity, field, value)
|
120
|
+
index_for_field(field).remove(entity, field, value)
|
121
|
+
end
|
122
|
+
|
123
|
+
def find(query, params = {})
|
124
|
+
type = params[:type] || :exact
|
125
|
+
index = index_for_type(type)
|
126
|
+
query = (params[:wrapped].nil? || params[:wrapped]) ? WrappedQuery.new(index, query) : index.query(query)
|
127
|
+
|
128
|
+
if block_given?
|
129
|
+
begin
|
130
|
+
ret = yield query
|
131
|
+
ensure
|
132
|
+
query.close
|
133
|
+
end
|
134
|
+
ret
|
135
|
+
else
|
136
|
+
query
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# clears the index, if no type is provided clear all types of indexes
|
141
|
+
def clear_index_type(type=nil)
|
142
|
+
if type
|
143
|
+
#raise "can't clear index of type '#{type}' since it does not exist ([#{@field_types.values.join(',')}] exists)" unless index_type?(type)
|
144
|
+
@indexes[type] && @indexes[type].clear
|
145
|
+
else
|
146
|
+
@indexes.each_value { |index| index.clear }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def on_neo4j_shutdown
|
151
|
+
# Since we might start the database again we must make sure that we don't keep any references to
|
152
|
+
# an old lucene index service.
|
153
|
+
@indexes.clear
|
154
|
+
end
|
155
|
+
|
156
|
+
def rm_index_type(type=nil)
|
157
|
+
if type
|
158
|
+
@field_types.delete_if { |k, v| v == type }
|
159
|
+
else
|
160
|
+
@field_types.clear
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def index_for_field(field)
|
165
|
+
type = @field_types[field]
|
166
|
+
@indexes[type] ||= create_index_with(type)
|
167
|
+
end
|
168
|
+
|
169
|
+
def index_for_type(type)
|
170
|
+
@indexes[type] ||= create_index_with(type)
|
171
|
+
end
|
172
|
+
|
173
|
+
def lucene_config(type)
|
174
|
+
conf = Neo4j::Config[:lucene][type.to_sym]
|
175
|
+
raise "unknown lucene type #{type}" unless conf
|
176
|
+
conf
|
177
|
+
end
|
178
|
+
|
179
|
+
def create_index_with(type)
|
180
|
+
db=Neo4j.started_db
|
181
|
+
index_config = lucene_config(type)
|
182
|
+
if @type == :node
|
183
|
+
db.lucene.node_index("#{@indexer_for}-#{type}", index_config)
|
184
|
+
else
|
185
|
+
db.lucene.relationship_index("#{@indexer_for}-#{type}", index_config)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|