neo4j 9.6.2 → 10.0.0.pre.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -13
  3. data/CONTRIBUTORS +4 -0
  4. data/Gemfile +2 -33
  5. data/lib/neo4j.rb +6 -2
  6. data/lib/neo4j/active_base.rb +19 -22
  7. data/lib/neo4j/active_node/has_n.rb +1 -1
  8. data/lib/neo4j/active_node/labels.rb +1 -11
  9. data/lib/neo4j/active_node/node_wrapper.rb +1 -1
  10. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +1 -1
  11. data/lib/neo4j/active_rel/rel_wrapper.rb +2 -2
  12. data/lib/neo4j/ansi.rb +14 -0
  13. data/lib/neo4j/core.rb +14 -0
  14. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  15. data/lib/neo4j/core/cypher_error.rb +37 -0
  16. data/lib/neo4j/core/driver.rb +83 -0
  17. data/lib/neo4j/core/has_uri.rb +63 -0
  18. data/lib/neo4j/core/instrumentable.rb +36 -0
  19. data/lib/neo4j/core/label.rb +158 -0
  20. data/lib/neo4j/core/logging.rb +44 -0
  21. data/lib/neo4j/core/node.rb +23 -0
  22. data/lib/neo4j/core/querable.rb +88 -0
  23. data/lib/neo4j/core/query.rb +487 -0
  24. data/lib/neo4j/core/query_builder.rb +32 -0
  25. data/lib/neo4j/core/query_clauses.rb +727 -0
  26. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  27. data/lib/neo4j/core/relationship.rb +13 -0
  28. data/lib/neo4j/core/responses.rb +50 -0
  29. data/lib/neo4j/core/result.rb +33 -0
  30. data/lib/neo4j/core/schema.rb +30 -0
  31. data/lib/neo4j/core/schema_errors.rb +12 -0
  32. data/lib/neo4j/core/wrappable.rb +30 -0
  33. data/lib/neo4j/migration.rb +2 -2
  34. data/lib/neo4j/migrations/base.rb +1 -1
  35. data/lib/neo4j/model_schema.rb +2 -2
  36. data/lib/neo4j/railtie.rb +8 -52
  37. data/lib/neo4j/schema/operation.rb +1 -1
  38. data/lib/neo4j/shared.rb +1 -1
  39. data/lib/neo4j/shared/property.rb +1 -1
  40. data/lib/neo4j/tasks/migration.rake +5 -4
  41. data/lib/neo4j/transaction.rb +137 -0
  42. data/lib/neo4j/version.rb +1 -1
  43. data/neo4j.gemspec +5 -5
  44. metadata +59 -26
  45. data/bin/neo4j-jars +0 -33
  46. data/lib/neo4j/active_base/session_registry.rb +0 -12
  47. data/lib/neo4j/session_manager.rb +0 -78
