neo4j-core 2.0.0.alpha.1-java → 2.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/Gemfile +2 -2
  2. data/README.rdoc +161 -13
  3. data/config/neo4j/config.yml +1 -1
  4. data/lib/db/active_tx_log +1 -0
  5. data/lib/db/index/lucene-store.db +0 -0
  6. data/lib/db/index/lucene.log.active +0 -0
  7. data/lib/db/messages.log +2299 -0
  8. data/lib/db/neostore +0 -0
  9. data/lib/db/neostore.id +0 -0
  10. data/lib/db/neostore.nodestore.db +0 -0
  11. data/lib/db/neostore.nodestore.db.id +0 -0
  12. data/lib/db/neostore.propertystore.db +0 -0
  13. data/lib/db/neostore.propertystore.db.arrays +0 -0
  14. data/lib/db/neostore.propertystore.db.arrays.id +0 -0
  15. data/lib/db/neostore.propertystore.db.id +0 -0
  16. data/lib/db/neostore.propertystore.db.index +0 -0
  17. data/lib/db/neostore.propertystore.db.index.id +0 -0
  18. data/lib/db/neostore.propertystore.db.index.keys +0 -0
  19. data/lib/db/neostore.propertystore.db.index.keys.id +0 -0
  20. data/lib/db/neostore.propertystore.db.strings +0 -0
  21. data/lib/db/neostore.propertystore.db.strings.id +0 -0
  22. data/lib/db/neostore.relationshipstore.db +0 -0
  23. data/lib/db/neostore.relationshipstore.db.id +0 -0
  24. data/lib/db/neostore.relationshiptypestore.db +0 -0
  25. data/lib/db/neostore.relationshiptypestore.db.id +0 -0
  26. data/lib/db/neostore.relationshiptypestore.db.names +0 -0
  27. data/lib/db/neostore.relationshiptypestore.db.names.id +0 -0
  28. data/lib/db/nioneo_logical.log.active +0 -0
  29. data/lib/db/tm_tx_log.1 +0 -0
  30. data/lib/neo4j-core.rb +20 -3
  31. data/lib/neo4j-core/cypher/cypher.rb +1033 -0
  32. data/lib/neo4j-core/cypher/result_wrapper.rb +48 -0
  33. data/lib/neo4j-core/database.rb +4 -5
  34. data/lib/neo4j-core/event_handler.rb +1 -1
  35. data/lib/neo4j-core/hash_with_indifferent_access.rb +165 -0
  36. data/lib/neo4j-core/index/class_methods.rb +27 -41
  37. data/lib/neo4j-core/index/index.rb +3 -4
  38. data/lib/neo4j-core/index/index_config.rb +30 -23
  39. data/lib/neo4j-core/index/indexer.rb +65 -53
  40. data/lib/neo4j-core/index/indexer_registry.rb +2 -2
  41. data/lib/neo4j-core/index/lucene_query.rb +53 -42
  42. data/lib/neo4j-core/index/unique_factory.rb +54 -0
  43. data/lib/neo4j-core/node/class_methods.rb +4 -4
  44. data/lib/neo4j-core/node/node.rb +1 -8
  45. data/lib/neo4j-core/property/java.rb +59 -0
  46. data/lib/neo4j-core/property/property.rb +1 -3
  47. data/lib/neo4j-core/relationship/relationship.rb +8 -10
  48. data/lib/neo4j-core/rels/rels.rb +9 -4
  49. data/lib/neo4j-core/rels/traverser.rb +13 -27
  50. data/lib/neo4j-core/traversal/prune_evaluator.rb +2 -2
  51. data/lib/neo4j-core/traversal/traverser.rb +122 -27
  52. data/lib/neo4j-core/version.rb +1 -1
  53. data/lib/neo4j-core/wrapper/class_methods.rb +22 -0
  54. data/lib/neo4j-core/wrapper/wrapper.rb +20 -0
  55. data/lib/neo4j/algo.rb +300 -0
  56. data/lib/neo4j/config.rb +3 -6
  57. data/lib/neo4j/cypher.rb +180 -0
  58. data/lib/neo4j/neo4j.rb +51 -23
  59. data/lib/neo4j/node.rb +27 -0
  60. data/lib/neo4j/relationship.rb +25 -0
  61. data/lib/test.rb~ +27 -0
  62. data/neo4j-core.gemspec +2 -2
  63. metadata +44 -11
  64. data/lib/neo4j-core/type_converters/type_converters.rb +0 -287
  65. data/lib/neo4j-core/version.rb~ +0 -3
  66. data/lib/test.rb +0 -27
