neo4j 7.2.3 → 8.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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