@@ -0,0 +1,49 @@
1
+ module Neo4j
2
+ module Core
3
+ module QueryFindInBatches
4
+ def find_in_batches(node_var, prop_var, options = {})
5
+ validate_find_in_batches_options!(options)
6
+
7
+ batch_size = options.delete(:batch_size) || 1000
8
+
9
+ query = reorder(node_var => prop_var).limit(batch_size)
10
+
11
+ records = query.to_a
12
+
13
+ while records.any?
14
+ records_size = records.size
15
+ primary_key_offset = primary_key_offset(records.last, node_var, prop_var)
16
+
17
+ yield records
18
+
19
+ break if records_size < batch_size
20
+
21
+ primary_key_var = Neo4j::Core::QueryClauses::Clause.from_key_and_single_value(node_var, prop_var)
22
+ records = query.where("#{primary_key_var} > {primary_key_offset}")
23
+ .params(primary_key_offset: primary_key_offset).to_a
24
+ end
25
+ end
26
+
27
+ def find_each(*args, &block)
28
+ find_in_batches(*args) { |batch| batch.each(&block) }
29
+ end
30
+
31
+ private
32
+
33
+ def validate_find_in_batches_options!(options)
34
+ invalid_keys = options.keys.map(&:to_sym) - [:batch_size]
35
+ fail ArgumentError, "Invalid keys: #{invalid_keys.join(', ')}" if not invalid_keys.empty?
36
+ end
37
+
38
+ def primary_key_offset(last_record, node_var, prop_var)
39
+ last_record.send(node_var).send(prop_var)
40
+ rescue NoMethodError
41
+ begin
42
+ last_record.send(node_var).properties[prop_var.to_sym]
43
+ rescue NoMethodError
44
+ last_record.send("#{node_var}.#{prop_var}") # In case we're explicitly returning it
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ require 'neo4j/core/wrappable'
2
+
3
+ module Neo4j
4
+ module Core
5
+ module Relationship
6
+ def props; properties; end
7
+ def neo_id; id; end
8
+ def start_node_neo_id; start_node_id; end
9
+ def end_node_neo_id; end_node_id; end
10
+ def rel_type; type; end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ require 'neo4j/core/result'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+
4
+ module Neo4j
5
+ module Core
6
+ module Responses
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ mattr_accessor :wrap_level
11
+ end
12
+
13
+ class_methods do
14
+ def result_from_data(entities_data)
15
+ rows = entities_data.map do |entity_data|
16
+ wrap(entity_data.values)
17
+ end
18
+
19
+ Neo4j::Core::Result.new(entities_data.keys, rows)
20
+ end
21
+
22
+ private
23
+
24
+ def wrap(value)
25
+ case value
26
+ when Neo4j::Driver::Types::Entity
27
+ wrap_by_level(value)
28
+ when Neo4j::Driver::Types::Path
29
+ value
30
+ when Hash
31
+ value.map { |key, val| [key, wrap(val)] }.to_h
32
+ when Enumerable
33
+ value.map(&method(:wrap))
34
+ else
35
+ value
36
+ end
37
+ end
38
+
39
+ def wrap_by_level(entity)
40
+ case wrap_level
41
+ when :core_entity
42
+ entity
43
+ else
44
+ entity.wrap
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ module Neo4j
2
+ module Core
3
+ class Result
4
+ attr_reader :columns, :rows
5
+
6
+ def initialize(columns, rows)
7
+ @columns = columns.map(&:to_sym)
8
+ @rows = rows
9
+ @struct_class = Struct.new(:index, *@columns)
10
+ end
11
+
12
+ include Enumerable
13
+
14
+ def each
15
+ structs.each do |struct|
16
+ yield struct
17
+ end
18
+ end
19
+
20
+ def structs
21
+ @structs ||= rows.each_with_index.map do |row, index|
22
+ @struct_class.new(index, *row)
23
+ end
24
+ end
25
+
26
+ def hashes
27
+ @hashes ||= rows.map do |row|
28
+ Hash[@columns.zip(row)]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ module Neo4j
2
+ module Core
3
+ module Schema
4
+ def version
5
+ result = query('CALL dbms.components()', {}, skip_instrumentation: true)
6
+
7
+ # BTW: community / enterprise could be retrieved via `result.first.edition`
8
+ result.first.versions[0]
9
+ end
10
+
11
+ def indexes
12
+ result = query('CALL db.indexes()', {}, skip_instrumentation: true)
13
+
14
+ result.map do |row|
15
+ label, property = row.description.match(/INDEX ON :([^\(]+)\(([^\)]+)\)/)[1, 2]
16
+ { type: row.type.to_sym, label: label.to_sym, properties: [property.to_sym], state: row.state.to_sym }
17
+ end
18
+ end
19
+
20
+ def constraints
21
+ result = query('CALL db.indexes()', {}, skip_instrumentation: true)
22
+
23
+ result.select { |row| row.type == 'node_unique_property' }.map do |row|
24
+ label, property = row.description.match(/INDEX ON :([^\(]+)\(([^\)]+)\)/)[1, 2]
25
+ { type: :uniqueness, label: label.to_sym, properties: [property.to_sym] }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module Neo4j
2
+ module Core
3
+ module SchemaErrors
4
+ class ConstraintValidationFailedError < CypherError;
5
+ end
6
+ class ConstraintAlreadyExistsError < CypherError;
7
+ end
8
+ class IndexAlreadyExistsError < CypherError;
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ module Neo4j
2
+ module Core
3
+ module Wrappable
4
+ extend ActiveSupport::Concern
5
+
6
+ def wrap
7
+ self.class.wrap(self)
8
+ end
9
+
10
+ class_methods do
11
+ def wrapper_callback(proc)
12
+ fail 'Callback already specified!' if @wrapper_callback
13
+ @wrapper_callback = proc
14
+ end
15
+
16
+ def clear_wrapper_callback
17
+ @wrapper_callback = nil
18
+ end
19
+
20
+ def wrap(node)
21
+ if @wrapper_callback
22
+ @wrapper_callback.call(node)
23
+ else
24
+ node
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -28,7 +28,7 @@ module Neo4j
28
28
  end
29
29
 
