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.
Files changed (125) hide show
  1. data/History.txt +83 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +151 -0
  4. data/README.txt +37 -0
  5. data/bin/rubyrep +8 -0
  6. data/lib/rubyrep.rb +72 -0
  7. data/lib/rubyrep/base_runner.rb +195 -0
  8. data/lib/rubyrep/command_runner.rb +144 -0
  9. data/lib/rubyrep/committers/buffered_committer.rb +151 -0
  10. data/lib/rubyrep/committers/committers.rb +152 -0
  11. data/lib/rubyrep/configuration.rb +275 -0
  12. data/lib/rubyrep/connection_extenders/connection_extenders.rb +165 -0
  13. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +65 -0
  14. data/lib/rubyrep/connection_extenders/mysql_extender.rb +59 -0
  15. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +277 -0
  16. data/lib/rubyrep/database_proxy.rb +52 -0
  17. data/lib/rubyrep/direct_table_scan.rb +75 -0
  18. data/lib/rubyrep/generate_runner.rb +105 -0
  19. data/lib/rubyrep/initializer.rb +39 -0
  20. data/lib/rubyrep/log_helper.rb +30 -0
  21. data/lib/rubyrep/logged_change.rb +160 -0
  22. data/lib/rubyrep/logged_change_loader.rb +197 -0
  23. data/lib/rubyrep/noisy_connection.rb +80 -0
  24. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  25. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  26. data/lib/rubyrep/proxy_connection.rb +431 -0
  27. data/lib/rubyrep/proxy_cursor.rb +44 -0
  28. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  29. data/lib/rubyrep/proxy_runner.rb +89 -0
  30. data/lib/rubyrep/replication_difference.rb +100 -0
  31. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  32. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +236 -0
  33. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  34. data/lib/rubyrep/replication_helper.rb +142 -0
  35. data/lib/rubyrep/replication_initializer.rb +327 -0
  36. data/lib/rubyrep/replication_run.rb +142 -0
  37. data/lib/rubyrep/replication_runner.rb +166 -0
  38. data/lib/rubyrep/replicators/replicators.rb +42 -0
  39. data/lib/rubyrep/replicators/two_way_replicator.rb +361 -0
  40. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  41. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  42. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  43. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  44. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  45. data/lib/rubyrep/scan_runner.rb +25 -0
  46. data/lib/rubyrep/session.rb +230 -0
  47. data/lib/rubyrep/sync_helper.rb +121 -0
  48. data/lib/rubyrep/sync_runner.rb +31 -0
  49. data/lib/rubyrep/syncers/syncers.rb +112 -0
  50. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  51. data/lib/rubyrep/table_scan.rb +54 -0
  52. data/lib/rubyrep/table_scan_helper.rb +46 -0
  53. data/lib/rubyrep/table_sorter.rb +70 -0
  54. data/lib/rubyrep/table_spec_resolver.rb +142 -0
  55. data/lib/rubyrep/table_sync.rb +90 -0
  56. data/lib/rubyrep/task_sweeper.rb +77 -0
  57. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  58. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  59. data/lib/rubyrep/uninstall_runner.rb +93 -0
  60. data/lib/rubyrep/version.rb +9 -0
  61. data/rubyrep +8 -0
  62. data/rubyrep.bat +4 -0
  63. data/setup.rb +1585 -0
  64. data/spec/base_runner_spec.rb +218 -0
  65. data/spec/buffered_committer_spec.rb +274 -0
  66. data/spec/command_runner_spec.rb +145 -0
  67. data/spec/committers_spec.rb +178 -0
  68. data/spec/configuration_spec.rb +203 -0
  69. data/spec/connection_extender_interface_spec.rb +141 -0
  70. data/spec/connection_extenders_registration_spec.rb +164 -0
  71. data/spec/database_proxy_spec.rb +48 -0
  72. data/spec/database_rake_spec.rb +40 -0
  73. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  74. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  75. data/spec/direct_table_scan_spec.rb +61 -0
  76. data/spec/dolphins.jpg +0 -0
  77. data/spec/generate_runner_spec.rb +84 -0
  78. data/spec/initializer_spec.rb +46 -0
  79. data/spec/log_helper_spec.rb +39 -0
  80. data/spec/logged_change_loader_spec.rb +68 -0
  81. data/spec/logged_change_spec.rb +470 -0
  82. data/spec/noisy_connection_spec.rb +78 -0
  83. data/spec/postgresql_replication_spec.rb +48 -0
  84. data/spec/postgresql_schema_support_spec.rb +212 -0
  85. data/spec/postgresql_support_spec.rb +63 -0
  86. data/spec/progress_bar_spec.rb +77 -0
  87. data/spec/proxied_table_scan_spec.rb +151 -0
  88. data/spec/proxy_block_cursor_spec.rb +197 -0
  89. data/spec/proxy_connection_spec.rb +423 -0
  90. data/spec/proxy_cursor_spec.rb +56 -0
  91. data/spec/proxy_row_cursor_spec.rb +66 -0
  92. data/spec/proxy_runner_spec.rb +70 -0
  93. data/spec/replication_difference_spec.rb +161 -0
  94. data/spec/replication_extender_interface_spec.rb +367 -0
  95. data/spec/replication_extenders_spec.rb +32 -0
  96. data/spec/replication_helper_spec.rb +178 -0
  97. data/spec/replication_initializer_spec.rb +509 -0
  98. data/spec/replication_run_spec.rb +443 -0
  99. data/spec/replication_runner_spec.rb +254 -0
  100. data/spec/replicators_spec.rb +36 -0
  101. data/spec/rubyrep_spec.rb +8 -0
  102. data/spec/scan_detail_reporter_spec.rb +119 -0
  103. data/spec/scan_progress_printers_spec.rb +68 -0
  104. data/spec/scan_report_printers_spec.rb +67 -0
  105. data/spec/scan_runner_spec.rb +50 -0
  106. data/spec/scan_summary_reporter_spec.rb +61 -0
  107. data/spec/session_spec.rb +253 -0
  108. data/spec/spec.opts +1 -0
  109. data/spec/spec_helper.rb +305 -0
  110. data/spec/strange_name_support_spec.rb +135 -0
  111. data/spec/sync_helper_spec.rb +169 -0
  112. data/spec/sync_runner_spec.rb +78 -0
  113. data/spec/syncers_spec.rb +171 -0
  114. data/spec/table_scan_helper_spec.rb +36 -0
  115. data/spec/table_scan_spec.rb +49 -0
  116. data/spec/table_sorter_spec.rb +30 -0
  117. data/spec/table_spec_resolver_spec.rb +111 -0
  118. data/spec/table_sync_spec.rb +140 -0
  119. data/spec/task_sweeper_spec.rb +47 -0
  120. data/spec/trigger_mode_switcher_spec.rb +83 -0
  121. data/spec/two_way_replicator_spec.rb +721 -0
  122. data/spec/two_way_syncer_spec.rb +256 -0
  123. data/spec/type_casting_cursor_spec.rb +50 -0
  124. data/spec/uninstall_runner_spec.rb +93 -0
  125. 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
+