neo4j 7.2.3 → 8.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -46
  3. data/Gemfile +15 -14
  4. data/README.md +21 -14
  5. data/bin/neo4j-jars +1 -1
  6. data/lib/neo4j.rb +12 -1
  7. data/lib/neo4j/active_base.rb +68 -0
  8. data/lib/neo4j/active_base/session_registry.rb +12 -0
  9. data/lib/neo4j/active_node.rb +13 -21
  10. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +6 -6
  11. data/lib/neo4j/active_node/enum.rb +3 -6
  12. data/lib/neo4j/active_node/has_n.rb +24 -19
  13. data/lib/neo4j/active_node/has_n/association.rb +6 -2
  14. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +1 -1
  15. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +1 -1
  16. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +1 -1
  17. data/lib/neo4j/active_node/id_property.rb +52 -15
  18. data/lib/neo4j/active_node/labels.rb +32 -10
  19. data/lib/neo4j/active_node/labels/index.rb +5 -55
  20. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  21. data/lib/neo4j/active_node/node_wrapper.rb +39 -37
  22. data/lib/neo4j/active_node/persistence.rb +27 -13
  23. data/lib/neo4j/active_node/query/query_proxy.rb +11 -9
  24. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +4 -4
  25. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +1 -0
  26. data/lib/neo4j/active_node/query/query_proxy_link.rb +13 -9
  27. data/lib/neo4j/active_node/query/query_proxy_methods.rb +76 -8
  28. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +1 -1
  29. data/lib/neo4j/active_node/query_methods.rb +3 -3
  30. data/lib/neo4j/active_node/scope.rb +24 -7
  31. data/lib/neo4j/active_rel.rb +21 -3
  32. data/lib/neo4j/active_rel/initialize.rb +2 -2
  33. data/lib/neo4j/active_rel/persistence.rb +32 -6
  34. data/lib/neo4j/active_rel/persistence/query_factory.rb +3 -3
  35. data/lib/neo4j/active_rel/property.rb +9 -9
  36. data/lib/neo4j/active_rel/query.rb +6 -4
  37. data/lib/neo4j/active_rel/rel_wrapper.rb +24 -16
  38. data/lib/neo4j/active_rel/related_node.rb +5 -1
  39. data/lib/neo4j/active_rel/types.rb +2 -2
  40. data/lib/neo4j/config.rb +0 -1
  41. data/lib/neo4j/errors.rb +3 -0
  42. data/lib/neo4j/migration.rb +90 -71
  43. data/lib/neo4j/migrations.rb +10 -0
  44. data/lib/neo4j/migrations/base.rb +44 -0
  45. data/lib/neo4j/migrations/helpers.rb +101 -0
  46. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  47. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  48. data/lib/neo4j/migrations/helpers/schema.rb +53 -0
  49. data/lib/neo4j/migrations/migration_file.rb +24 -0
  50. data/lib/neo4j/migrations/runner.rb +110 -0
  51. data/lib/neo4j/migrations/schema_migration.rb +9 -0
  52. data/lib/neo4j/model_schema.rb +100 -0
  53. data/lib/neo4j/railtie.rb +29 -110
  54. data/lib/neo4j/schema/operation.rb +24 -13
  55. data/lib/neo4j/session_manager.rb +137 -0
  56. data/lib/neo4j/shared.rb +20 -11
  57. data/lib/neo4j/shared/attributes.rb +10 -16
  58. data/lib/neo4j/shared/callbacks.rb +3 -3
  59. data/lib/neo4j/shared/cypher.rb +1 -1
  60. data/lib/neo4j/shared/declared_properties.rb +1 -1
  61. data/lib/neo4j/shared/declared_property.rb +1 -1
  62. data/lib/neo4j/shared/enum.rb +6 -18
  63. data/lib/neo4j/shared/identity.rb +27 -21
  64. data/lib/neo4j/shared/persistence.rb +26 -17
  65. data/lib/neo4j/shared/property.rb +5 -2
  66. data/lib/neo4j/shared/query_factory.rb +4 -5
  67. data/lib/neo4j/shared/type_converters.rb +8 -9
  68. data/lib/neo4j/shared/validations.rb +1 -5
  69. data/lib/neo4j/tasks/migration.rake +83 -2
  70. data/lib/neo4j/version.rb +1 -1
  71. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  72. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  73. data/lib/rails/generators/neo4j/model/model_generator.rb +1 -3
  74. data/lib/rails/generators/neo4j_generator.rb +1 -0
  75. data/neo4j.gemspec +3 -3
  76. metadata +58 -65
  77. data/bin/rake +0 -17
  78. data/lib/neo4j/shared/permitted_attributes.rb +0 -28
