delfos 0.0.1.pre.rc1 → 0.0.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.
- checksums.yaml +4 -4
- data/lib/delfos.rb +22 -78
- data/lib/delfos/call_stack.rb +39 -0
- data/lib/delfos/call_stack/stack.rb +59 -0
- data/lib/delfos/file_system/common_path.rb +62 -0
- data/lib/delfos/{distance/calculation.rb → file_system/distance_calculation.rb} +4 -15
- data/lib/delfos/file_system/path_determination.rb +37 -0
- data/lib/delfos/{distance → file_system}/relation.rb +1 -1
- data/lib/delfos/method_logging.rb +32 -49
- data/lib/delfos/method_logging/call_site_parsing.rb +74 -0
- data/lib/delfos/method_logging/code_location.rb +34 -98
- data/lib/delfos/method_logging/{args.rb → method_parameters.rb} +17 -18
- data/lib/delfos/neo4j.rb +58 -0
- data/lib/delfos/neo4j/batch/execution.rb +149 -0
- data/lib/delfos/neo4j/{execution_persistence.rb → call_stack_query.rb} +12 -22
- data/lib/delfos/neo4j/distance/call_site_fetcher.rb +19 -0
- data/lib/delfos/neo4j/distance/update.rb +50 -0
- data/lib/delfos/neo4j/informer.rb +62 -34
- data/lib/delfos/neo4j/query_execution/errors.rb +30 -0
- data/lib/delfos/neo4j/query_execution/http.rb +61 -0
- data/lib/delfos/neo4j/query_execution/http_query.rb +67 -0
- data/lib/delfos/neo4j/query_execution/sync.rb +35 -0
- data/lib/delfos/neo4j/query_execution/transactional.rb +68 -0
- data/lib/delfos/neo4j/schema.rb +73 -0
- data/lib/delfos/patching/basic_object.rb +1 -5
- data/lib/delfos/patching/method_cache.rb +83 -0
- data/lib/delfos/patching/method_override.rb +76 -66
- data/lib/delfos/patching/module_defining_methods.rb +105 -0
- data/lib/delfos/patching/unstubber.rb +34 -0
- data/lib/delfos/setup.rb +88 -0
- metadata +42 -15
- data/lib/delfos/common_path.rb +0 -58
- data/lib/delfos/execution_chain.rb +0 -75
- data/lib/delfos/method_logging/added_methods.rb +0 -67
- data/lib/delfos/neo4j/distance_update.rb +0 -73
- data/lib/delfos/neo4j/query_execution.rb +0 -79
- data/lib/delfos/patching/unstubbing_spec_helper.rb +0 -58
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "json"
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
require_relative "http_query"
|
6
|
+
require_relative "errors"
|
7
|
+
|
8
|
+
module Delfos
|
9
|
+
module Neo4j
|
10
|
+
module QueryExecution
|
11
|
+
class Sync
|
12
|
+
include HttpQuery
|
13
|
+
|
14
|
+
def perform
|
15
|
+
raise InvalidQuery.new(json["errors"], query, params) if errors?
|
16
|
+
|
17
|
+
strip_out_meta_data
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def strip_out_meta_data
|
23
|
+
json["results"]
|
24
|
+
&.first
|
25
|
+
&.[]("data")
|
26
|
+
&.map { |r| r["row"] }
|
27
|
+
end
|
28
|
+
|
29
|
+
def uri
|
30
|
+
@uri ||= Delfos.neo4j.uri_for("/db/data/transaction/commit")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "json"
|
3
|
+
require "uri"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
require_relative "http"
|
7
|
+
require_relative "http_query"
|
8
|
+
|
9
|
+
module Delfos
|
10
|
+
module Neo4j
|
11
|
+
module QueryExecution
|
12
|
+
class Transactional
|
13
|
+
include HttpQuery
|
14
|
+
|
15
|
+
def self.flush!(commit_url)
|
16
|
+
response = Http.new(commit_url).post({ statements: [] }.to_json)
|
17
|
+
|
18
|
+
check_for_error(commit_url, response)
|
19
|
+
|
20
|
+
response.code == "200"
|
21
|
+
end
|
22
|
+
|
23
|
+
VALID_RESPONSE_MATCHER = /\A2\d\d\z/
|
24
|
+
|
25
|
+
def self.check_for_error(uri, response)
|
26
|
+
return if response.code[VALID_RESPONSE_MATCHER]
|
27
|
+
|
28
|
+
if response.code == "404"
|
29
|
+
raise ExpiredTransaction.new(uri, response)
|
30
|
+
end
|
31
|
+
|
32
|
+
raise InvalidCommit.new(uri, response)
|
33
|
+
end
|
34
|
+
|
35
|
+
def perform
|
36
|
+
self.class.check_for_error(uri, response)
|
37
|
+
|
38
|
+
raise InvalidQuery.new(json["errors"], query, params) if errors?
|
39
|
+
|
40
|
+
transaction_url = URI.parse header("location") if header("location")
|
41
|
+
commit_url = URI.parse json["commit"] if json["commit"]
|
42
|
+
expires = Time.parse json["transaction"]["expires"] if json["transaction"]
|
43
|
+
|
44
|
+
[transaction_url, commit_url, expires]
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def header(name)
|
50
|
+
response.each_header.to_a.find { |n, _| n == name }&.last
|
51
|
+
end
|
52
|
+
|
53
|
+
def uri
|
54
|
+
@uri ||= Delfos.neo4j.uri_for("/db/data/transaction")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class InvalidCommit < IOError
|
59
|
+
def initialize(commit_url, response)
|
60
|
+
super ["URL:", commit_url, "response:", response].join("\n ")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ExpiredTransaction < InvalidCommit
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Delfos
|
3
|
+
module Neo4j
|
4
|
+
module Schema
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def ensure_constraints!(required)
|
8
|
+
log "-" * 80
|
9
|
+
log "checking constraints"
|
10
|
+
log Time.now
|
11
|
+
log "-" * 80
|
12
|
+
|
13
|
+
if satisfies_constraints?(required)
|
14
|
+
log "Neo4j schema constraints satisfied"
|
15
|
+
else
|
16
|
+
error "-" * 80
|
17
|
+
error "Neo4j schema constraints not satisfied - adding"
|
18
|
+
error Time.now
|
19
|
+
|
20
|
+
required.each do |label, attribute|
|
21
|
+
create_constraint(label, attribute)
|
22
|
+
end
|
23
|
+
|
24
|
+
log "-" * 80
|
25
|
+
log "Constraints added"
|
26
|
+
log Time.now
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def error(s)
|
33
|
+
log(s, :error)
|
34
|
+
end
|
35
|
+
|
36
|
+
def log(s, type = :debug)
|
37
|
+
Delfos.logger.send(type, s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_constraint(label, attribute)
|
41
|
+
Neo4j.execute_sync <<-QUERY
|
42
|
+
CREATE CONSTRAINT ON (c:#{label}) ASSERT c.#{attribute} IS UNIQUE
|
43
|
+
QUERY
|
44
|
+
end
|
45
|
+
|
46
|
+
def satisfies_constraints?(required)
|
47
|
+
existing_constraints = fetch_existing_constraints
|
48
|
+
|
49
|
+
required.inject(true) do |_result, (label, attribute)|
|
50
|
+
constraint = existing_constraints.find { |c| c["label"] == label }
|
51
|
+
|
52
|
+
constraint && constraint["property_keys"].include?(attribute)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_existing_constraints
|
57
|
+
response = QueryExecution::Http.new(constraints_uri).get
|
58
|
+
|
59
|
+
if response.code == "200"
|
60
|
+
JSON.parse response.body
|
61
|
+
else
|
62
|
+
raise IOError.new uri, response
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def constraints_uri
|
69
|
+
Delfos.neo4j.uri_for("/db/data/schema/constraint")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -3,15 +3,11 @@ require_relative "method_override"
|
|
3
3
|
|
4
4
|
class BasicObject
|
5
5
|
def self.method_added(name)
|
6
|
-
return if %i(to_s singleton_method_added method_added).include?(name)
|
7
|
-
|
8
6
|
::Delfos::Patching::MethodOverride.setup(self, name, private_instance_methods, class_method: false)
|
9
7
|
end
|
10
8
|
|
11
9
|
def self.singleton_method_added(name)
|
12
|
-
|
13
|
-
return if %i(to_s name extended included inherited method_added singleton_method_added).include?(name)
|
14
|
-
return if ::Delfos::MethodLogging.exclude?(method(name))
|
10
|
+
return if %i(define_method extended included inherited method_added singleton_method_added).include?(name)
|
15
11
|
|
16
12
|
::Delfos::Patching::MethodOverride.setup(self, name, private_methods, class_method: true)
|
17
13
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "Forwardable" unless defined? Forwardable
|
3
|
+
|
4
|
+
module Delfos
|
5
|
+
module Patching
|
6
|
+
class MethodCache
|
7
|
+
class << self
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :instance,
|
11
|
+
:files_for,
|
12
|
+
:append,
|
13
|
+
:find
|
14
|
+
|
15
|
+
def reset!
|
16
|
+
@instance = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def instance
|
20
|
+
@instance ||= new
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_method
|
24
|
+
instance.send(:added_methods).each do |klass, methods|
|
25
|
+
methods.each do |k, m|
|
26
|
+
class_method = !!(k[/^ClassMethod_/])
|
27
|
+
yield klass, m, class_method
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@added_methods = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def files_for(klass)
|
38
|
+
fetch(klass).
|
39
|
+
values.
|
40
|
+
map(&:source_location).
|
41
|
+
compact.
|
42
|
+
map(&:first).
|
43
|
+
compact.
|
44
|
+
uniq
|
45
|
+
end
|
46
|
+
|
47
|
+
def append(klass:, method:)
|
48
|
+
class_method = method.respond_to?(:receiver) && method.receiver.is_a?(Class)
|
49
|
+
key = key_for(class_method, method.name)
|
50
|
+
m = fetch(klass)[key]
|
51
|
+
|
52
|
+
fetch(klass)[key] = method if m.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def find(klass:, method_name:, class_method:)
|
56
|
+
key = key_for(class_method, method_name)
|
57
|
+
|
58
|
+
fetch(klass)[key]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
attr_reader :added_methods
|
64
|
+
|
65
|
+
def key_for(class_method, method_name)
|
66
|
+
class_method ? "ClassMethod_#{method_name}" : "InstanceMethod_#{method_name}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch(klass)
|
70
|
+
# Find method definitions defined in klass or its ancestors
|
71
|
+
super_klass = klass.ancestors.detect do |k|
|
72
|
+
(fetch_without_default(k) || {}).values.length.positive?
|
73
|
+
end
|
74
|
+
|
75
|
+
added_methods[(super_klass || klass).to_s] ||= {}
|
76
|
+
end
|
77
|
+
|
78
|
+
def fetch_without_default(klass)
|
79
|
+
added_methods[klass.to_s]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,25 +1,60 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "delfos/method_logging"
|
4
|
+
require "delfos/call_stack"
|
5
|
+
|
6
|
+
require_relative "module_defining_methods"
|
7
|
+
require_relative "unstubber"
|
4
8
|
|
5
9
|
module Delfos
|
6
10
|
module Patching
|
11
|
+
MUTEX = Mutex.new
|
12
|
+
|
7
13
|
class MethodOverride
|
14
|
+
include ModuleDefiningMethods
|
15
|
+
|
8
16
|
class << self
|
9
17
|
def setup(klass, name, private_methods, class_method:)
|
18
|
+
return if skip_meta_programming_defined_method?
|
19
|
+
|
20
|
+
MUTEX.synchronize do
|
21
|
+
return if Thread.current[:__delfos_disable_patching]
|
22
|
+
end
|
23
|
+
|
10
24
|
instance = new(klass, name, private_methods, class_method)
|
11
25
|
|
12
26
|
instance.ensure_method_recorded_once!
|
13
27
|
end
|
28
|
+
|
29
|
+
META_PROGRAMMING_REGEX = /`define_method'\z|`attr_accessor'\z|`attr_reader'\z|`attr_writer'\z/
|
30
|
+
|
31
|
+
def skip_meta_programming_defined_method?
|
32
|
+
stack = caller.dup
|
33
|
+
|
34
|
+
i = stack.index do |l|
|
35
|
+
l["delfos/patching/basic_object.rb"]
|
36
|
+
end
|
37
|
+
|
38
|
+
return unless i
|
39
|
+
|
40
|
+
result = stack[i + 1][META_PROGRAMMING_REGEX]
|
41
|
+
|
42
|
+
return unless result
|
43
|
+
|
44
|
+
Delfos.logger.debug "Skipping setting up delfos logging of method defined by #{result} #{stack[i+1]}"
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
|
14
49
|
end
|
15
50
|
|
16
51
|
attr_reader :klass, :name, :private_methods, :class_method
|
17
52
|
|
18
53
|
def initialize(klass, name, private_methods, class_method)
|
19
|
-
@klass
|
20
|
-
@name
|
54
|
+
@klass = klass
|
55
|
+
@name = name
|
21
56
|
@private_methods = private_methods
|
22
|
-
@class_method
|
57
|
+
@class_method = class_method
|
23
58
|
original_method # ensure memoized method is the original not the overridden one
|
24
59
|
end
|
25
60
|
|
@@ -27,59 +62,55 @@ module Delfos
|
|
27
62
|
record_method! { setup }
|
28
63
|
end
|
29
64
|
|
30
|
-
|
65
|
+
|
66
|
+
# Redefine the method at runtime to enabling logging to Neo4j
|
31
67
|
def setup
|
32
|
-
processor = method(:process)
|
33
68
|
cm = class_method
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
69
|
+
with_stack = method(:with_stack)
|
70
|
+
method_name = name
|
71
|
+
om = original_method
|
72
|
+
|
73
|
+
mod = module_definition(klass, name, class_method) do
|
74
|
+
define_method(method_name) do |*args, **kw_args, &block|
|
75
|
+
stack = caller.dup
|
76
|
+
caller_binding = binding.dup
|
77
|
+
parameters = Delfos::MethodLogging::MethodParameters.new(args, kw_args, block)
|
78
|
+
|
79
|
+
call_site = Delfos::MethodLogging::CodeLocation.from_call_site(stack, caller_binding)
|
80
|
+
|
81
|
+
if call_site
|
82
|
+
Delfos::MethodLogging.log(call_site, self, om, cm, parameters)
|
83
|
+
end
|
84
|
+
|
85
|
+
with_stack.call(call_site) do
|
86
|
+
if !kw_args.empty?
|
87
|
+
super(*args, **kw_args, &block)
|
88
|
+
else
|
89
|
+
super(*args, &block)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
49
93
|
end
|
50
|
-
|
94
|
+
return unless mod
|
51
95
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
yield
|
96
|
+
if class_method
|
97
|
+
klass.prepend mod
|
98
|
+
else
|
99
|
+
klass.instance_eval { prepend mod }
|
57
100
|
end
|
58
101
|
end
|
59
102
|
|
60
|
-
|
61
103
|
def with_stack(call_site)
|
62
104
|
return yield unless call_site
|
63
105
|
|
64
106
|
begin
|
65
|
-
|
107
|
+
CallStack.push(call_site)
|
66
108
|
yield
|
67
109
|
ensure
|
68
|
-
|
110
|
+
CallStack.pop
|
69
111
|
end
|
70
112
|
end
|
71
113
|
|
72
|
-
MethodArguments = Struct.new(:args, :keyword_args, :block, :class_method) do
|
73
|
-
def apply_to(method)
|
74
|
-
if keyword_args.empty?
|
75
|
-
method.call(*args, &block)
|
76
|
-
else
|
77
|
-
method.call(*args, **keyword_args, &block)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
|
83
114
|
def original_method
|
84
115
|
@original_method ||= if class_method
|
85
116
|
klass.method(name)
|
@@ -92,22 +123,9 @@ module Delfos
|
|
92
123
|
|
93
124
|
def record_method!
|
94
125
|
return true if bail?
|
95
|
-
|
126
|
+
MethodCache.append(klass: klass, method: original_method)
|
96
127
|
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
def method_selector(instance)
|
101
|
-
if class_method
|
102
|
-
m = Delfos::MethodLogging::AddedMethods.find(instance, "ClassMethod_#{name}")
|
103
|
-
m.receiver == instance ? m : m.unbind.bind(instance)
|
104
|
-
else
|
105
|
-
original_method.bind(instance)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def method_defining_method
|
110
|
-
class_method ? klass.method(:define_singleton_method) : klass.method(:define_method)
|
128
|
+
yield
|
111
129
|
end
|
112
130
|
|
113
131
|
def bail?
|
@@ -115,7 +133,7 @@ module Delfos
|
|
115
133
|
end
|
116
134
|
|
117
135
|
def method_has_been_added?
|
118
|
-
|
136
|
+
MethodCache.find(klass: klass, class_method: class_method, method_name: name)
|
119
137
|
end
|
120
138
|
|
121
139
|
def private_method?
|
@@ -123,15 +141,7 @@ module Delfos
|
|
123
141
|
end
|
124
142
|
|
125
143
|
def exclude?
|
126
|
-
::Delfos.
|
127
|
-
end
|
128
|
-
|
129
|
-
def key
|
130
|
-
"#{type}_#{name}"
|
131
|
-
end
|
132
|
-
|
133
|
-
def type
|
134
|
-
class_method ? "ClassMethod" : "InstanceMethod"
|
144
|
+
::Delfos::MethodLogging.exclude?(original_method)
|
135
145
|
end
|
136
146
|
end
|
137
147
|
end
|