30
30
  def query(*args)
31
- Neo4j::ActiveBase.current_session.query(*args)
31
+ Neo4j::ActiveBase.current_driver.query(*args)
32
32
  end
33
33
 
34
34
  class AddIdProperty < Neo4j::Migration
@@ -115,7 +115,7 @@ MESSAGE
115
115
  # def id_batch_set(label, id_property, new_ids, count)
116
116
  # tx = Neo4j::ActiveBase.new_transaction
117
117
 
118
- # Neo4j::ActiveBase.current_session.query("MATCH (n:`#{label}`) WHERE NOT EXISTS(n.#{id_property})
118
+ # Neo4j::ActiveBase.current_driver.query("MATCH (n:`#{label}`) WHERE NOT EXISTS(n.#{id_property})
119
119
  # with COLLECT(n) as nodes, #{new_ids} as ids
120
120
  # FOREACH(i in range(0,#{count - 1})|
121
121
  # FOREACH(node in [nodes[i]]|
@@ -67,7 +67,7 @@ module Neo4j
67
67
  end
68
68
 
69
69
  def log_queries
70
- subscriber = Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&method(:output))
70
+ subscriber = Neo4j::Transaction.subscribe_to_query(&method(:output))
71
71
  yield
72
72
  ensure
73
73
  ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
@@ -32,7 +32,7 @@ module Neo4j
32
32
  def model_constraints
33
33
  return @model_constraints if @model_constraints
34
34
 
35
- constraints = Neo4j::ActiveBase.current_session.constraints.each_with_object({}) do |row, result|
35
+ constraints = Neo4j::Transaction.constraints.each_with_object({}) do |row, result|
36
36
  result[row[:label]] ||= []
37
37
  result[row[:label]] << row[:properties]
38
38
  end
@@ -43,7 +43,7 @@ module Neo4j
43
43
  def model_indexes
44
44
  return @model_indexes if @model_indexes
45
45
 
46
- indexes = Neo4j::ActiveBase.current_session.indexes.each_with_object({}) do |row, result|
46
+ indexes = Neo4j::Transaction.indexes.each_with_object({}) do |row, result|
47
47
  result[row[:label]] ||= []
48
48
  result[row[:label]] << row[:properties]
49
49
  end
data/lib/neo4j/railtie.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  require 'active_support/notifications'
2
2
  require 'rails/railtie'
3
- require 'neo4j/session_manager'
4
3
  # Need the action_dispatch railtie to have action_dispatch.rescue_responses initialized correctly
5
4
  require 'action_dispatch/railtie'
6
- require 'neo4j/core/cypher_session/adaptors'
5
+ require 'neo4j/core/driver'
7
6
 
8
7
  module Neo4j
9
8
  class Railtie < ::Rails::Railtie
@@ -59,49 +58,15 @@ module Neo4j
59
58
  end
60
59
 
61
60
  def setup!(neo4j_config = empty_config)
62
- wait_for_connection = neo4j_config.wait_for_connection
63
- type, url, path, options = final_session_config!(neo4j_config).values_at(:type, :url, :path, :options)
64
- type ||= default_session_type
61
+ url, path, options = final_session_config!(neo4j_config).values_at(:url, :path, :options)
65
62
  options ||= {}
66
- register_neo4j_cypher_logging(type, options)
63
+ register_neo4j_cypher_logging
67
64
 
68
- Neo4j::SessionManager.open_neo4j_session(type,
69
- url || path || default_session_path_or_url,
70
- wait_for_connection,
71
- options)
65
+ Neo4j::ActiveBase.new_driver( url || path || default_session_path_or_url, options)
72
66
  end
73
67
 
74
68
  def final_session_config!(neo4j_config)
75
- support_deprecated_session_configs!(neo4j_config)
76
-
77
- (neo4j_config[:session].empty? ? yaml_config_data : neo4j_config[:session]).dup.tap do |result|
78
- result[:type] ||= URI(result[:url]).scheme if result[:url]
79
- end
80
- end
81
-
82
- def support_deprecated_session_configs!(neo4j_config)
83
- if neo4j_config.sessions.present?
84
- ActiveSupport::Deprecation.warn('neo4j.config.sessions is deprecated, please use neo4j.config.session (not an array)')
85
- neo4j_config[:session] = neo4j_config.sessions[0] if neo4j_config[:session].empty?
86
- end
87
-
88
- %w(type path url options).each do |key|
89
- value = neo4j_config.send("session_#{key}")
90
- if value.present?
91
- ActiveSupport::Deprecation.warn("neo4j.config.session_#{key} is deprecated, please use neo4j.config.session.#{key}")
92
- neo4j_config[:session][key] = value
93
- end
94
- end
95
- end
96
-
97
- def default_session_type
98
- if ENV['NEO4J_URL']
99
- scheme = URI(ENV['NEO4J_URL']).scheme
100
- fail "Invalid scheme for NEO4J_URL: #{scheme}" if !%w(http https bolt).include?(scheme)
101
- scheme == 'https' ? 'http' : scheme
102
- else
103
- ENV['NEO4J_TYPE'] || :http
104
- end.to_sym
69
+ (neo4j_config[:session].empty? ? yaml_config_data : neo4j_config[:session]).dup
105
70
  end