@@ -0,0 +1,9 @@
1
+ module Neo4j
2
+ module Migrations
3
+ class SchemaMigration
4
+ include Neo4j::ActiveNode
5
+ id_property :migration_id
6
+ property :migration_id, type: String
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,100 @@
1
+ require 'set'
2
+ module Neo4j
3
+ # This is here to support the removed functionality of being able to
4
+ # defined indexes and constraints on models
5
+ # This code should be removed later
6
+ module ModelSchema
7
+ MODEL_INDEXES = {}
8
+ MODEL_CONSTRAINTS = {}
9
+ REQUIRED_INDEXES = {}
10
+
11
+ class << self
12
+ def add_defined_constraint(model, property_name)
13
+ MODEL_CONSTRAINTS[model] ||= Set.new
14
+ MODEL_CONSTRAINTS[model] << property_name.to_sym
15
+ end
16
+
17
+ def add_defined_index(model, property_name)
18
+ MODEL_INDEXES[model] ||= Set.new
19
+ MODEL_INDEXES[model] << property_name.to_sym
20
+ end
21
+
22
+ def add_required_index(model, property_name)
23
+ REQUIRED_INDEXES[model] ||= Set.new
24
+ REQUIRED_INDEXES[model] << property_name.to_sym
25
+ end
26
+
27
+ def defined_constraint?(model, property_name)
28
+ MODEL_CONSTRAINTS[model] &&
29
+ MODEL_CONSTRAINTS[model].include?(property_name.to_sym)
30
+ end
31
+
32
+ def model_constraints
33
+ constraints = Neo4j::ActiveBase.current_session.constraints.each_with_object({}) do |row, result|
34
+ result[row[:label]] ||= []
35
+ result[row[:label]] << row[:properties]
36
+ end
37
+
38
+ schema_elements_list(MODEL_CONSTRAINTS, constraints)
39
+ end
40
+
41
+ def model_indexes
42
+ indexes = Neo4j::ActiveBase.current_session.indexes.each_with_object({}) do |row, result|
43
+ result[row[:label]] ||= []
44
+ result[row[:label]] << row[:properties]
45
+ end
46
+
47
+ schema_elements_list(MODEL_INDEXES, indexes) +
48
+ schema_elements_list(REQUIRED_INDEXES, indexes).reject(&:last)
49
+ # reject required indexes which are already in the DB
50
+ end
51
+
52
+ # should be private
53
+ def schema_elements_list(by_model, db_results)
54
+ by_model.flat_map do |model, property_names|
55
+ label = model.mapped_label_name.to_sym
56
+ property_names.map do |property_name|
57
+ exists = db_results[label] && db_results[label].include?([property_name])
58
+ [model, label, property_name, exists]
59
+ end
60
+ end
61
+ end
62
+
63
+ def validate_model_schema!
64
+ messages = {index: [], constraint: []}
65
+ [[:constraint, model_constraints], [:index, model_indexes]].each do |type, schema_elements|
66
+ schema_elements.map do |model, label, property_name, exists|
67
+ if exists
68
+ log_warning!(type, model, property_name)
69
+ else
70
+ messages[type] << force_add_message(type, label, property_name)
71
+ end
72
+ end
73
+ end
74
+
75
+ return if messages.values.all?(&:empty?)
76
+
77
+ fail validation_error_message(messages)
78
+ end
79
+
80
+ def validation_error_message(messages)
81
+ <<MSG
82
+ Some schema elements were defined by the model (which is no longer support), but they do not exist in the database. Run the following to create them:
83
+
84
+ #{messages[:constraint].join("\n")}
85
+ #{messages[:index].join("\n")}
86
+
87
+ (zshell users may need to escape the brackets)
88
+ MSG
89
+ end
90
+
91
+ def force_add_message(index_or_constraint, label, property_name)
92
+ "rake neo4j:generate_schema_migration[#{index_or_constraint},#{label},#{property_name}]"
93
+ end
94
+
95
+ def log_warning!(index_or_constraint, model, property_name)
96
+ Neo4j::ActiveBase.logger.warn "WARNING: The #{index_or_constraint} option is no longer supported (Defined on #{model.name} for #{property_name})"
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,7 +1,11 @@
1
1
  require 'active_support/notifications'
