andyjeffries-rubyrep 1.2.1

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