@@ -0,0 +1,48 @@
1
+ module Neo4j
2
+ module Core
3
+ module Cypher
4
+ # Wraps the Cypher query result.
5
+ # Loads the node and relationships wrapper if possible and use symbol as column keys.
6
+ # @notice The result is a once forward read only Enumerable, work if you need to read the result twice - use #to_a
7
+ #
8
+ # @example
9
+ # result = Neo4j.query(@a, @b){|a,b| node(a,b).as(:n)}
10
+ # r = @query_result.to_a # can only loop once
11
+ # r.size.should == 2
12
+ # r.first.should include(:n)
13
+ # r[0][:n].neo_id.should == @a.neo_id
14
+ # r[1][:n].neo_id.should == @b.neo_id
15
+ class ResultWrapper
16
+ include Enumerable
17
+
18
+ # @return the original result from the Neo4j Cypher Engine, once forward read only !
19
+ attr_reader :source
20
+
21
+ def initialize(source)
22
+ @source = source
23
+ end
24
+
25
+ # @return [Array<Symbol>] the columns in the query result
26
+ def columns
27
+ @source.columns.map{|x| x.to_sym}
28
+ end
29
+
30
+ # for the Enumerable contract
31
+ def each
32
+ @source.each { |row| yield map(row) }
33
+ end
34
+
35
+ # Maps each row so that we can use symbols for column names.
36
+ # @private
37
+ def map(row)
38
+ out = {} # move to a real hash!
39
+ row.each do |key, value|
40
+ out[key.to_sym] = value.respond_to?(:wrapper) ? value.wrapper : value
41
+ end
42
+ out
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -53,10 +53,10 @@ module Neo4j
53
53
  start_readonly_graph_db
54
54
  elsif Neo4j::Config['ha.db']
55
55
  start_ha_graph_db
56
- Neo4j.migrate!
56
+ Neo4j.migrate! if Neo4j.respond_to?(:migrate!)
57
57
  else
58
58
  start_local_graph_db
59
- Neo4j.migrate!
59
+ Neo4j.migrate! if Neo4j.respond_to?(:migrate!)
60
60
  end
61
61
  rescue
62
62
  @running = false
@@ -67,7 +67,8 @@ module Neo4j
67
67
  end
68
68
 
69
69
 
70
- def running? #:nodoc:
70
+ # true if the database has started
71
+ def running?
71
72
  @running
72
73
  end
73
74
 
@@ -163,8 +164,6 @@ module Neo4j
163
164
  Neo4j.logger.info "starting Neo4j in HA mode, machine id: #{Neo4j.config['ha.server_id']} at #{Neo4j.config['ha.server']} db #{@storage_path}"
164
165
  # Modify the public base classes for the HA Node and Relationships
165
166
  # (instead of private Java::OrgNeo4jKernel::HighlyAvailableGraphDatabase::LookupNode)
166
- Neo4j::Node.extend_java_class(Java::OrgNeo4jToolingWrap::WrappedNode)
167
- Neo4j::Relationship.extend_java_class(Java::OrgNeo4jToolingWrap::WrappedRelationship)
168
167
  @graph = Java::OrgNeo4jKernel::HighlyAvailableGraphDatabase.new(@storage_path, Neo4j.config.to_java_map)
169
168
  @graph.register_transaction_event_handler(@event_handler)
170
169
  @lucene = @graph.index
@@ -88,7 +88,7 @@ module Neo4j
88
88
  # You only need to implement the methods that you need.
89
89
  #