2
2
  require 'rails/railtie'
3
+ require 'neo4j/session_manager'
3
4
  # Need the action_dispatch railtie to have action_dispatch.rescue_responses initialized correctly
4
5
  require 'action_dispatch/railtie'
6
+ require 'neo4j/core/cypher_session/adaptors/http'
7
+ require 'neo4j/core/cypher_session/adaptors/bolt'
8
+ require 'neo4j/core/cypher_session/adaptors/embedded'
5
9
 
6
10
  module Neo4j
7
11
  class Railtie < ::Rails::Railtie
@@ -39,131 +43,46 @@ module Neo4j
39
43
  load 'neo4j/tasks/migration.rake'
40
44
  end
41
45
 
42
- class << self
43
- def java_platform?
44
- RUBY_PLATFORM =~ /java/
45
- end
46
-
47
- def setup_default_session(cfg)
48
- setup_config_defaults!(cfg)
49
-
50
- return if !cfg.sessions.empty?
51
-
52
- cfg.sessions << {type: cfg.session_type, path: cfg.session_path, options: cfg.session_options.merge(default: true)}
53
- end
54
-
55
- def setup_config_defaults!(cfg)
56
- cfg.session_type ||= default_session_type
57
- cfg.session_path ||= default_session_path
58
- cfg.session_options ||= {}
59
- cfg.sessions ||= []
60
- end
61
-
62
- def config_data
63
- @config_data ||= if yaml_path
64
- HashWithIndifferentAccess.new(YAML.load(ERB.new(yaml_path.read).result)[Rails.env])
65
- else
66
- {}
67
- end
68
- end
69
-
70
- def yaml_path
71
- @yaml_path ||= %w(config/neo4j.yml config/neo4j.yaml).map do |path|
72
- Rails.root.join(path)
73
- end.detect(&:exist?)
74
- end
75
-
76
- def default_session_type
77
- type = ENV['NEO4J_TYPE'] || config_data[:type] || :server_db
78
- type.to_sym
79
- end
80
-
81
- def default_session_path
82
- ENV['NEO4J_URL'] || ENV['NEO4J_PATH'] ||
83
- config_data[:url] || config_data[:path] ||
84
- 'http://localhost:7474'
85
- end
46
+ console do |app|
47
+ Neo4j::Config[:logger] = ActiveSupport::Logger.new(STDOUT)
86
48
 
87
- def start_embedded_session(session)
88
- # See https://github.com/jruby/jruby/wiki/UnlimitedStrengthCrypto
89
- security_class = java.lang.Class.for_name('javax.crypto.JceSecurity')
90
- restricted_field = security_class.get_declared_field('isRestricted')
91
- restricted_field.accessible = true
92
- restricted_field.set nil, false
93
- session.start
94
- end
49
+ register_neo4j_cypher_logging(app.config.neo4j.sessions.map { |s| s[:type] })
50
+ end
95
51
 