106
71
 
107
72
  def default_session_path_or_url
@@ -123,7 +88,7 @@ module Neo4j
123
88
  end.detect(&:exist?)
124
89
  end
125
90
 
126
- def register_neo4j_cypher_logging(session_type, options)
91
+ def register_neo4j_cypher_logging
127
92
  return if @neo4j_cypher_logging_registered
128
93
 
129
94
  Neo4j::Core::Query.pretty_cypher = Neo4j::Config[:pretty_logged_cypher_queries]
@@ -131,19 +96,10 @@ module Neo4j
131
96
  logger_proc = ->(message) do
132
97
  (Neo4j::Config[:logger] ||= Rails.logger).debug message
133
98
  end
134
- Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&logger_proc)
135
- subscribe_to_session_type_logging!(session_type, options, logger_proc)
99
+ Neo4j::Transaction.subscribe_to_query(&logger_proc)
100
+ Neo4j::Transaction.subscribe_to_request(&logger_proc)
136
101
 
137
102
  @neo4j_cypher_logging_registered = true
138
103
  end
139
-
140
- def subscribe_to_session_type_logging!(session_type, options, logger_proc)
141
- SessionManager
142
- .adaptor_class(session_type, options)
143
- .send(
144
- [:embedded, :embedded_db].include?(session_type.to_sym) ? :subscribe_to_transaction : :subscribe_to_request,
145
- &logger_proc
146
- )
147
- end
148
104
  end
149
105
  end
@@ -7,7 +7,7 @@ module Neo4j
7
7
  @label = if label.is_a?(Neo4j::Core::Label)
8
8
  label
9
9
  else
10
- Neo4j::Core::Label.new(label, ActiveBase.current_session)
10
+ Neo4j::Core::Label.new(label)
11
11
  end
12
12
 
13
13
  @property = property.to_sym
data/lib/neo4j/shared.rb CHANGED
@@ -14,7 +14,7 @@ module Neo4j
14
14
 
15
15
  # remove?
16
16
  def neo4j_session
17
- Neo4j::ActiveBase.current_session
17
+ Neo4j::ActiveBase.current_driver
18
18
  end
19
19
 
20
20
  # remove?
@@ -80,7 +80,7 @@ module Neo4j::Shared
80
80
  end
81
81
  end
82
82
 