90
90
  class EventHandler
91
- include org.neo4j.graphdb.event.TransactionEventHandler
91
+ include Java::OrgNeo4jGraphdbEvent::TransactionEventHandler
92
92
 
93
93
  def initialize
94
94
  @listeners = []
@@ -0,0 +1,165 @@
1
+ module Neo4j
2
+ module Core
3
+ # Stolen from http://as.rubyonrails.org/classes/HashWithIndifferentAccess.html
4
+ # We don't want to depend on active support
5
+ class HashWithIndifferentAccess < Hash
6
+
7
+ # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
8
+ def extractable_options?
9
+ true
10
+ end
11
+
12
+ def with_indifferent_access
13
+ dup
14
+ end
15
+
16
+ def nested_under_indifferent_access
17
+ self
18
+ end
19
+
20
+ def initialize(constructor = {})
21
+ if constructor.is_a?(Hash)
22
+ super()
23
+ update(constructor)
24
+ else
25
+ super(constructor)
26
+ end
27
+ end
28
+
29
+ def default(key = nil)
30
+ if key.is_a?(Symbol) && include?(key = key.to_s)
31
+ self[key]
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def self.new_from_hash_copying_default(hash)
38
+ new(hash).tap do |new_hash|
39
+ new_hash.default = hash.default
40
+ end
41
+ end
42
+
43
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
44
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
45
+
46
+ # Assigns a new value to the hash:
47
+ #
48
+ # hash = HashWithIndifferentAccess.new
49
+ # hash[:key] = "value"
50
+ #
51
+ def []=(key, value)
52
+ regular_writer(convert_key(key), convert_value(value))
53
+ end
54
+
55
+ alias_method :store, :[]=
56
+
57
+ # Updates the instantized hash with values from the second:
58
+ #
59
+ # hash_1 = HashWithIndifferentAccess.new
60
+ # hash_1[:key] = "value"
61
+ #
62
+ # hash_2 = HashWithIndifferentAccess.new
63
+ # hash_2[:key] = "New Value!"
64
+ #
65
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
66
+ #
67
+ def update(other_hash)
68
+ if other_hash.is_a? HashWithIndifferentAccess
69
+ super(other_hash)
70
+ else
71
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
72
+ self
73
+ end
74
+ end
75
+
76
+ alias_method :merge!, :update
77
+
78
+ # Checks the hash for a key matching the argument passed in:
79
+ #
80
+ # hash = HashWithIndifferentAccess.new
81
+ # hash["key"] = "value"
82
+ # hash.key? :key # => true
83
+ # hash.key? "key" # => true
84
+ #
85
+ def key?(key)
86
+ super(convert_key(key))
87
+ end
88
+
89
+ alias_method :include?, :key?
90
+ alias_method :has_key?, :key?
91
+ alias_method :member?, :key?
92
+
93
+ # Fetches the value for the specified key, same as doing hash[key]
94
+ def fetch(key, *extras)
95
+ super(convert_key(key), *extras)
96
+ end
97
+
98
+ # Returns an array of the values at the specified indices:
99
+ #
100
+ # hash = HashWithIndifferentAccess.new
101
+ # hash[:a] = "x"
102
+ # hash[:b] = "y"
103
+ # hash.values_at("a", "b") # => ["x", "y"]
104
+ #
105
+ def values_at(*indices)
106
+ indices.collect {|key| self[convert_key(key)]}
107
+ end
108
+
109
+ # Returns an exact copy of the hash.
110
+ def dup
111
+ self.class.new(self).tap do |new_hash|
112
+ new_hash.default = default
113
+ end
114
+ end
115
+
116
+ # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash.
117
+ # Does not overwrite the existing hash.
118
+ def merge(hash)
119
+ self.dup.update(hash)
120
+ end
121
+
122
+ # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
123
+ # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>.
124
+ def reverse_merge(other_hash)
125
+ super self.class.new_from_hash_copying_default(other_hash)
126
+ end
127
+
128
+ def reverse_merge!(other_hash)
129
+ replace(reverse_merge( other_hash ))
130
+ end
131
+
132
+ # Removes a specified key from the hash.
133
+ def delete(key)
134
+ super(convert_key(key))
135
+ end
136
+
137
+ def stringify_keys!; self end
138
+ def stringify_keys; dup end
139
+ # undef :symbolize_keys!
140
+ def symbolize_keys; to_hash.symbolize_keys end
141
+ def to_options!; self end
142
+
143
+ # Convert to a Hash with String keys.
144
+ def to_hash
145
+ Hash.new(default).merge!(self)
146
+ end
147
+
148
+ protected
149
+ def convert_key(key)
150
+ key.kind_of?(Symbol) ? key.to_s : key
151
+ end
152
+
153
+ def convert_value(value)
154
+ if value.is_a? Hash
155
+ value #.nested_under_indifferent_access
156
+ elsif value.is_a?(Array)
157
+ value.dup.replace(value.map { |e| convert_value(e) })
158
+ else
159
+ value
160
+ end
161
+ end
162
+ end
163
+
164
+ end
165
+ end
@@ -7,7 +7,6 @@ module Neo4j
7
7
  attr_reader :_indexer