96
- def open_neo4j_session(options, wait_for_connection = false)
97
- type, name, default, path = options.values_at(:type, :name, :default, :path)
52
+ # Starting Neo after :load_config_initializers allows apps to
53
+ # register migrations in config/initializers
54
+ initializer 'neo4j.start', after: :load_config_initializers do |app|
55
+ cfg = app.config.neo4j
56
+ # Set Rails specific defaults
57
+ Neo4j::SessionManager.setup! cfg
98
58
 
99
- if !java_platform? && type == :embedded_db
100
- fail "Tried to start embedded Neo4j db without using JRuby (got #{RUBY_PLATFORM}), please run `rvm jruby`"
101
- end
59
+ Neo4j::Config[:logger] ||= Rails.logger
102
60
 
103
- session = wait_for_value(wait_for_connection) do
104
- if options.key?(:name)
105
- Neo4j::Session.open_named(type, name, default, path)
106
- else
107
- Neo4j::Session.open(type, path, options[:options])
108
- end
109
- end
61
+ session_types = cfg.sessions.map { |session_opts| session_opts[:type] }
110
62
 
111
- start_embedded_session(session) if type == :embedded_db
112
- end
63
+ register_neo4j_cypher_logging(session_types)
113
64
  end
114
65
 
115
- def wait_for_value(wait)
116
- session = nil
117
- Timeout.timeout(60) do
118
- until session
119
- begin
120
- if session = yield
121
- puts
122
- return session
123
- end
124
- rescue Faraday::ConnectionFailed => e
125
- raise e if !wait
126
-
127
- putc '.'
128
- sleep(1)
129
- end
130
- end
131
- end
132
- end
66
+ TYPE_SUBSCRIBERS = {
67
+ http: Neo4j::Core::CypherSession::Adaptors::HTTP.method(:subscribe_to_request),
68
+ bolt: Neo4j::Core::CypherSession::Adaptors::Bolt.method(:subscribe_to_request),
69
+ embedded: Neo4j::Core::CypherSession::Adaptors::Embedded.method(:subscribe_to_transaction)
70
+ }
133
71
 
134
- def register_neo4j_cypher_logging
72
+ def register_neo4j_cypher_logging(session_types)
135
73
  return if @neo4j_cypher_logging_registered
136
74
 
137
75
  Neo4j::Core::Query.pretty_cypher = Neo4j::Config[:pretty_logged_cypher_queries]
138
76
 
139
- Neo4j::Server::CypherSession.log_with do |message|
140
- (Neo4j::Config[:logger] || Rails.logger).debug message
77
+ logger_proc = ->(message) do
78
+ (Neo4j::Config[:logger] ||= Rails.logger).debug message
141
79
  end
142
-
143
- @neo4j_cypher_logging_registered = true
144
- end
145
-
146
- console do
147
- Neo4j::Config[:logger] = ActiveSupport::Logger.new(STDOUT)
148
-
149
- register_neo4j_cypher_logging
150
- end
151
-
152
- # Starting Neo after :load_config_initializers allows apps to
153
- # register migrations in config/initializers
154
- initializer 'neo4j.start', after: :load_config_initializers do |app|
155
- cfg = app.config.neo4j
156
- # Set Rails specific defaults
157
- Neo4j::Railtie.setup_default_session(cfg)
158
-
159
- cfg.sessions.each do |session_opts|
160
- Neo4j::Railtie.open_neo4j_session(session_opts, cfg.wait_for_connection)
80
+ Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&logger_proc)
81
+ session_types.map(&:to_sym).uniq.each do |type|
82
+ TYPE_SUBSCRIBERS[type].call(&logger_proc)
161
83
  end
162
- Neo4j::Config.configuration.merge!(cfg.to_hash)
163
84
 
164
- Neo4j::Config[:logger] ||= Rails.logger
165
-
166
- register_neo4j_cypher_logging
85
+ @neo4j_cypher_logging_registered = true
167
86
  end
168
87
  end
169
88
  end
@@ -1,10 +1,15 @@
1
1
  module Neo4j
2
2
  module Schema
3
3
  class Operation
