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,165 @@
|
|
|
1
|
+
class ActiveRecord::ConnectionAdapters::AbstractAdapter
|
|
2
|
+
# The current log subscriber
|
|
3
|
+
attr_accessor :log_subscriber
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class ActiveRecord::ConnectionAdapters::Column
|
|
7
|
+
# Bug in ActiveRecord parsing of PostgreSQL timestamps with microseconds:
|
|
8
|
+
# Certain values are incorrectly rounded, thus ending up with timestamps
|
|
9
|
+
# that are off by one microsecond.
|
|
10
|
+
# This monkey patch fixes the problem.
|
|
11
|
+
def self.fast_string_to_time(string)
|
|
12
|
+
if string =~ Format::ISO_DATETIME
|
|
13
|
+
microsec = ($7.to_f * 1_000_000).round # used to be #to_i instead
|
|
14
|
+
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module RR
|
|
20
|
+
|
|
21
|
+
# Connection extenders provide additional database specific functionality
|
|
22
|
+
# not coming in the ActiveRecord library.
|
|
23
|
+
# This module itself only provides functionality to register and retrieve
|
|
24
|
+
# such connection extenders.
|
|
25
|
+
module ConnectionExtenders
|
|
26
|
+
# Returns a Hash of currently registered connection extenders.
|
|
27
|
+
# (Empty Hash if no connection extenders were defined.)
|
|
28
|
+
def self.extenders
|
|
29
|
+
@extenders ||= {}
|
|
30
|
+
@extenders
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Registers one or multiple connection extender.
|
|
34
|
+
# extender is a Hash with
|
|
35
|
+
# key:: The adapter symbol as used by ActiveRecord::Connection Adapters, e. g. :postgresql
|
|
36
|
+
# value:: Name of the module implementing the connection extender
|
|
37
|
+
def self.register(extender)
|
|
38
|
+
@extenders ||= {}
|
|
39
|
+
@extenders.merge! extender
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Dummy ActiveRecord descendant only used to create database connections.
|
|
43
|
+
class DummyActiveRecord < ActiveRecord::Base
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Creates an ActiveRecord database connection according to the provided +config+ connection hash.
|
|
47
|
+
# Possible values of this parameter are described in ActiveRecord::Base#establish_connection.
|
|
48
|
+
# The database connection is extended with the correct ConnectionExtenders module.
|
|
49
|
+
#
|
|
50
|
+
# ActiveRecord only allows one database connection per class.
|
|
51
|
+
# (It disconnects the existing database connection if a new connection is established.)
|
|
52
|
+
# To go around this, we delete ActiveRecord's memory of the existing database connection
|
|
53
|
+
# as soon as it is created.
|
|
54
|
+
def self.db_connect_without_cache(config)
|
|
55
|
+
if RUBY_PLATFORM =~ /java/
|
|
56
|
+
adapter = config[:adapter]
|
|
57
|
+
|
|
58
|
+
# As recommended in the activerecord-jdbc-adapter use the jdbc versions
|
|
59
|
+
# of the Adapters. E. g. instead of "postgresql", "jdbcpostgresql".
|
|
60
|
+
adapter = 'jdbc' + adapter unless adapter =~ /^jdbc/
|
|
61
|
+
|
|
62
|
+
DummyActiveRecord.establish_connection(config.merge(:adapter => adapter))
|
|
63
|
+
else
|
|
64
|
+
DummyActiveRecord.establish_connection(config)
|
|
65
|
+
end
|
|
66
|
+
connection = DummyActiveRecord.connection
|
|
67
|
+
|
|
68
|
+
# Delete the database connection from ActiveRecords's 'memory'
|
|
69
|
+
ActiveRecord::Base.connection_handler.connection_pools.delete DummyActiveRecord.name
|
|
70
|
+
|
|
71
|
+
extender = ""
|
|
72
|
+
if RUBY_PLATFORM =~ /java/
|
|
73
|
+
extender = :jdbc
|
|
74
|
+
elsif ConnectionExtenders.extenders.include? config[:adapter].to_sym
|
|
75
|
+
extender = config[:adapter].to_sym
|
|
76
|
+
else
|
|
77
|
+
raise "No ConnectionExtender available for :#{config[:adapter]}"
|
|
78
|
+
end
|
|
79
|
+
connection.extend ConnectionExtenders.extenders[extender]
|
|
80
|
+
|
|
81
|
+
# Hack to get Postgres schema support under JRuby to par with the standard
|
|
82
|
+
# ruby version
|
|
83
|
+
if RUBY_PLATFORM =~ /java/ and config[:adapter].to_sym == :postgresql
|
|
84
|
+
connection.extend RR::ConnectionExtenders::PostgreSQLExtender
|
|
85
|
+
connection.initialize_search_path
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
replication_module = ReplicationExtenders.extenders[config[:adapter].to_sym]
|
|
89
|
+
connection.extend replication_module if replication_module
|
|
90
|
+
|
|
91
|
+
connection
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
@@use_cache = true
|
|
95
|
+
|
|
96
|
+
# Returns the current cache status (+true+ if caching is used; +false+ otherwise).
|
|
97
|
+
def self.use_cache?; @@use_cache; end
|
|
98
|
+
|
|
99
|
+
# Returns the connection cache hash.
|
|
100
|
+
def self.connection_cache; @@connection_cache; end
|
|
101
|
+
|
|
102
|
+
# Sets a new connection cache
|
|
103
|
+
def self.connection_cache=(cache)
|
|
104
|
+
@@connection_cache = cache
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Installs the configured logger (if any) into the database connection.
|
|
108
|
+
# * +db_connection+: database connection (as produced by #db_connect)
|
|
109
|
+
# * +config+: database configuration (as provided to #db_connect)
|
|
110
|
+
def self.install_logger(db_connection, config)
|
|
111
|
+
if config[:logger]
|
|
112
|
+
if config[:logger].respond_to?(:debug)
|
|
113
|
+
logger = config[:logger]
|
|
114
|
+
else
|
|
115
|
+
logger = ActiveSupport::BufferedLogger.new(config[:logger])
|
|
116
|
+
end
|
|
117
|
+
db_connection.instance_variable_set :@logger, logger
|
|
118
|
+
if ActiveSupport.const_defined?(:Notifications)
|
|
119
|
+
connection_object_id = db_connection.object_id
|
|
120
|
+
db_connection.log_subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |name, start, finish, id, payload|
|
|
121
|
+
if payload[:connection_id] == connection_object_id and logger.debug?
|
|
122
|
+
logger.debug payload[:sql].squeeze(" ")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Creates database connections by calling #db_connect_without_cache with the
|
|
130
|
+
# provided +config+ configuration hash.
|
|
131
|
+
# A new database connection is created only if no according cached connection
|
|
132
|
+
# is available.
|
|
133
|
+
def self.db_connect(config)
|
|
134
|
+
if not use_cache?
|
|
135
|
+
db_connection = db_connect_without_cache config
|
|
136
|
+
else
|
|
137
|
+
config_dump = Marshal.dump config.reject {|key, | [:proxy_host, :proxy_port, :logger].include? key}
|
|
138
|
+
config_checksum = Digest::SHA1.hexdigest(config_dump)
|
|
139
|
+
@@connection_cache ||= {}
|
|
140
|
+
|
|
141
|
+
db_connection = connection_cache[config_checksum]
|
|
142
|
+
unless db_connection and db_connection.active?
|
|
143
|
+
db_connection = db_connect_without_cache config
|
|
144
|
+
connection_cache[config_checksum] = db_connection
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
install_logger db_connection, config
|
|
149
|
+
|
|
150
|
+
db_connection
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# If status == true: enable the cache. If status == false: don' use cache
|
|
154
|
+
# Returns the old connection caching status
|
|
155
|
+
def self.use_db_connection_cache(status)
|
|
156
|
+
old_status, @@use_cache = @@use_cache, status
|
|
157
|
+
old_status
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Free up all cached connections
|
|
161
|
+
def self.clear_db_connection_cache
|
|
162
|
+
@@connection_cache = {}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'java'
|
|
2
|
+
|
|
3
|
+
module RR
|
|
4
|
+
module ConnectionExtenders
|
|
5
|
+
|
|
6
|
+
# Provides various JDBC specific functionality required by Rubyrep.
|
|
7
|
+
module JdbcSQLExtender
|
|
8
|
+
RR::ConnectionExtenders.register :jdbc => self
|
|
9
|
+
|
|
10
|
+
# Monkey patch for activerecord-jdbc-adapter-0.7.2 as it doesn't set the
|
|
11
|
+
# +@active+ flag to false, thus ActiveRecord#active? incorrectly confirms
|
|
12
|
+
# the connection to still be active.
|
|
13
|
+
def disconnect!
|
|
14
|
+
super
|
|
15
|
+
@active = false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns an ordered list of primary key column names of the given table
|
|
19
|
+
def primary_key_names(table)
|
|
20
|
+
if tables.grep(/^#{table}$/i).empty?
|
|
21
|
+
# Note: Cannot use tables.include? as returned tables are made lowercase under JRuby MySQL
|
|
22
|
+
raise "table '#{table}' does not exist"
|
|
23
|
+
end
|
|
24
|
+
columns = []
|
|
25
|
+
result_set = @connection.connection.getMetaData.getPrimaryKeys(nil, nil, table);
|
|
26
|
+
while result_set.next
|
|
27
|
+
column_name = result_set.getString("COLUMN_NAME")
|
|
28
|
+
key_seq = result_set.getShort("KEY_SEQ")
|
|
29
|
+
columns << {:column_name => column_name, :key_seq => key_seq}
|
|
30
|
+
end
|
|
31
|
+
columns.sort! {|a, b| a[:key_seq] <=> b[:key_seq]}
|
|
32
|
+
key_names = columns.map {|column| column[:column_name]}
|
|
33
|
+
key_names
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns for each given table, which other tables it references via
|
|
37
|
+
# foreign key constraints.
|
|
38
|
+
# * tables: an array of table names
|
|
39
|
+
# * returns: a hash with
|
|
40
|
+
# * key: name of the referencing table
|
|
41
|
+
# * value: an array of names of referenced tables
|
|
42
|
+
def referenced_tables(tables)
|
|
43
|
+
result = {}
|
|
44
|
+
tables.each do |table|
|
|
45
|
+
references_of_this_table = []
|
|
46
|
+
result_set = @connection.connection.getMetaData.getImportedKeys(nil, nil, table)
|
|
47
|
+
while result_set.next
|
|
48
|
+
referenced_table = result_set.getString("PKTABLE_NAME")
|
|
49
|
+
unless references_of_this_table.include? referenced_table
|
|
50
|
+
references_of_this_table << referenced_table
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
result[table] = references_of_this_table
|
|
54
|
+
end
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
require 'activerecord-jdbc-adapter'
|
|
62
|
+
if ArJdbc.const_defined?(:PostgreSQL)
|
|
63
|
+
ArJdbc::PostgreSQL::RecordNotUnique = ActiveRecord::RecordNotUnique unless ArJdbc::PostgreSQL.const_defined?(:RecordNotUnique)
|
|
64
|
+
ArJdbc::PostgreSQL::InvalidForeignKey = ActiveRecord::InvalidForeignKey unless ArJdbc::PostgreSQL.const_defined?(:InvalidForeignKey)
|
|
65
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module RR
|
|
2
|
+
|
|
3
|
+
module ConnectionExtenders
|
|
4
|
+
|
|
5
|
+
# Provides various MySQL specific functionality required by Rubyrep.
|
|
6
|
+
module MysqlExtender
|
|
7
|
+
RR::ConnectionExtenders.register :mysql2 => self
|
|
8
|
+
|
|
9
|
+
# Returns an ordered list of primary key column names of the given table
|
|
10
|
+
def primary_key_names(table)
|
|
11
|
+
row = self.select_one(<<-end_sql)
|
|
12
|
+
select table_name from information_schema.tables
|
|
13
|
+
where table_schema = database() and table_name = '#{table}'
|
|
14
|
+
end_sql
|
|
15
|
+
if row.nil?
|
|
16
|
+
raise "table '#{table}' does not exist"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
rows = self.select_all(<<-end_sql)
|
|
20
|
+
select column_name from information_schema.key_column_usage
|
|
21
|
+
where table_schema = database() and table_name = '#{table}'
|
|
22
|
+
and constraint_name = 'PRIMARY'
|
|
23
|
+
order by ordinal_position
|
|
24
|
+
end_sql
|
|
25
|
+
|
|
26
|
+
columns = rows.map {|_row| _row['column_name']}
|
|
27
|
+
columns
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns for each given table, which other tables it references via
|
|
31
|
+
# foreign key constraints.
|
|
32
|
+
# * tables: an array of table names
|
|
33
|
+
# Returns: a hash with
|
|
34
|
+
# * key: name of the referencing table
|
|
35
|
+
# * value: an array of names of referenced tables
|
|
36
|
+
def referenced_tables(tables)
|
|
37
|
+
rows = self.select_all(<<-end_sql)
|
|
38
|
+
select distinct table_name as referencing_table, referenced_table_name as referenced_table
|
|
39
|
+
from information_schema.key_column_usage
|
|
40
|
+
where table_schema = database()
|
|
41
|
+
and table_name in ('#{tables.join("', '")}')
|
|
42
|
+
end_sql
|
|
43
|
+
result = {}
|
|
44
|
+
rows.each do |row|
|
|
45
|
+
unless result.include? row['referencing_table']
|
|
46
|
+
result[row['referencing_table']] = []
|
|
47
|
+
end
|
|
48
|
+
if row['referenced_table'] != nil
|
|
49
|
+
result[row['referencing_table']] << row['referenced_table']
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
tables.each do |table|
|
|
53
|
+
result[table] = [] unless result.include? table
|
|
54
|
+
end
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
|
|
3
|
+
# Hack:
|
|
4
|
+
# For some reasons these methods were removed in Rails 2.2.2, thus breaking
|
|
5
|
+
# the binary and multi-lingual data loading.
|
|
6
|
+
# So here they are again.
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
module ConnectionAdapters
|
|
9
|
+
# PostgreSQL-specific extensions to column definitions in a table.
|
|
10
|
+
class PostgreSQLColumn < Column #:nodoc:
|
|
11
|
+
|
|
12
|
+
# Escapes binary strings for bytea input to the database.
|
|
13
|
+
def self.string_to_binary(value)
|
|
14
|
+
if PGconn.respond_to?(:escape_bytea)
|
|
15
|
+
self.class.module_eval do
|
|
16
|
+
define_method(:string_to_binary) do |value|
|
|
17
|
+
PGconn.escape_bytea(value) if value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
self.class.module_eval do
|
|
22
|
+
define_method(:string_to_binary) do |value|
|
|
23
|
+
if value
|
|
24
|
+
result = ''
|
|
25
|
+
value.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
|
26
|
+
result
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
self.class.string_to_binary(value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Unescapes bytea output from a database to the binary string it represents.
|
|
35
|
+
def self.binary_to_string(value)
|
|
36
|
+
# In each case, check if the value actually is escaped PostgreSQL bytea output
|
|
37
|
+
# or an unescaped Active Record attribute that was just written.
|
|
38
|
+
if PGconn.respond_to?(:unescape_bytea)
|
|
39
|
+
self.class.module_eval do
|
|
40
|
+
define_method(:binary_to_string) do |value|
|
|
41
|
+
if value =~ /\\\d{3}/
|
|
42
|
+
PGconn.unescape_bytea(value)
|
|
43
|
+
else
|
|
44
|
+
value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
self.class.module_eval do
|
|
50
|
+
define_method(:binary_to_string) do |value|
|
|
51
|
+
if value =~ /\\\d{3}/
|
|
52
|
+
result = ''
|
|
53
|
+
i, max = 0, value.size
|
|
54
|
+
while i < max
|
|
55
|
+
char = value[i]
|
|
56
|
+
if char == ?\\
|
|
57
|
+
if value[i+1] == ?\\
|
|
58
|
+
char = ?\\
|
|
59
|
+
i += 1
|
|
60
|
+
else
|
|
61
|
+
char = value[i+1..i+3].oct
|
|
62
|
+
i += 3
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
result << char
|
|
66
|
+
i += 1
|
|
67
|
+
end
|
|
68
|
+
result
|
|
69
|
+
else
|
|
70
|
+
value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
self.class.binary_to_string(value)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module RR
|
|
82
|
+
module ConnectionExtenders
|
|
83
|
+
|
|
84
|
+
# Provides various PostgreSQL specific functionality required by Rubyrep.
|
|
85
|
+
module PostgreSQLExtender
|
|
86
|
+
RR::ConnectionExtenders.register :postgresql => self
|
|
87
|
+
|
|
88
|
+
# Returns an array of schemas in the current search path.
|
|
89
|
+
def schemas
|
|
90
|
+
unless @schemas
|
|
91
|
+
search_path = select_one("show search_path")['search_path']
|
|
92
|
+
@schemas = search_path.split(/,/).map { |p| quote(p.strip) }.join(',')
|
|
93
|
+
end
|
|
94
|
+
@schemas
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# *** Monkey patch***
|
|
98
|
+
# Returns the list of all tables in the schema search path or a specified schema.
|
|
99
|
+
# This overwrites the according ActiveRecord::PostgreSQLAdapter method
|
|
100
|
+
# to make sure that also search paths with spaces work
|
|
101
|
+
# (E. g. 'public, rr' instead of only 'public,rr')
|
|
102
|
+
def tables(name = nil)
|
|
103
|
+
select_all(<<-SQL, name).map { |row| row['tablename'] }
|
|
104
|
+
SELECT tablename
|
|
105
|
+
FROM pg_tables
|
|
106
|
+
WHERE schemaname IN (#{schemas})
|
|
107
|
+
SQL
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Disables schema extraction from table names by overwriting the according
|
|
111
|
+
# ActiveRecord method.
|
|
112
|
+
# Necessary to support table names containing dots (".").
|
|
113
|
+
# (This is possible as rubyrep exclusively uses the search_path setting to
|
|
114
|
+
# support PostgreSQL schemas.)
|
|
115
|
+
def extract_pg_identifier_from_name(name)
|
|
116
|
+
return name, nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns an ordered list of primary key column names of the given table
|
|
120
|
+
def primary_key_names(table)
|
|
121
|
+
row = self.select_one(<<-end_sql)
|
|
122
|
+
SELECT relname
|
|
123
|
+
FROM pg_class
|
|
124
|
+
WHERE relname = '#{table}' and relnamespace IN
|
|
125
|
+
(SELECT oid FROM pg_namespace WHERE nspname in (#{schemas}))
|
|
126
|
+
end_sql
|
|
127
|
+
raise "table '#{table}' does not exist" if row.nil?
|
|
128
|
+
|
|
129
|
+
row = self.select_one(<<-end_sql)
|
|
130
|
+
SELECT cons.conkey
|
|
131
|
+
FROM pg_class rel
|
|
132
|
+
JOIN pg_constraint cons ON (rel.oid = cons.conrelid)
|
|
133
|
+
WHERE cons.contype = 'p' AND rel.relname = '#{table}' AND rel.relnamespace IN
|
|
134
|
+
(SELECT oid FROM pg_namespace WHERE nspname in (#{schemas}))
|
|
135
|
+
end_sql
|
|
136
|
+
return [] if row.nil?
|
|
137
|
+
column_parray = row['conkey']
|
|
138
|
+
|
|
139
|
+
# Change a Postgres Array of attribute numbers
|
|
140
|
+
# (returned in String form, e. g.: "{1,2}") into an array of Integers
|
|
141
|
+
column_ids = column_parray.sub(/^\{(.*)\}$/,'\1').split(',').map {|a| a.to_i}
|
|
142
|
+
|
|
143
|
+
columns = {}
|
|
144
|
+
rows = self.select_all(<<-end_sql)
|
|
145
|
+
SELECT attnum, attname
|
|
146
|
+
FROM pg_class rel
|
|
147
|
+
JOIN pg_constraint cons ON (rel.oid = cons.conrelid)
|
|
148
|
+
JOIN pg_attribute attr ON (rel.oid = attr.attrelid and attr.attnum = any (cons.conkey))
|
|
149
|
+
WHERE cons.contype = 'p' AND rel.relname = '#{table}' AND rel.relnamespace IN
|
|
150
|
+
(SELECT oid FROM pg_namespace WHERE nspname in (#{schemas}))
|
|
151
|
+
end_sql
|
|
152
|
+
sorted_columns = []
|
|
153
|
+
if not rows.nil?
|
|
154
|
+
rows.each() {|r| columns[r['attnum'].to_i] = r['attname']}
|
|
155
|
+
sorted_columns = column_ids.map {|column_id| columns[column_id]}
|
|
156
|
+
end
|
|
157
|
+
sorted_columns
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns for each given table, which other tables it references via
|
|
161
|
+
# foreign key constraints.
|
|
162
|
+
# * tables: an array of table names
|
|
163
|
+
# Returns: a hash with
|
|
164
|
+
# * key: name of the referencing table
|
|
165
|
+
# * value: an array of names of referenced tables
|
|
166
|
+
def referenced_tables(tables)
|
|
167
|
+
rows = self.select_all(<<-end_sql)
|
|
168
|
+
select distinct referencing.relname as referencing_table, referenced.relname as referenced_table
|
|
169
|
+
from pg_class referencing
|
|
170
|
+
left join pg_constraint on referencing.oid = pg_constraint.conrelid
|
|
171
|
+
left join pg_class referenced on pg_constraint.confrelid = referenced.oid
|
|
172
|
+
where referencing.relkind='r'
|
|
173
|
+
and referencing.relname in ('#{tables.join("', '")}')
|
|
174
|
+
and referencing.relnamespace IN
|
|
175
|
+
(SELECT oid FROM pg_namespace WHERE nspname in (#{schemas}))
|
|
176
|
+
end_sql
|
|
177
|
+
result = {}
|
|
178
|
+
rows.each do |row|
|
|
179
|
+
unless result.include? row['referencing_table']
|
|
180
|
+
result[row['referencing_table']] = []
|
|
181
|
+
end
|
|
182
|
+
if row['referenced_table'] != nil
|
|
183
|
+
result[row['referencing_table']] << row['referenced_table']
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
result
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Sets the schema search path as per configuration parameters
|
|
190
|
+
def initialize_search_path
|
|
191
|
+
execute "SET search_path TO #{config[:schema_search_path] || 'public'}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# *** Moneky patch***
|
|
195
|
+
# Returns the column objects for the named table.
|
|
196
|
+
# Fixes JRuby schema support
|
|
197
|
+
def columns(table_name, name = nil)
|
|
198
|
+
jdbc_connection = @connection.connection # the actual JDBC DatabaseConnection
|
|
199
|
+
@unquoted_schema ||= select_one("show search_path")['search_path']
|
|
200
|
+
|
|
201
|
+
# check if table exists
|
|
202
|
+
table_results = jdbc_connection.meta_data.get_tables(
|
|
203
|
+
jdbc_connection.catalog,
|
|
204
|
+
@unquoted_schema,
|
|
205
|
+
table_name,
|
|
206
|
+
["TABLE","VIEW","SYNONYM"].to_java(:string)
|
|
207
|
+
)
|
|
208
|
+
table_exists = table_results.next
|
|
209
|
+
table_results.close
|
|
210
|
+
raise "table '#{table_name}' not found" unless table_exists
|
|
211
|
+
|
|
212
|
+
# get ResultSet for columns of table
|
|
213
|
+
column_results = jdbc_connection.meta_data.get_columns(
|
|
214
|
+
jdbc_connection.catalog,
|
|
215
|
+
@unquoted_schema,
|
|
216
|
+
table_name,
|
|
217
|
+
nil
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# create the Column objects
|
|
221
|
+
columns = []
|
|
222
|
+
while column_results.next
|
|
223
|
+
|
|
224
|
+
# generate type clause
|
|
225
|
+
type_clause = column_results.get_string('TYPE_NAME')
|
|
226
|
+
precision = column_results.get_int('COLUMN_SIZE')
|
|
227
|
+
scale = column_results.get_int('DECIMAL_DIGITS')
|
|
228
|
+
if precision > 0
|
|
229
|
+
type_clause += "(#{precision}#{scale > 0 ? ",#{scale}" : ""})"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# create column
|
|
233
|
+
columns << ::ActiveRecord::ConnectionAdapters::JdbcColumn.new(
|
|
234
|
+
@config,
|
|
235
|
+
column_results.get_string('COLUMN_NAME'),
|
|
236
|
+
column_results.get_string('COLUMN_DEF'),
|
|
237
|
+
type_clause,
|
|
238
|
+
column_results.get_string('IS_NULLABLE').strip == "NO"
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
column_results.close
|
|
242
|
+
|
|
243
|
+
columns
|
|
244
|
+
end if RUBY_PLATFORM =~ /java/
|
|
245
|
+
|
|
246
|
+
# *** Monkey patch***
|
|
247
|
+
# Returns the list of a table's column names, data types, and default values.
|
|
248
|
+
# This overwrites the according ActiveRecord::PostgreSQLAdapter method
|
|
249
|
+
# to
|
|
250
|
+
# * work with tables containing a dot (".") and
|
|
251
|
+
# * only look for tables in the current schema search path.
|
|
252
|
+
def column_definitions(table_name) #:nodoc:
|
|
253
|
+
rows = self.select_all <<-end_sql
|
|
254
|
+
SELECT
|
|
255
|
+
a.attname as name,
|
|
256
|
+
format_type(a.atttypid, a.atttypmod) as type,
|
|
257
|
+
d.adsrc as source,
|
|
258
|
+
a.attnotnull as notnull
|
|
259
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
|
260
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
|
261
|
+
WHERE a.attrelid = (
|
|
262
|
+
SELECT oid FROM pg_class
|
|
263
|
+
WHERE relname = '#{table_name}' AND relnamespace IN
|
|
264
|
+
(SELECT oid FROM pg_namespace WHERE nspname in (#{schemas}))
|
|
265
|
+
LIMIT 1
|
|
266
|
+
)
|
|
267
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
268
|
+
ORDER BY a.attnum
|
|
269
|
+
end_sql
|
|
270
|
+
|
|
271
|
+
rows.map {|row| [row['name'], row['type'], row['source'], row['notnull']]}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|