8
8
 
9
9
 
10
- # TODO fix YARD docs update using DSL
11
10
  # Sets which indexer should be used for the given node class.
12
11
  # You can share an indexer between several different classes.
13
12
  #
@@ -20,49 +19,18 @@ module Neo4j
20
19
  # node_indexer do
21
20
  # index_names :exact => 'myindex_exact', :fulltext => 'myindex_fulltext'
22
21
  # trigger_on :ntype => 'foo', :name => ['bar', 'foobar']
23
- # # TODO multitenancy support
24
- # prefix_index_name do
25
- # return "" unless Neo4j.running?
26
- # return "" unless @indexer_for.respond_to?(:ref_node_for_class)
27
- # ref_node = @indexer_for.ref_node_for_class.wrapper
28
- # prefix = ref_node.send(:_index_prefix) if ref_node.respond_to?(:_index_prefix)
29
- # prefix ||= ref_node[:name] # To maintain backward compatiblity
30
- # prefix.blank? ? "" : prefix + "_"
31
- # end
32
- #
33
22
  #
34
- # TODO ...
35
- # numeric do
36
- # type = decl_props && decl_props[field.to_sym] && decl_props[field.to_sym][:type]
37
- # type && !type.is_a?(String)
23
+ # prefix_index_name do
24
+ # "Foo" # this is used for example in multitenancy to let each domain have there own index files
38
25
  # end
39
26
  # end
40
27
  # end
41
28
  #
42
- # @example Using Neo4j::NodeMixin
43
- #
44
- # class Contact
45
- # include Neo4j::NodeMixin
46
- # index :name
47
- # has_one :phone
48
- # end
49
- #
50
- # class Phone
51
- # include Neo4j::NodeMixin
52
- # property :phone
53
- # node_indexer Contact # put index on the Contact class instead
54
- # index :phone
55
- # end
56
- #
57
- # # Find an contact with a phone number, this works since they share the same index
58
- # Contact.find('phone: 12345').first #=> a phone object !
59
- #
29
+ # @param [Neo4j::Core::Index::IndexConfig] config the configuration used as context for the config_dsl
60
30
  # @return [Neo4j::Core::Index::Indexer] The indexer that should be used to index the given class
61
31
  # @see Neo4j::Core::Index::IndexConfig for possible configuration values in the +config_dsl+ block
62
32
  # @yield evaluated in the a Neo4j::Core::Index::IndexConfig object to configure it.
63
- def node_indexer(&config_dsl)
64
- # TODO reuse an existing index config
65
- config = IndexConfig.new(:node)
33
+ def node_indexer(config = _config || IndexConfig.new(:node), &config_dsl)
66
34
  config.instance_eval(&config_dsl)
67
35
  indexer(config)
68
36
  end
@@ -70,17 +38,32 @@ module Neo4j
70
38
  # Sets which indexer should be used for the given relationship class
71
39
  # Same as #node_indexer except that it indexes relationships instead of nodes.