4
- attr_reader :label_name, :property, :options
4
+ attr_reader :label, :property, :options
5
+
6
+ def initialize(label, property, options = default_options)
7
+ @label = if label.is_a?(Neo4j::Core::Label)
8
+ label
9
+ else
10
+ Neo4j::Core::Label.new(label, ActiveBase.current_session)
11
+ end
5
12
 
6
- def initialize(label_name, property, options = default_options)
7
- @label_name = label_name.to_sym
8
13
  @property = property.to_sym
9
14
  @options = options
10
15
  end
@@ -13,14 +18,14 @@ module Neo4j
13
18
  []
14
19
  end
15
20
 
21
+ def label_object
22
+ label
23
+ end
24
+
16
25
  def create!
17
26
  drop_incompatible!
18
27
  return if exist?
19
- label_object.send(:"create_#{type}", property, options)
20
- end
21
-
22
- def label_object
23
- @label_object ||= Neo4j::Label.create(label_name)
28
+ schema_query(:"create_#{type}")
24
29
  end
25
30
 
26
31
  def incompatible_operation_classes
@@ -28,12 +33,12 @@ module Neo4j
28
33
  end
29
34
 
30
35
  def drop!
31
- label_object.send(:"drop_#{type}", property, options)
36
+ schema_query(:"drop_#{type}")
32
37
  end
33
38
 
34
39
  def drop_incompatible!
35
40
  incompatible_operation_classes.each do |clazz|
36
- operation = clazz.new(label_name, property)
41
+ operation = clazz.new(@label, property)
37
42
  operation.drop! if operation.exist?
38
43
  end
39
44
  end
@@ -49,6 +54,12 @@ module Neo4j
49
54
  def type
50
55
  fail 'Abstract class, not implemented'
51
56
  end
57
+
58
+ private
59
+
60
+ def schema_query(method)
61
+ label.send(method, property, options)
62
+ end
52
63
  end
53
64
 
54
65
  class ExactIndexOperation < Neo4j::Schema::Operation
@@ -61,7 +72,7 @@ module Neo4j
61
72
  end
62
73
 
63
74
  def exist?
64
- label_object.indexes[:property_keys].include?([property])
75
+ label.index?(property)
65
76
  end
66
77
  end
67
78
 
@@ -71,7 +82,7 @@ module Neo4j
71
82
  end
72
83
 
73
84
  def type
74
- 'constraint'
85
+ 'uniqueness_constraint'
75
86
  end
76
87
 
77
88
  def create!
@@ -80,7 +91,7 @@ module Neo4j
80
91
  end
81
92
 
82
93
  def exist?
83
- Neo4j::Label.constraint?(label_name, property)
94
+ label.uniqueness_constraint?(property)
84
95
  end
85
96
 
86
97
  def default_options
