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

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.
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