72
40
  #
73
- # @see #node_indexer
74
- # @return [Neo4j::Core::Index::Indexer] The indexer that should be used to index the given class
75
- def rel_indexer(clazz)
76
- indexer(clazz, :rel)
41
+ # @param (see #node_indexer)
42
+ # @return (see #node_indexer)
43
+ def rel_indexer(config = _config || IndexConfig.new(:rel), &config_dsl)
44
+ config.instance_eval(&config_dsl)
45
+ indexer(config)
77
46
  end
78
47
 
48
+ def _config
49
+ @_indexer && @_indexer.config
50
+ end
79
51
 
80
52
  def indexer(index_config)
81
53
  @_indexer ||= IndexerRegistry.instance.register(Indexer.new(index_config))
82
54
  end
83
55
 
56
+ # You can specify which nodes should be triggered.
57
+ # The index can be triggered by one or more properties having one or more values.
58
+ # This can also be done using the #node_indexer or #rel_indexer methods.
59
+ #
60
+ # @example trigger on property :type being 'MyType1'
61
+ # Neo4j::NodeIndex.trigger_on(:type => 'MyType1')
62
+ #
63
+ def trigger_on(hash)
64
+ _config.trigger_on(hash)
65
+ end
66
+
84
67
 
85
68
  class << self
86
69
  private
@@ -93,7 +76,7 @@ module Neo4j
93
76
  def delegate(method_name)
94
77
  class_eval(<<-EOM, __FILE__, __LINE__)
95
78
  def #{method_name}(*args, &block)