83
- DATE_KEY_REGEX = /\A([^\(]+)\((\d+)([if])\)$/
83
+ DATE_KEY_REGEX = /\A([^\(]+)\((\d+)([ifs])\)$/
84
84
  # Gives support for Rails date_select, datetime_select, time_select helpers.
85
85
  def process_attributes(attributes = nil)
86
86
  return attributes if attributes.blank?
@@ -10,8 +10,7 @@ if !defined?(Rails) && !Rake::Task.task_defined?('environment')
10
10
  neo4j_url = ENV['NEO4J_URL'] || 'http://localhost:7474'
11
11
  $LOAD_PATH.unshift File.dirname('./')
12
12
  Neo4j::ActiveBase.on_establish_session do
13
- type = neo4j_url =~ /^bolt/ ? :bolt : :http
14
- Neo4j::SessionManager.open_neo4j_session(type, neo4j_url)
13
+ Neo4j::ActiveBase.new_driver(neo4j_url)
15
14
  end
16
15
  end
17
16
  end
@@ -95,12 +94,14 @@ COMMENT
95
94
  require 'neo4j/migrations/schema'
96
95
 
97
96
  args.with_defaults(remove_missing: false)
97
+
98
98
  schema_data = YAML.safe_load(File.read(SCHEMA_YAML_PATH), [Symbol])
99
+
99
100
  Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&method(:puts))
101
+
100
102
  Neo4j::ActiveBase.run_transaction do
101
103
  Neo4j::Migrations::Schema.synchronize_schema_data(Neo4j::ActiveBase.current_session, schema_data, args[:remove_missing])
102
- end
103
- Neo4j::ActiveBase.run_transaction do
104
+
104
105
  runner = Neo4j::Migrations::Runner.new
105
106
  runner.mark_versions_as_complete(schema_data[:versions]) # Run in test mode?
106
107
  end
@@ -0,0 +1,137 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
+ require 'neo4j/core/querable'
4
+ require 'neo4j/core/schema'
5
+
6
+ module Neo4j
7
+ class Transaction
8
+ include Neo4j::Core::Querable
9
+ extend Neo4j::Core::Schema
10
+
11
+ thread_mattr_accessor :stack
12
+ attr_reader :root
13
+ attr_reader :driver_tx, :driver_session
14
+
15
+ class << self
16
+ # Runs the given block in a new transaction.
17
+ # @param [Boolean] run_in_tx if true a new transaction will not be created, instead if will simply yield to the given block
18
+ # @@yield [Neo4j::Transaction::Instance]
19
+ def run(driver, run_in_tx)
20
+ return yield(nil) unless run_in_tx
21
+
22
+ tx = Neo4j::Transaction.new
23
+ yield tx
24
+ rescue Exception => e # rubocop:disable Lint/RescueException
25
+
26
+ tx.mark_failed unless tx.nil?
27
+ raise e
28
+ ensure
29
+ tx.close unless tx.nil?
30
+ end
31
+
32
+ def root
33
+ stack.first
34
+ end
35
+ end
36
+
37
+ def initialize(_options = {})
38
+ (self.stack ||= []) << self
39
+
40
+ @root = stack.first
41
+ return unless root?
42
+ @driver_session = Neo4j::Core::Driver.singleton.driver.session(Neo4j::Driver::AccessMode::WRITE)
43
+ @driver_tx = @driver_session.begin_transaction
44
+ rescue StandardError => e
45
+ self.stack = []
46
+ @driver_tx.close if @driver_tx
47
+ @driver_session.close if @driver_session
48
+ raise e
49
+ end
50
+
51
+ # Commits or marks this transaction for rollback, depending on whether #mark_failed has been previously invoked.
52
+ def close
53
+ fail 'Tried closing when transaction stack is empty (maybe you closed too many?)' if stack.empty?
54
+ fail "Closed transaction which wasn't the most recent on the stack (maybe you forgot to close one?)" if stack.pop != self
55
+
56
+ post_close! if stack.empty?
57
+ end
58
+
59
+ # Marks this transaction as failed,
60
+ # which means that it will unconditionally be rolled back
61
+ # when #close is called.
62
+ # Aliased for legacy purposes.
63
+ def mark_failed
64
+ root.mark_failed if root && root != self
65
+ @failure = true
66
+ end
67
+
68
+ alias failure mark_failed
69
+
70
+ # If it has been marked as failed.
71
+ # Aliased for legacy purposes.
72
+ def failed?
73
+ !!@failure
74
+ end
75
+
76
+ alias failure? failed?
77
+
78
+ def root?
79
+ @root == self
80
+ end
81
+
82
+ def query(*args)
83
+ options = if args[0].is_a?(::Neo4j::Core::Query)
84
+ args[1] ||= {}
85
+ else
86
+ args[1] ||= {}
87
+ args[2] ||= {}
88
+ end
89
+ options[:transaction] ||= self
90
+
91
+ self.class.query(*args)
92
+ end
93
+
94
+ def queries(options = {}, &block)
95
+ self.class.queries({ transaction: self }.merge(options), &block)
96
+ end
97
+
98
+ def after_commit_registry
99
+ @after_commit_registry ||= []
100
+ end
101
+
102
+ def after_commit(&block)
103
+ after_commit_registry << block
104
+ end
105
+
106
+ def commit
107
+ return unless root?
108
+ begin
109
+ @driver_tx.success
110
+ @driver_tx.close
111
+ ensure
112
+ @driver_session.close
113
+ end
114
+ end
115
+
116
+ def delete
117
+ root.driver_tx.failure
118
+ root.driver_tx.close
119
+ root.driver_session.close
120
+ end
121
+
122
+ def root_tx
123
+ root.driver_tx
124
+ end
125
+
126
+ private
127
+
128
+ def post_close!
129
+ if failed?
130
+ delete
131
+ else
132
+ commit
133
+ after_commit_registry.each(&:call)
134
+ end
135
+ end
136
+ end
137
+ end