@@ -0,0 +1,137 @@
1
+ require 'active_support/core_ext/hash'
2
+ require 'active_support/ordered_options'
3
+ require 'neo4j/core/cypher_session/adaptors/http'
4
+ require 'neo4j/core/cypher_session/adaptors/bolt'
5
+ require 'neo4j/core/cypher_session/adaptors/embedded'
6
+
7
+ module Neo4j
8
+ class SessionManager
9
+ class << self
10
+ def setup!(cfg = nil)
11
+ cfg ||= ActiveSupport::OrderedOptions.new
12
+
13
+ setup_default_session(cfg)
14
+
15
+ cfg.sessions.each do |session_opts|
16
+ open_neo4j_session(session_opts, cfg.wait_for_connection)
17
+ end
18
+
19
+ Neo4j::Config.configuration.merge!(cfg.to_h)
20
+ end
21
+
22
+ # TODO: Remove ability for multiple sessions?
23
+ # Ability to overwrite default session per-model like ActiveRecord?
24
+ def setup_default_session(cfg)
25
+ setup_config_defaults!(cfg)
26
+
27
+ return if !cfg.sessions.empty?
28
+
29
+ cfg.sessions << {type: cfg.session_type, path: cfg.session_path, options: cfg.session_options.merge(default: true)}
30
+ end
31
+
32
+ # TODO: Support `session_url` config for server mode
33
+ def setup_config_defaults!(cfg)
34
+ cfg.session_type ||= default_session_type
35
+ cfg.session_path ||= default_session_path
36
+ cfg.session_options ||= {}
37
+ cfg.sessions ||= []
38
+ end
39
+
40
+ def open_neo4j_session(options, wait_for_connection = false)
41
+ session_type, path, url = options.values_at(:type, :path, :url)
42
+
43
+ enable_unlimited_strength_crypto! if java_platform? && session_type_is_embedded?(session_type)
44
+
45
+ adaptor = wait_for_value(wait_for_connection) do
46
+ cypher_session_adaptor(session_type, url || path, (options[:options] || {}).merge(wrap_level: :proc))
47
+ end
48
+
49
+ Neo4j::ActiveBase.current_adaptor = adaptor
50
+ end
51
+
52
+ protected
53
+
54
+ def session_type_is_embedded?(session_type)
55
+ [:embedded_db, :embedded].include?(session_type)
56
+ end
57
+
58
+ def enable_unlimited_strength_crypto!
59
+ # See https://github.com/jruby/jruby/wiki/UnlimitedStrengthCrypto
60
+ security_class = java.lang.Class.for_name('javax.crypto.JceSecurity')
61
+ restricted_field = security_class.get_declared_field('isRestricted')
62
+ restricted_field.accessible = true
63
+ restricted_field.set nil, false
64
+ end
65
+
66
+ def config_data
67
+ @config_data ||= if yaml_path
68
+ HashWithIndifferentAccess.new(YAML.load(ERB.new(yaml_path.read).result)[Rails.env])
69
+ else
70
+ {}
71
+ end
72
+ end
73
+
74
+ def yaml_path
75
+ return unless defined?(Rails)
76
+ @yaml_path ||= %w(config/neo4j.yml config/neo4j.yaml).map do |path|
77
+ Rails.root.join(path)
78
+ end.detect(&:exist?)
79
+ end
80
+
81
+ # TODO: Deprecate embedded_db and http in favor of embedded and http
82
+ #
83
+ def cypher_session_adaptor(type, path_or_url, options = {})
84
+ case type
85
+ when :embedded_db, :embedded
86
+ Neo4j::Core::CypherSession::Adaptors::Embedded.new(path_or_url, options)
87
+ when :http
88
+ Neo4j::Core::CypherSession::Adaptors::HTTP.new(path_or_url, options)
89
+ when :bolt
90
+ Neo4j::Core::CypherSession::Adaptors::Bolt.new(path_or_url, options)
91
+ else
92
+ extra = ' (`server_db` has been replaced by `http` or `bolt`)'
93
+ fail ArgumentError, "Invalid session type: #{type.inspect}#{extra if type.to_sym == :server_db}"
94
+ end
95
+ end
96
+
97
+ def default_session_type
98
+ if ENV['NEO4J_URL']
99
+ URI(ENV['NEO4J_URL']).scheme.tap do |scheme|
100
+ fail "Invalid scheme for NEO4J_URL: #{scheme}" if !%w(http bolt).include?(scheme)
101
+ end
102
+ else
103
+ ENV['NEO4J_TYPE'] || config_data[:type] || :http
104
+ end.to_sym
105
+ end
106
+
107
+ def default_session_path
108
+ ENV['NEO4J_URL'] || ENV['NEO4J_PATH'] ||
109
+ config_data[:url] || config_data[:path] ||
110
+ 'http://localhost:7474'
111
+ end
112
+
113
+ def java_platform?
114
+ RUBY_PLATFORM =~ /java/
115
+ end
116
+
117
+ def wait_for_value(wait)
118
+ session = nil
119
+ Timeout.timeout(60) do
120
+ until session
121
+ begin
122
+ if session = yield
123
+ puts
124
+ return session
125
+ end
126
+ rescue Faraday::ConnectionFailed => e
127
+ raise e if !wait
128
+
129
+ putc '.'
130
+ sleep(1)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end