96
- @_indexer.send(:#{method_name}, *args, &block)
79
+ _indexer.send(:#{method_name}, *args, &block)
97
80
  end
98
81
  EOM
99
82
  end
@@ -109,6 +92,9 @@ module Neo4j
109
92
  delegate :add_index
110
93
  delegate :rm_index
111
94
  delegate :index_type
95
+ delegate :index_name_for_type
96
+ delegate :index_for_type
97
+ delegate :put_if_absent
112
98
  end
113
99
 
114
100
 
@@ -2,7 +2,7 @@ module Neo4j
2
2
  module Core
3
3
 
4
4
  # A mixin which adds indexing behaviour to your own Ruby class
5
- # You are expected to implement the method `wrapped_entity` returning the underlying Neo4j Node or Relationship
5
+ # You are expected to implement the method `_java_entity` returning the underlying Neo4j Node or Relationship.
6
6
  module Index
7
7
 
8
8
  # Adds an index on the given property
@@ -16,8 +16,7 @@ module Neo4j
16
16
  # @see Neo4j::Core::Index::ClassMethods#add_index
17
17
  #
18
18
  def add_index(field, value=self[field])
19
- converted_value = Neo4j::TypeConverters.convert(value, field, self.class)
20
- self.class.add_index(wrapped_entity, field.to_s, converted_value)
19
+ self.class.add_index(_java_entity, field.to_s, value)
21
20
  end
22
21
 
23
22
  # Removes an index on the given property.
@@ -27,7 +26,7 @@ module Neo4j
27
26
  # @see Neo4j::Core::Index::ClassMethods#rm_index
28
27
  #
29
28
  def rm_index(field, value=self[field])
30
- self.class.rm_index(wrapped_entity, field.to_s, value)
29
+ self.class.rm_index(_java_entity, field.to_s, value)
31
30
  end
32
31
 
33
32
  end
@@ -5,14 +5,25 @@ module Neo4j
5
5
  # Responsible for holding the configuration for one index
6
6
  # Is used in a DSL to configure the index.
7
7
  class IndexConfig
8
- attr_reader :_trigger_on, :_index_names, :entity_type
8
+ attr_reader :_trigger_on, :_index_names, :entity_type, :_index_type, :_field_types
9
9
 
10
+ # @param [:rel, :node] entity_type the type of index
10
11
  def initialize(entity_type)
11
12
  @entity_type = entity_type
12
- @index_type = {}
13
- @numeric_types = []
13
+ @_index_type = {}
14
+ @_field_types = {}
14
15
  @_trigger_on = {}
15
- @decl_type = {}
16
+ end
17
+
18
+ def to_s
19
+ "IndexConfig [#@entity_type, _index_type: #{@_index_type.inspect}, _field_types: #{@_field_types.inspect}, _trigger_on: #{@_trigger_on.inspect}]"
20
+ end
21
+
22
+ def inherit_from(clazz)
23
+ c = clazz._indexer.config
24
+ raise "Can't inherit from different index type #{@entity_type} != #{c.entity_type}" if @entity_type != c.entity_type
25
+ @_index_type.merge!(c._index_type)
26
+ @_field_types.merge!(c._field_types)
16
27
  end
17
28
 
18
29
  # Specifies which property and values the index should be triggered on.
@@ -22,11 +33,8 @@ module Neo4j
22
33
  merge_and_to_string(@_trigger_on, hash)
23
34
  end
24
35
 
25
- # Specifies the types of the properties being indexed so that the proper sort order can be applied.
26
- # Used in the Index DSL.
27
- # @see Neo4j::Core::Index::ClassMethods#node_indexer
28
- def decl_type(prop_and_type_hash)
29
- merge_and_to_string(@decl_type, prop_and_type_hash)
36
+ def field_type(key)
37
+ @_field_types[key.to_s]
30
38
  end
31
39
 
32
40
  # Specifies an index with configuration
@@ -37,15 +45,15 @@ module Neo4j
37
45
  conf = args.last.kind_of?(Hash) ? args.pop : {}
38
46
 
39
47
  args.uniq.each do |field|
40
- @index_type[field.to_s] = conf[:type] || :exact
41
- @numeric_types << field.to_s if conf[:numeric] == true
48
+ @_index_type[field.to_s] = conf[:type] || :exact
49
+ @_field_types[field.to_s] = conf[:field_type] || String
42
50
  end
43
51
  end
44
52
 
45
- # @return [Class] the specified type of the property or String
53
+ # @return [Class,nil] the specified type of the property or nil
46
54
  # @see #decl_type
47
55
  def decl_type_on(prop)
48
- @decl_type[prop] || String
56
+ @_field_types[prop]
49
57
  end
50
58
 
51
59
  # @return [true, false] if the props can/should trigger an index operation
@@ -71,31 +79,30 @@ module Neo4j
71
79
  @_index_names = hash
72
80
  end
73
81
 
74
-
75
82
  def rm_index_config
76
- @index_type = {}
77
- @numeric_types = []
83
+ @_index_type = {}
84
+ @_field_types = {}
78
85
  end
79
86
 
80
87
  def index_type(field)
81
- @index_type[field.to_s]
88
+ @_index_type[field.to_s]
82
89
  end
83
90
 
84
91
  def has_index_type?(type)
85
- @index_type.values.include?(type)
92
+ @_index_type.values.include?(type)
86
93
  end
87
94
 
88
95
  def fields
89
- @index_type.keys
96
+ @_index_type.keys
90
97
  end
91
98
 
92
99
  def index?(field)
93
- @index_type.include?(field.to_s)
100
+ @_index_type.include?(field.to_s)
94
101
  end
95
102
 
96
103
  def numeric?(field)
97
- return true if @numeric_types.include?(field)
98
- # TODO callback to numeric dsl for Neo4j::NodeMixin decl_props check
104
+ raise "No index on #{field.inspect}, has fields: #{@field_types.inspect}" unless @_field_types[field]
105
+ @_field_types[field] != String
99
106
  end
100
107
 
101
108
  private
@@ -103,7 +110,7 @@ module Neo4j
103
110
  def merge_and_to_string(existing_hash, new_hash)
104
111
  new_hash.each_pair do |k, v|
105
112
  existing_hash[k.to_s] ||= Set.new
106
- existing_hash[k.to_s].merge(v.is_a?(Array)? v : [v])
113
+ existing_hash[k.to_s].merge(v.is_a?(Array) ? v : [v])
107
114
  end
108
115
  end
109
116
  end