andyjeffries-rubyrep 1.2.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.
- data/History.txt +83 -0
- data/License.txt +20 -0
- data/Manifest.txt +151 -0
- data/README.txt +37 -0
- data/bin/rubyrep +8 -0
- data/lib/rubyrep.rb +72 -0
- data/lib/rubyrep/base_runner.rb +195 -0
- data/lib/rubyrep/command_runner.rb +144 -0
- data/lib/rubyrep/committers/buffered_committer.rb +151 -0
- data/lib/rubyrep/committers/committers.rb +152 -0
- data/lib/rubyrep/configuration.rb +275 -0
- data/lib/rubyrep/connection_extenders/connection_extenders.rb +165 -0
- data/lib/rubyrep/connection_extenders/jdbc_extender.rb +65 -0
- data/lib/rubyrep/connection_extenders/mysql_extender.rb +59 -0
- data/lib/rubyrep/connection_extenders/postgresql_extender.rb +277 -0
- data/lib/rubyrep/database_proxy.rb +52 -0
- data/lib/rubyrep/direct_table_scan.rb +75 -0
- data/lib/rubyrep/generate_runner.rb +105 -0
- data/lib/rubyrep/initializer.rb +39 -0
- data/lib/rubyrep/log_helper.rb +30 -0
- data/lib/rubyrep/logged_change.rb +160 -0
- data/lib/rubyrep/logged_change_loader.rb +197 -0
- data/lib/rubyrep/noisy_connection.rb +80 -0
- data/lib/rubyrep/proxied_table_scan.rb +171 -0
- data/lib/rubyrep/proxy_block_cursor.rb +145 -0
- data/lib/rubyrep/proxy_connection.rb +431 -0
- data/lib/rubyrep/proxy_cursor.rb +44 -0
- data/lib/rubyrep/proxy_row_cursor.rb +43 -0
- data/lib/rubyrep/proxy_runner.rb +89 -0
- data/lib/rubyrep/replication_difference.rb +100 -0
- data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
- data/lib/rubyrep/replication_extenders/postgresql_replication.rb +236 -0
- data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
- data/lib/rubyrep/replication_helper.rb +142 -0
- data/lib/rubyrep/replication_initializer.rb +327 -0
- data/lib/rubyrep/replication_run.rb +142 -0
- data/lib/rubyrep/replication_runner.rb +166 -0
- data/lib/rubyrep/replicators/replicators.rb +42 -0
- data/lib/rubyrep/replicators/two_way_replicator.rb +361 -0
- data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
- data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
- data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
- data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
- data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
- data/lib/rubyrep/scan_runner.rb +25 -0
- data/lib/rubyrep/session.rb +230 -0
- data/lib/rubyrep/sync_helper.rb +121 -0
- data/lib/rubyrep/sync_runner.rb +31 -0
- data/lib/rubyrep/syncers/syncers.rb +112 -0
- data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
- data/lib/rubyrep/table_scan.rb +54 -0
- data/lib/rubyrep/table_scan_helper.rb +46 -0
- data/lib/rubyrep/table_sorter.rb +70 -0
- data/lib/rubyrep/table_spec_resolver.rb +142 -0
- data/lib/rubyrep/table_sync.rb +90 -0
- data/lib/rubyrep/task_sweeper.rb +77 -0
- data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
- data/lib/rubyrep/type_casting_cursor.rb +31 -0
- data/lib/rubyrep/uninstall_runner.rb +93 -0
- data/lib/rubyrep/version.rb +9 -0
- data/rubyrep +8 -0
- data/rubyrep.bat +4 -0
- data/setup.rb +1585 -0
- data/spec/base_runner_spec.rb +218 -0
- data/spec/buffered_committer_spec.rb +274 -0
- data/spec/command_runner_spec.rb +145 -0
- data/spec/committers_spec.rb +178 -0
- data/spec/configuration_spec.rb +203 -0
- data/spec/connection_extender_interface_spec.rb +141 -0
- data/spec/connection_extenders_registration_spec.rb +164 -0
- data/spec/database_proxy_spec.rb +48 -0
- data/spec/database_rake_spec.rb +40 -0
- data/spec/db_specific_connection_extenders_spec.rb +34 -0
- data/spec/db_specific_replication_extenders_spec.rb +38 -0
- data/spec/direct_table_scan_spec.rb +61 -0
- data/spec/dolphins.jpg +0 -0
- data/spec/generate_runner_spec.rb +84 -0
- data/spec/initializer_spec.rb +46 -0
- data/spec/log_helper_spec.rb +39 -0
- data/spec/logged_change_loader_spec.rb +68 -0
- data/spec/logged_change_spec.rb +470 -0
- data/spec/noisy_connection_spec.rb +78 -0
- data/spec/postgresql_replication_spec.rb +48 -0
- data/spec/postgresql_schema_support_spec.rb +212 -0
- data/spec/postgresql_support_spec.rb +63 -0
- data/spec/progress_bar_spec.rb +77 -0
- data/spec/proxied_table_scan_spec.rb +151 -0
- data/spec/proxy_block_cursor_spec.rb +197 -0
- data/spec/proxy_connection_spec.rb +423 -0
- data/spec/proxy_cursor_spec.rb +56 -0
- data/spec/proxy_row_cursor_spec.rb +66 -0
- data/spec/proxy_runner_spec.rb +70 -0
- data/spec/replication_difference_spec.rb +161 -0
- data/spec/replication_extender_interface_spec.rb +367 -0
- data/spec/replication_extenders_spec.rb +32 -0
- data/spec/replication_helper_spec.rb +178 -0
- data/spec/replication_initializer_spec.rb +509 -0
- data/spec/replication_run_spec.rb +443 -0
- data/spec/replication_runner_spec.rb +254 -0
- data/spec/replicators_spec.rb +36 -0
- data/spec/rubyrep_spec.rb +8 -0
- data/spec/scan_detail_reporter_spec.rb +119 -0
- data/spec/scan_progress_printers_spec.rb +68 -0
- data/spec/scan_report_printers_spec.rb +67 -0
- data/spec/scan_runner_spec.rb +50 -0
- data/spec/scan_summary_reporter_spec.rb +61 -0
- data/spec/session_spec.rb +253 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +305 -0
- data/spec/strange_name_support_spec.rb +135 -0
- data/spec/sync_helper_spec.rb +169 -0
- data/spec/sync_runner_spec.rb +78 -0
- data/spec/syncers_spec.rb +171 -0
- data/spec/table_scan_helper_spec.rb +36 -0
- data/spec/table_scan_spec.rb +49 -0
- data/spec/table_sorter_spec.rb +30 -0
- data/spec/table_spec_resolver_spec.rb +111 -0
- data/spec/table_sync_spec.rb +140 -0
- data/spec/task_sweeper_spec.rb +47 -0
- data/spec/trigger_mode_switcher_spec.rb +83 -0
- data/spec/two_way_replicator_spec.rb +721 -0
- data/spec/two_way_syncer_spec.rb +256 -0
- data/spec/type_casting_cursor_spec.rb +50 -0
- data/spec/uninstall_runner_spec.rb +93 -0
- metadata +190 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/..'
|
|
2
|
+
|
|
3
|
+
require 'drb'
|
|
4
|
+
|
|
5
|
+
require 'rubyrep'
|
|
6
|
+
require 'forwardable'
|
|
7
|
+
|
|
8
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
|
9
|
+
|
|
10
|
+
module RR
|
|
11
|
+
|
|
12
|
+
# Enables the fetching of (potential large) result sets in chunks.
|
|
13
|
+
class ResultFetcher
|
|
14
|
+
|
|
15
|
+
# The current database ProxyConnection
|
|
16
|
+
attr_accessor :connection
|
|
17
|
+
|
|
18
|
+
# hash of select options as described under ProxyConnection#select_cursor
|
|
19
|
+
attr_accessor :options
|
|
20
|
+
|
|
21
|
+
# column_name => value hash of the last returned row
|
|
22
|
+
attr_accessor :last_row
|
|
23
|
+
|
|
24
|
+
# The current row set: an array of column_name => value hashes
|
|
25
|
+
attr_accessor :rows
|
|
26
|
+
|
|
27
|
+
# Index to the current row in rows
|
|
28
|
+
attr_accessor :current_row_index
|
|
29
|
+
|
|
30
|
+
# Creates a new fetcher.
|
|
31
|
+
# * +connection+: the current ProxyConnection
|
|
32
|
+
# * +options+: hash of select options as described under ProxyConnection#select_cursor
|
|
33
|
+
def initialize(connection, options)
|
|
34
|
+
self.connection = connection
|
|
35
|
+
self.options = options.clone
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns +true+ if there are more rows to read.
|
|
39
|
+
def next?
|
|
40
|
+
unless self.rows
|
|
41
|
+
# Try to load some records
|
|
42
|
+
|
|
43
|
+
if options[:query] and last_row != nil
|
|
44
|
+
# A query was directly specified and all it's rows were returned
|
|
45
|
+
# ==> Finished.
|
|
46
|
+
return false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if options[:query]
|
|
50
|
+
# If a query has been directly specified, just directly execute it
|
|
51
|
+
query = options[:query]
|
|
52
|
+
else
|
|
53
|
+
# Otherwise build the query
|
|
54
|
+
if last_row
|
|
55
|
+
# There was a previous batch.
|
|
56
|
+
# Next batch will start after the last returned row
|
|
57
|
+
options.merge! :from => last_row, :exclude_starting_row => true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
query = connection.table_select_query(options[:table], options)
|
|
61
|
+
|
|
62
|
+
if options[:row_buffer_size]
|
|
63
|
+
# Set the batch size
|
|
64
|
+
query += " limit #{options[:row_buffer_size]}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
self.rows = connection.select_all query
|
|
69
|
+
self.current_row_index = 0
|
|
70
|
+
end
|
|
71
|
+
self.current_row_index < self.rows.size
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns the row as a column => value hash and moves the cursor to the next row.
|
|
75
|
+
def next_row
|
|
76
|
+
raise("no more rows available") unless next?
|
|
77
|
+
self.last_row = self.rows[self.current_row_index]
|
|
78
|
+
self.current_row_index += 1
|
|
79
|
+
|
|
80
|
+
if self.current_row_index == self.rows.size
|
|
81
|
+
self.rows = nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
self.last_row
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Frees up all ressources
|
|
88
|
+
def clear
|
|
89
|
+
self.rows = nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# This class represents a remote activerecord database connection.
|
|
94
|
+
# Normally created by DatabaseProxy
|
|
95
|
+
class ProxyConnection
|
|
96
|
+
# Ensure that the proxy object always stays on server side and only remote
|
|
97
|
+
# references are returned to the client.
|
|
98
|
+
include DRbUndumped
|
|
99
|
+
|
|
100
|
+
extend Forwardable
|
|
101
|
+
|
|
102
|
+
# The database connection
|
|
103
|
+
attr_accessor :connection
|
|
104
|
+
|
|
105
|
+
# A hash as described by ActiveRecord::Base#establish_connection
|
|
106
|
+
attr_accessor :config
|
|
107
|
+
|
|
108
|
+
# Forward certain methods to the proxied database connection
|
|
109
|
+
def_delegators :connection,
|
|
110
|
+
:columns, :quote_column_name,
|
|
111
|
+
:quote_table_name, :execute,
|
|
112
|
+
:select_one, :select_all, :tables, :update, :delete,
|
|
113
|
+
:begin_db_transaction, :rollback_db_transaction, :commit_db_transaction,
|
|
114
|
+
:referenced_tables,
|
|
115
|
+
:create_or_replace_replication_trigger_function,
|
|
116
|
+
:create_replication_trigger, :drop_replication_trigger, :replication_trigger_exists?,
|
|
117
|
+
:sequence_values, :update_sequences, :clear_sequence_setup,
|
|
118
|
+
:drop_table, :add_big_primary_key, :add_column, :remove_column
|
|
119
|
+
|
|
120
|
+
# Caching the primary keys. This is a hash with
|
|
121
|
+
# * key: table name
|
|
122
|
+
# * value: array of primary key names
|
|
123
|
+
attr_accessor :primary_key_names_cache
|
|
124
|
+
|
|
125
|
+
# Hash to register cursors.
|
|
126
|
+
# Purpose:
|
|
127
|
+
# Objects only referenced remotely via DRb can be garbage collected.
|
|
128
|
+
# We register them in this hash to protect them from unintended garbage collection.
|
|
129
|
+
attr_accessor :cursors
|
|
130
|
+
|
|
131
|
+
# 2-level Hash of table_name => column_name => Column objects.
|
|
132
|
+
attr_accessor :table_columns
|
|
133
|
+
|
|
134
|
+
# Hash of table_name => array of column names pairs.
|
|
135
|
+
attr_accessor :table_column_names
|
|
136
|
+
|
|
137
|
+
# A hash of manually overwritten primary keys:
|
|
138
|
+
# * key: table_name
|
|
139
|
+
# * value: array of primary key names
|
|
140
|
+
attr_accessor :manual_primary_keys
|
|
141
|
+
|
|
142
|
+
# Returns an array of primary key names for the given +table_name+.
|
|
143
|
+
# Caches the result for future calls. Allows manual overwrites through
|
|
144
|
+
# the Configuration options +:primary_key_names+ or :+primary_key_only_limit+.
|
|
145
|
+
#
|
|
146
|
+
# Parameters:
|
|
147
|
+
# * +table_name+: name of the table
|
|
148
|
+
# * +options+: An option hash with the following valid options:
|
|
149
|
+
# * :+raw+: if +true+, than don't use manual overwrites and don't cache
|
|
150
|
+
def primary_key_names(table_name, options = {})
|
|
151
|
+
return connection.primary_key_names(table_name) if options[:raw]
|
|
152
|
+
|
|
153
|
+
self.primary_key_names_cache ||= {}
|
|
154
|
+
result = primary_key_names_cache[table_name]
|
|
155
|
+
unless result
|
|
156
|
+
result = manual_primary_keys[table_name] || connection.primary_key_names(table_name)
|
|
157
|
+
primary_key_names_cache[table_name] = result
|
|
158
|
+
end
|
|
159
|
+
result
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Creates a table
|
|
163
|
+
# Call forwarded to ActiveRecord::ConnectionAdapters::SchemaStatements#create_table
|
|
164
|
+
# Provides an empty block (to prevent DRB from calling back the client)
|
|
165
|
+
def create_table(*params)
|
|
166
|
+
connection.create_table(*params) {}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Returns a Hash of currently registerred cursors
|
|
170
|
+
def cursors
|
|
171
|
+
@cursors ||= {}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Store a cursor in the register to protect it from the garbage collector.
|
|
175
|
+
def save_cursor(cursor)
|
|
176
|
+
cursors[cursor] = cursor
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Returns a cusor as produced by the #select_cursor method of the connection
|
|
180
|
+
# extenders.
|
|
181
|
+
#
|
|
182
|
+
# Two modes of operation: Either
|
|
183
|
+
# * execute the specified query (takes precedense) OR
|
|
184
|
+
# * first build the query based on options forwarded to #table_select_query
|
|
185
|
+
# +options+ is a hash with
|
|
186
|
+
# * :+query+: executes the given query
|
|
187
|
+
# * :+type_cast+:
|
|
188
|
+
# Unless explicitely disabled with +false+, build type casting cursor
|
|
189
|
+
# around result.
|
|
190
|
+
# * :+table+:
|
|
191
|
+
# Name of the table from which to read data.
|
|
192
|
+
# Required unless type casting is disabled.
|
|
193
|
+
# * further options as taken by #table_select_query to build the query
|
|
194
|
+
# * :+row_buffer_size+:
|
|
195
|
+
# Integer controlling how many rows a read into memory at one time.
|
|
196
|
+
def select_cursor(options)
|
|
197
|
+
cursor = ResultFetcher.new(self, options)
|
|
198
|
+
unless options[:type_cast] == false
|
|
199
|
+
cursor = TypeCastingCursor.new(self, options[:table], cursor)
|
|
200
|
+
end
|
|
201
|
+
cursor
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Reads the designated record from the database.
|
|
205
|
+
# Refer to #select_cursor for details parameter description.
|
|
206
|
+
# Returns the first matching row (column_name => value hash or +nil+).
|
|
207
|
+
def select_record(options)
|
|
208
|
+
cursor = select_cursor({:row_buffer_size => 1}.merge(options))
|
|
209
|
+
row = cursor.next? ? cursor.next_row : nil
|
|
210
|
+
cursor.clear
|
|
211
|
+
row
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Reads the designated records from the database.
|
|
215
|
+
# Refer to #select_cursor for details parameter description.
|
|
216
|
+
# Returns an array of matching rows (column_name => value hashes).
|
|
217
|
+
def select_records(options)
|
|
218
|
+
cursor = select_cursor(options)
|
|
219
|
+
rows = []
|
|
220
|
+
while cursor.next?
|
|
221
|
+
rows << cursor.next_row
|
|
222
|
+
end
|
|
223
|
+
cursor.clear
|
|
224
|
+
rows
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Create a session on the proxy side according to provided configuration hash.
|
|
228
|
+
# +config+ is a hash as described by ActiveRecord::Base#establish_connection
|
|
229
|
+
def initialize(config)
|
|
230
|
+
self.connection = ConnectionExtenders.db_connect config
|
|
231
|
+
self.config = config
|
|
232
|
+
self.manual_primary_keys = {}
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Destroys the session
|
|
236
|
+
def destroy
|
|
237
|
+
cursors.each_key do |cursor|
|
|
238
|
+
cursor.destroy
|
|
239
|
+
end
|
|
240
|
+
cursors.clear
|
|
241
|
+
|
|
242
|
+
if connection.log_subscriber
|
|
243
|
+
ActiveSupport::Notifications.notifier.unsubscribe connection.log_subscriber
|
|
244
|
+
connection.log_subscriber = nil
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
self.connection.disconnect!
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Quotes the given value. It is assumed that the value belongs to the specified column name and table name.
|
|
251
|
+
# Caches the column objects for higher speed.
|
|
252
|
+
def quote_value(table, column, value)
|
|
253
|
+
self.table_columns ||= {}
|
|
254
|
+
unless table_columns.include? table
|
|
255
|
+
table_columns[table] = {}
|
|
256
|
+
columns(table).each {|c| table_columns[table][c.name] = c}
|
|
257
|
+
end
|
|
258
|
+
connection.quote value, table_columns[table][column]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Create a cursor for the given table.
|
|
262
|
+
# * +cursor_class+: should specify the Cursor class (e. g. ProxyBlockCursor or ProxyRowCursor).
|
|
263
|
+
# * +table+: name of the table
|
|
264
|
+
# * +options+: An option hash that is used to construct the SQL query. See ProxyCursor#construct_query for details.
|
|
265
|
+
def create_cursor(cursor_class, table, options = {})
|
|
266
|
+
cursor = cursor_class.new self, table
|
|
267
|
+
cursor.prepare_fetch options
|
|
268
|
+
save_cursor cursor
|
|
269
|
+
cursor
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Destroys the provided cursor and removes it from the register
|
|
273
|
+
def destroy_cursor(cursor)
|
|
274
|
+
cursor.destroy
|
|
275
|
+
cursors.delete cursor
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Returns an array of column names of the given table name.
|
|
279
|
+
# The array is ordered in the sequence as returned by the database.
|
|
280
|
+
# The result is cached for higher speed.
|
|
281
|
+
def column_names(table)
|
|
282
|
+
self.table_column_names ||= {}
|
|
283
|
+
unless table_column_names.include? table
|
|
284
|
+
table_column_names[table] = columns(table).map {|c| c.name}
|
|
285
|
+
end
|
|
286
|
+
table_column_names[table]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Returns a list of quoted column names for the given +table+ as comma
|
|
290
|
+
# separated string.
|
|
291
|
+
def quote_column_list(table)
|
|
292
|
+
column_names(table).map do |column_name|
|
|
293
|
+
quote_column_name(column_name)
|
|
294
|
+
end.join(', ')
|
|
295
|
+
end
|
|
296
|
+
private :quote_column_list
|
|
297
|
+
|
|
298
|
+
# Returns a list of quoted primary key names for the given +table+ as comma
|
|
299
|
+
# separated string.
|
|
300
|
+
def quote_key_list(table)
|
|
301
|
+
primary_key_names(table).map do |column_name|
|
|
302
|
+
quote_column_name(column_name)
|
|
303
|
+
end.join(', ')
|
|
304
|
+
end
|
|
305
|
+
private :quote_key_list
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# Generates an sql condition string for the given +table+ based on
|
|
309
|
+
# * +row+: a hash of primary key => value pairs designating the target row
|
|
310
|
+
# * +condition+: the type of sql condition (something like '>=' or '=', etc.)
|
|
311
|
+
def row_condition(table, row, condition)
|
|
312
|
+
query_part = ""
|
|
313
|
+
query_part << ' (' << quote_key_list(table) << ') ' << condition
|
|
314
|
+
query_part << ' (' << primary_key_names(table).map do |key|
|
|
315
|
+
quote_value(table, key, row[key])
|
|
316
|
+
end.join(', ') << ')'
|
|
317
|
+
query_part
|
|
318
|
+
end
|
|
319
|
+
private :row_condition
|
|
320
|
+
|
|
321
|
+
# Returns an SQL query string for the given +table+ based on the provided +options+.
|
|
322
|
+
# +options+ is a hash that can contain any of the following:
|
|
323
|
+
# * :+from+: nil OR the hash of primary key => value pairs designating the start of the selection
|
|
324
|
+
# * :+exclude_starting_row+: if true, do not include the row specified by :+from+
|
|
325
|
+
# * :+to+: nil OR the hash of primary key => value pairs designating the end of the selection
|
|
326
|
+
# * :+row_keys+: an array of primary key => value hashes specify the target rows.
|
|
327
|
+
def table_select_query(table, options = {})
|
|
328
|
+
query = "select #{quote_column_list(table)}"
|
|
329
|
+
query << " from #{quote_table_name(table)}"
|
|
330
|
+
query << " where" if [:from, :to, :row_keys].any? {|key| options.include? key}
|
|
331
|
+
first_condition = true
|
|
332
|
+
if options[:from]
|
|
333
|
+
first_condition = false
|
|
334
|
+
matching_condition = options[:exclude_starting_row] ? '>' : '>='
|
|
335
|
+
query << row_condition(table, options[:from], matching_condition)
|
|
336
|
+
end
|
|
337
|
+
if options[:to]
|
|
338
|
+
query << ' and' unless first_condition
|
|
339
|
+
first_condition = false
|
|
340
|
+
query << row_condition(table, options[:to], '<=')
|
|
341
|
+
end
|
|
342
|
+
if options[:row_keys]
|
|
343
|
+
query << ' and' unless first_condition
|
|
344
|
+
if options[:row_keys].empty?
|
|
345
|
+
query << ' false'
|
|
346
|
+
else
|
|
347
|
+
query << ' (' << quote_key_list(table) << ') in ('
|
|
348
|
+
first_key = true
|
|
349
|
+
options[:row_keys].each do |row|
|
|
350
|
+
query << ', ' unless first_key
|
|
351
|
+
first_key = false
|
|
352
|
+
query << '(' << primary_key_names(table).map do |key|
|
|
353
|
+
quote_value(table, key, row[key])
|
|
354
|
+
end.join(', ') << ')'
|
|
355
|
+
end
|
|
356
|
+
query << ')'
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
query << " order by #{quote_key_list(table)}"
|
|
360
|
+
|
|
361
|
+
query
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Returns an SQL insert query for the given +table+ and +values+.
|
|
365
|
+
# +values+ is a hash of column_name => value pairs.
|
|
366
|
+
def table_insert_query(table, values)
|
|
367
|
+
query = "insert into #{quote_table_name(table)}"
|
|
368
|
+
query << '(' << values.keys.map do |column_name|
|
|
369
|
+
quote_column_name(column_name)
|
|
370
|
+
end.join(', ') << ') '
|
|
371
|
+
query << 'values(' << values.map do |column_name, value|
|
|
372
|
+
quote_value(table, column_name, value)
|
|
373
|
+
end.join(', ') << ')'
|
|
374
|
+
query
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Inserts the specified records into the named +table+.
|
|
378
|
+
# +values+ is a hash of column_name => value pairs.
|
|
379
|
+
def insert_record(table, values)
|
|
380
|
+
execute table_insert_query(table, values)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Returns an SQL update query.
|
|
384
|
+
# * +table+: name of the target table
|
|
385
|
+
# * +values+: a hash of column_name => value pairs
|
|
386
|
+
# * +org_key+:
|
|
387
|
+
# A hash of column_name => value pairs. If +nil+, use the key specified by
|
|
388
|
+
# +values+ instead.
|
|
389
|
+
def table_update_query(table, values, org_key = nil)
|
|
390
|
+
org_key ||= values
|
|
391
|
+
query = "update #{quote_table_name(table)} set "
|
|
392
|
+
query << values.map do |column_name, value|
|
|
393
|
+
"#{quote_column_name(column_name)} = #{quote_value(table, column_name, value)}"
|
|
394
|
+
end.join(', ')
|
|
395
|
+
query << " where (" << quote_key_list(table) << ") = ("
|
|
396
|
+
query << primary_key_names(table).map do |key|
|
|
397
|
+
quote_value(table, key, org_key[key])
|
|
398
|
+
end.join(', ') << ")"
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Updates the specified records of the specified table.
|
|
402
|
+
# * +table+: name of the target table
|
|
403
|
+
# * +values+: a hash of column_name => value pairs.
|
|
404
|
+
# * +org_key+:
|
|
405
|
+
# A hash of column_name => value pairs. If +nil+, use the key specified by
|
|
406
|
+
# +values+ instead.
|
|
407
|
+
# Returns the number of modified records.
|
|
408
|
+
def update_record(table, values, org_key = nil)
|
|
409
|
+
update table_update_query(table, values, org_key)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Returns an SQL delete query for the given +table+ and +values+
|
|
413
|
+
# +values+ is a hash of column_name => value pairs. (Only the primary key
|
|
414
|
+
# values will be used and must be included in the hash.)
|
|
415
|
+
def table_delete_query(table, values)
|
|
416
|
+
query = "delete from #{quote_table_name(table)}"
|
|
417
|
+
query << " where (" << quote_key_list(table) << ") = ("
|
|
418
|
+
query << primary_key_names(table).map do |key|
|
|
419
|
+
quote_value(table, key, values[key])
|
|
420
|
+
end.join(', ') << ")"
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# Deletes the specified record from the named +table+.
|
|
424
|
+
# +values+ is a hash of column_name => value pairs. (Only the primary key
|
|
425
|
+
# values will be used and must be included in the hash.)
|
|
426
|
+
# Returns the number of deleted records.
|
|
427
|
+
def delete_record(table, values)
|
|
428
|
+
delete table_delete_query(table, values)
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/..'
|
|
2
|
+
|
|
3
|
+
require 'rubyrep'
|
|
4
|
+
|
|
5
|
+
module RR
|
|
6
|
+
# Provides shared functionality for ProxyRowCursor and ProxyBlockCursor
|
|
7
|
+
class ProxyCursor
|
|
8
|
+
|
|
9
|
+
# The current ProxyConnection.
|
|
10
|
+
attr_accessor :connection
|
|
11
|
+
|
|
12
|
+
# The name of the current table.
|
|
13
|
+
attr_accessor :table
|
|
14
|
+
|
|
15
|
+
# Array of primary key names for current table.
|
|
16
|
+
attr_accessor :primary_key_names
|
|
17
|
+
|
|
18
|
+
# The current cursor.
|
|
19
|
+
attr_accessor :cursor
|
|
20
|
+
|
|
21
|
+
# Shared initializations
|
|
22
|
+
# * connection: the current proxy connection
|
|
23
|
+
# * table: table_name
|
|
24
|
+
def initialize(connection, table)
|
|
25
|
+
self.connection = connection
|
|
26
|
+
self.table = table
|
|
27
|
+
self.primary_key_names = connection.primary_key_names table
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Initiate a query for the specified row range.
|
|
31
|
+
# +options+: An option hash that is used to construct the SQL query. See ProxyCursor#construct_query for details.
|
|
32
|
+
def prepare_fetch(options = {})
|
|
33
|
+
self.cursor = connection.select_cursor(
|
|
34
|
+
options.merge(:table => table, :type_cast => true)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Releases all ressources
|
|
39
|
+
def destroy
|
|
40
|
+
self.cursor.clear if self.cursor
|
|
41
|
+
self.cursor = nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|