ocelot 0.0.0

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.
data/lib/ocelot.rb ADDED
@@ -0,0 +1,90 @@
1
+ module Ocelot
2
+ class Base
3
+
4
+ def connection
5
+ Ocelot.connection
6
+ end
7
+
8
+ def in?(*args)
9
+ Ocelot.in?(*args)
10
+ end
11
+
12
+ def remove_callbacks(*args)
13
+ Ocelot.remove_callbacks(*args)
14
+ end
15
+
16
+ def logger(*args)
17
+ Ocelot.logger(*args)
18
+ end
19
+
20
+ def obj_to_s(*args)
21
+ Ocelot.obj_to_s(*args)
22
+ end
23
+
24
+ def safely(*args, &b)
25
+ Ocelot.safely(*args) do |*bargs|
26
+ b.call(*bargs)
27
+ end
28
+ end
29
+ end
30
+
31
+ require 'ocelot/graph'
32
+ require 'ocelot/dsl'
33
+ require 'ocelot/rules'
34
+ require 'ocelot/database'
35
+ require 'ocelot/processor'
36
+
37
+ class << self
38
+ attr_accessor :connection
39
+ attr_accessor :logger
40
+
41
+ def obj_to_s(obj, assoc=nil)
42
+ assoc = assoc.nil? ? "" : ".#{assoc.name}"
43
+ "#{obj.class}(#{obj.id})#{assoc}"
44
+ end
45
+
46
+ def safely(obj, assoc=nil, val=nil)
47
+ begin
48
+ yield
49
+ rescue => e
50
+ target = obj.is_a?(Class) ? "#{obj}" : "#{obj_to_s(obj)}"
51
+
52
+ if assoc.nil?
53
+ logger.error "Could not read or write #{target}: #{e.message}"
54
+ elsif val.nil?
55
+ logger.error "Could not read data for #{target}.#{assoc}: #{e.message}"
56
+ else
57
+ logger.error "Could not write #{target}.#{assoc} adding #{val.class}: #{e.message}"
58
+ end
59
+ end
60
+ end
61
+
62
+ # this should really be a class-id pair store instead of the entire object,
63
+ # /maybe/
64
+ def in?(set, node)
65
+ result = set.include? node
66
+ set << node
67
+ result
68
+ end
69
+
70
+ def remove_callbacks(obj)
71
+ if obj.class.respond_to? :delete_observers
72
+ obj.class.delete_observers
73
+ end
74
+
75
+ class << obj
76
+ def valid?
77
+ true
78
+ end
79
+ end
80
+
81
+ ActiveRecord::Callbacks::CALLBACKS.each do |cb|
82
+ class << obj
83
+ def run_callbacks(*goaway)
84
+ true
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
data/lib/ocelot/cli.rb ADDED
@@ -0,0 +1,9 @@
1
+ # Rails wants to include AR first.
2
+ puts "Loading Rails environment..."
3
+ require File.join(ARGV[0], 'environment')
4
+
5
+ require 'ocelot/dsl'
6
+
7
+ go = Ocelot::Dsl.new
8
+ go.instance_eval File.read(ARGV[1]), ARGV[1]
9
+ go.go
@@ -0,0 +1,41 @@
1
+ require 'thread'
2
+ require 'active_record'
3
+ require 'active_record/connection_adapters/mysql2_adapter'
4
+ require 'active_record/connection_adapters/abstract/connection_pool'
5
+
6
+ module Ocelot
7
+ class Connection
8
+ attr_accessor :source
9
+ attr_accessor :target
10
+ attr_accessor :use_source
11
+
12
+ def initialize(source, target)
13
+ self.source = ActiveRecord::Base.mysql2_connection(source)
14
+ self.target = ActiveRecord::Base.mysql2_connection(target)
15
+ self.use_source = true
16
+
17
+ # ar doesn't like oop much, monkeypatch lots o stuff
18
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.class_variable_set(:@@conn, self)
19
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
20
+ def retrieve_connection(klass)
21
+ @@conn.current_connection
22
+ end
23
+ end
24
+
25
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
26
+ attr_reader :last_query
27
+
28
+ def log_with_last_query(sql, name, &block)
29
+ @last_query = [sql, name]
30
+ log_without_last_query(sql, name, &block)
31
+ end
32
+
33
+ alias_method_chain :log, :last_query
34
+ end
35
+ end
36
+
37
+ def current_connection
38
+ self.use_source ? source : target
39
+ end
40
+ end
41
+ end
data/lib/ocelot/dsl.rb ADDED
@@ -0,0 +1,146 @@
1
+ require 'ocelot'
2
+ require 'ocelot/rules'
3
+
4
+ module Ocelot
5
+ class Dsl
6
+ attr_accessor :rules
7
+ attr_accessor :filters
8
+
9
+ def initialize
10
+ @rules = {}
11
+ @filters = {}
12
+ @seeds = lambda { [] }
13
+ @logger = Logger.new(STDERR)
14
+ end
15
+
16
+ def source(source=nil)
17
+ @source = source unless source.nil?
18
+ @source
19
+ end
20
+
21
+ def seeds(&seeds)
22
+ @seeds = seeds unless seeds.nil?
23
+ @seeds
24
+ end
25
+
26
+ def target(target=nil)
27
+ @target = target unless target.nil?
28
+ @target
29
+ end
30
+
31
+ def classes(classes=nil)
32
+ @classes = classes.split(/\s+/) unless classes.nil?
33
+ @classes
34
+ end
35
+
36
+ def logger
37
+ @logger
38
+ end
39
+
40
+ def log_level(level=nil)
41
+ @logger.level = eval "Logger::#{level}" unless level.nil?
42
+ level
43
+ end
44
+
45
+ def init(&p)
46
+ @init = p unless p.nil?
47
+ @init
48
+ end
49
+
50
+ def rule(clazz, &p)
51
+ @rules[clazz] = ScriptRule.new(p)
52
+ end
53
+
54
+ def filter(clazz, &p)
55
+ @filters[clazz] = ScriptFilter.new(p)
56
+ end
57
+
58
+ def go
59
+ Ocelot::logger = logger
60
+ Ocelot::connection = Ocelot::Connection.new(source, target)
61
+
62
+ unless init.nil?
63
+ Ocelot.connection.use_source = false
64
+ init.call
65
+ Ocelot.connection.use_source = true
66
+ end
67
+
68
+ copier = Ocelot::Processor.new
69
+ copier.classes = classes.collect { |c| eval c }
70
+ copier.seeds = seeds.call.flatten
71
+ copier.extra_rules = rules
72
+ copier.extra_filters = filters
73
+
74
+ if true
75
+ copier.go
76
+ else
77
+ copier_thread = Thread.new(copier) do
78
+ copier.go
79
+ end
80
+ copier_thread.run
81
+ copier_thread.set_trace_func proc { |*args| copier.watcher.trace(*args) }
82
+ last_warning = nil
83
+
84
+ # pole status
85
+ while !copier_thread.status.nil? and copier_thread.status != false
86
+ copier_thread.join(30)
87
+ warning = copier.watcher.ping
88
+ unless warning.nil? or last_warning == warning
89
+ logger.warn warning
90
+ last_warning = warning
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ class ScriptFilter < Ocelot::Rules::Filter
98
+ alias :old_load :load
99
+ def initialize(p)
100
+ @p = p
101
+ end
102
+
103
+ def load(obj, collection, name)
104
+ @obj, @collection, @name = obj, collection, name
105
+
106
+ @result = nil
107
+ instance_exec(obj, collection, name, &@p)
108
+ @result || default
109
+ end
110
+
111
+ def nothing
112
+ @result = []
113
+ end
114
+
115
+ def result(val)
116
+ @result = val
117
+ end
118
+
119
+ def default
120
+ @result = old_load @obj, @collection, @name
121
+ end
122
+ end
123
+
124
+ class ScriptRule < Ocelot::Rules::DefaultRule
125
+ alias :old_save :save
126
+
127
+ def initialize(p)
128
+ @p = p
129
+ end
130
+
131
+ def inspect
132
+ @inspect = true
133
+ end
134
+
135
+ def persist(obj)
136
+ old_save obj
137
+ end
138
+
139
+ def save(obj, *args)
140
+ @inspect = false
141
+ args = [obj, *args]
142
+ instance_exec(*args, &@p)
143
+ return @inspect
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,14 @@
1
+ module Ocelot
2
+ module Graph
3
+ class Traverser
4
+ def self.run(queue)
5
+ while !queue.empty?
6
+ snapshot, queue = queue.uniq, []
7
+ snapshot.each do |node|
8
+ yield node, queue
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,182 @@
1
+ module Ocelot
2
+ class Watcher
3
+ attr_accessor :op
4
+ attr_accessor :association
5
+ attr_accessor :sql
6
+ attr_accessor :line
7
+ attr_accessor :rule
8
+ attr_reader :last
9
+ attr_reader :obj
10
+
11
+ def initialize
12
+ @last = Time.now
13
+ @mutex = Mutex.new
14
+ end
15
+
16
+ def obj=(obj)
17
+ @obj = Ocelot::obj_to_s(obj)
18
+ end
19
+
20
+ def ping
21
+ sync do |w|
22
+ "Might be stuck processing #{w.obj}#{w.association.nil? ? "" : "."}#{w.association} at #{w.line} using #{w.rule}, may be exec'ing #{ActiveRecord::Base.connection.last_query}" if Time.now - w.last > 20
23
+ end
24
+ end
25
+
26
+ def sync
27
+ @mutex.synchronize do
28
+ yield self
29
+ end
30
+ end
31
+
32
+ def trace(event, file, line, id, binding, clazz)
33
+ if clazz == Ocelot::Processor
34
+ @last = Time.now
35
+ @line = line
36
+ end
37
+ end
38
+ end
39
+
40
+ class Processor < Base
41
+ attr_accessor :seeds
42
+ attr_accessor :classes
43
+ attr_accessor :extra_rules
44
+ attr_accessor :extra_filters
45
+ attr_reader :watcher
46
+ attr_reader :saved
47
+
48
+ def initialize
49
+ @extra_rules = {}
50
+ @extra_filters = {}
51
+ @seeds = []
52
+ @unrecognised = Set.new
53
+ @watcher = Watcher.new
54
+ end
55
+
56
+ def process_filter(params)
57
+ obj = params.delete :obj
58
+ collection = params.delete :collection
59
+ name = params.delete :name
60
+
61
+ result = nil
62
+ filter = @filters[obj.class]
63
+
64
+ if !filter.nil?
65
+ watcher.sync do |w|
66
+ w.obj = obj
67
+ w.association = name
68
+ w.rule = filter
69
+ end
70
+
71
+ logger.info "Filtering #{obj_to_s(obj)}.#{name} via #{filter.class}"
72
+ result = filter.load(obj, collection, name)
73
+ elsif !@unrecognised.include? obj.class
74
+ logger.error "Unrecognised type #{obj.class}"
75
+ @unrecognised << obj.class
76
+ end
77
+ result || []
78
+ end
79
+
80
+ def process_rule(params)
81
+ obj = params.delete :obj
82
+ src = params.delete :src
83
+ target = params.delete :target
84
+ calller = params.delete :caller
85
+
86
+ rule = @rules[obj.class]
87
+
88
+ watcher.sync do |w|
89
+ w.obj = obj
90
+ w.association = nil
91
+ w.rule = rule
92
+ end
93
+
94
+ if !rule.nil?
95
+ logger.info "Saving #{obj_to_s(obj)} thru #{rule.class} via #{calller}"
96
+ return rule.save(obj, src, target)
97
+ elsif !@unrecognised.include? obj.class
98
+ logger.warn "Unrecognised type #{obj.class}"
99
+ @unrecognised << obj.class
100
+ end
101
+ end
102
+
103
+ def go
104
+ default_rule = Rules::DefaultRule.new
105
+ default_filter = Rules::Filter.new
106
+
107
+ @rules = Hash.new
108
+ @filters = Hash.new
109
+ @saved = Set.new
110
+ @inspected = Set.new
111
+
112
+ array_rule = ArrayRule.new
113
+
114
+ @classes.each { |c| @rules[c] = default_rule; @filters[c] = default_filter }
115
+ @rules.merge! @extra_rules
116
+ @filters.merge! @extra_filters
117
+ seeds = @seeds.collect { |o| [o, "script seed"] }
118
+
119
+ Graph::Traverser.run(seeds) do |objs, queue|
120
+ obj, calller = *objs
121
+
122
+ unless in? @saved, obj
123
+ @inspected << obj unless process_rule(:obj => obj, :caller => calller)
124
+ end
125
+
126
+ unless in? @inspected, obj
127
+ obj.class.reflect_on_all_associations.each do |assoc|
128
+ safely obj do
129
+ connection.use_source = true
130
+ value = obj.send(assoc.name)
131
+
132
+ unless value.nil?
133
+
134
+ # simple has_one (A->B handling), save B and conditionally inspect
135
+ if !assoc.collection?
136
+ unless in? @saved, value
137
+ process_rule(:obj => value, :caller => calller) and (queue << [value, obj_to_s(obj, assoc)])
138
+ end
139
+ # more complicated has many (A-B* and A-mapobj-B*), saving and inspecting each child
140
+ # monkeypatching the array_rule must yield B and whether to inspect it
141
+ # be very very very careful, as it handles saving objects whie yielding results
142
+ else
143
+ array_rule.save(obj, value, assoc, self) do |child, inspect|
144
+ @saved << child
145
+ if inspect
146
+ queue << [child, obj_to_s(obj, assoc)]
147
+ else
148
+ @inspected << child
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ class ArrayRule < Rules::Rule
160
+ include Ocelot
161
+
162
+ def save(obj, array, assoc, c)
163
+ safely obj, assoc.name do
164
+ c.process_filter(:obj => obj, :collection => array, :name => assoc.name).each do |val|
165
+ unless in? c.saved, val
166
+ use_source_db!
167
+ inspect = c.process_rule(:obj => val, :caller => obj_to_s(obj))
168
+ yield val, inspect
169
+ end
170
+
171
+ use_target_db!
172
+ if assoc.is_a? ActiveRecord::Reflection::ThroughReflection
173
+ safely assoc.through_reflection.klass, assoc.name, val do
174
+ c.process_rule :obj => assoc.through_reflection.klass.new, :src => obj, :target => val, :caller => obj_to_s(obj,assoc)
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,53 @@
1
+ module Ocelot
2
+ module Rules
3
+ class Filter < Ocelot::Base
4
+ def filter(value)
5
+ true
6
+ end
7
+
8
+ def load(obj, collection, name)
9
+ result = []
10
+ collection.each do |val|
11
+ safely(obj, name) do
12
+ result << val if filter val
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+
19
+ class Rule < Base
20
+ def relationships(obj)
21
+ obj.class.reflect_on_all_associations.collect { |assoc| assoc.name }
22
+ end
23
+
24
+ def use_target_db!
25
+ Ocelot::connection.use_source = false
26
+ end
27
+
28
+ def use_source_db!
29
+ Ocelot::connection.use_source = true
30
+ end
31
+
32
+ def save_to_db(obj, *extra)
33
+ begin
34
+ use_target_db!
35
+ obj.save(false)
36
+ ensure
37
+ use_source_db!
38
+ end
39
+ end
40
+ end
41
+
42
+ class DefaultRule < Rule
43
+ def save(obj, *extra)
44
+ id = obj.id
45
+ obj = obj.clone
46
+ obj.id = id
47
+ remove_callbacks(obj)
48
+ save_to_db(obj)
49
+ true
50
+ end
51
+ end
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ocelot
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.0
6
+ platform: ruby
7
+ authors:
8
+ - spencer p
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-17 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Extreme alpha
18
+ email:
19
+ - exussum@gmail.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - lib/ocelot/cli.rb
28
+ - lib/ocelot/database.rb
29
+ - lib/ocelot/dsl.rb
30
+ - lib/ocelot/graph.rb
31
+ - lib/ocelot/processor.rb
32
+ - lib/ocelot/rules.rb
33
+ - lib/ocelot.rb
34
+ has_rdoc: true
35
+ homepage: ""
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.6.2
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: DBA like tool to copy a slice of data from one db to another
62
+ test_files: []
63
+