ocelot 0.0.0

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