activerecord-jdbc-adapter 0.9.3-java

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 (121) hide show
  1. data/History.txt +248 -0
  2. data/LICENSE.txt +21 -0
  3. data/Manifest.txt +125 -0
  4. data/README.txt +218 -0
  5. data/Rakefile +10 -0
  6. data/lib/active_record/connection_adapters/cachedb_adapter.rb +1 -0
  7. data/lib/active_record/connection_adapters/derby_adapter.rb +13 -0
  8. data/lib/active_record/connection_adapters/h2_adapter.rb +13 -0
  9. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +13 -0
  10. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  11. data/lib/active_record/connection_adapters/jdbc_adapter.rb +640 -0
  12. data/lib/active_record/connection_adapters/jdbc_adapter_spec.rb +26 -0
  13. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  14. data/lib/active_record/connection_adapters/mysql_adapter.rb +13 -0
  15. data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
  16. data/lib/active_record/connection_adapters/postgresql_adapter.rb +13 -0
  17. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +13 -0
  18. data/lib/generators/jdbc/jdbc_generator.rb +9 -0
  19. data/lib/jdbc_adapter.rb +27 -0
  20. data/lib/jdbc_adapter/jdbc.rake +121 -0
  21. data/lib/jdbc_adapter/jdbc_adapter_internal.jar +0 -0
  22. data/lib/jdbc_adapter/jdbc_cachedb.rb +33 -0
  23. data/lib/jdbc_adapter/jdbc_db2.rb +203 -0
  24. data/lib/jdbc_adapter/jdbc_derby.rb +430 -0
  25. data/lib/jdbc_adapter/jdbc_firebird.rb +109 -0
  26. data/lib/jdbc_adapter/jdbc_hsqldb.rb +218 -0
  27. data/lib/jdbc_adapter/jdbc_informix.rb +147 -0
  28. data/lib/jdbc_adapter/jdbc_mimer.rb +141 -0
  29. data/lib/jdbc_adapter/jdbc_mssql.rb +337 -0
  30. data/lib/jdbc_adapter/jdbc_mysql.rb +236 -0
  31. data/lib/jdbc_adapter/jdbc_oracle.rb +377 -0
  32. data/lib/jdbc_adapter/jdbc_postgre.rb +498 -0
  33. data/lib/jdbc_adapter/jdbc_sqlite3.rb +384 -0
  34. data/lib/jdbc_adapter/jdbc_sybase.rb +50 -0
  35. data/lib/jdbc_adapter/missing_functionality_helper.rb +87 -0
  36. data/lib/jdbc_adapter/rake_tasks.rb +10 -0
  37. data/lib/jdbc_adapter/tsql_helper.rb +60 -0
  38. data/lib/jdbc_adapter/version.rb +5 -0
  39. data/lib/pg.rb +4 -0
  40. data/rails_generators/jdbc_generator.rb +15 -0
  41. data/rails_generators/templates/config/initializers/jdbc.rb +7 -0
  42. data/rails_generators/templates/lib/tasks/jdbc.rake +8 -0
  43. data/rakelib/compile.rake +23 -0
  44. data/rakelib/package.rake +90 -0
  45. data/rakelib/rails.rake +41 -0
  46. data/rakelib/test.rake +76 -0
  47. data/src/java/jdbc_adapter/JdbcAdapterInternalService.java +53 -0
  48. data/src/java/jdbc_adapter/JdbcConnectionFactory.java +36 -0
  49. data/src/java/jdbc_adapter/JdbcDerbySpec.java +293 -0
  50. data/src/java/jdbc_adapter/JdbcMySQLSpec.java +134 -0
  51. data/src/java/jdbc_adapter/MssqlRubyJdbcConnection.java +71 -0
  52. data/src/java/jdbc_adapter/PostgresRubyJdbcConnection.java +55 -0
  53. data/src/java/jdbc_adapter/RubyJdbcConnection.java +1162 -0
  54. data/src/java/jdbc_adapter/SQLBlock.java +27 -0
  55. data/src/java/jdbc_adapter/Sqlite3RubyJdbcConnection.java +41 -0
  56. data/test/abstract_db_create.rb +107 -0
  57. data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
  58. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
  59. data/test/cachedb_simple_test.rb +6 -0
  60. data/test/db/cachedb.rb +9 -0
  61. data/test/db/db2.rb +9 -0
  62. data/test/db/derby.rb +14 -0
  63. data/test/db/h2.rb +11 -0
  64. data/test/db/hsqldb.rb +12 -0
  65. data/test/db/informix.rb +11 -0
  66. data/test/db/jdbc.rb +11 -0
  67. data/test/db/jndi_config.rb +30 -0
  68. data/test/db/logger.rb +3 -0
  69. data/test/db/mssql.rb +9 -0
  70. data/test/db/mysql.rb +10 -0
  71. data/test/db/oracle.rb +34 -0
  72. data/test/db/postgres.rb +9 -0
  73. data/test/db/sqlite3.rb +15 -0
  74. data/test/db2_simple_test.rb +10 -0
  75. data/test/derby_migration_test.rb +21 -0
  76. data/test/derby_multibyte_test.rb +12 -0
  77. data/test/derby_simple_test.rb +21 -0
  78. data/test/generic_jdbc_connection_test.rb +9 -0
  79. data/test/h2_simple_test.rb +6 -0
  80. data/test/has_many_through.rb +79 -0
  81. data/test/helper.rb +5 -0
  82. data/test/hsqldb_simple_test.rb +6 -0
  83. data/test/informix_simple_test.rb +48 -0
  84. data/test/jdbc_adapter/jdbc_db2_test.rb +26 -0
  85. data/test/jdbc_adapter/jdbc_sybase_test.rb +33 -0
  86. data/test/jdbc_common.rb +25 -0
  87. data/test/jndi_callbacks_test.rb +38 -0
  88. data/test/jndi_test.rb +35 -0
  89. data/test/manualTestDatabase.rb +191 -0
  90. data/test/minirunit.rb +109 -0
  91. data/test/minirunit/testConnect.rb +14 -0
  92. data/test/minirunit/testH2.rb +73 -0
  93. data/test/minirunit/testHsqldb.rb +73 -0
  94. data/test/minirunit/testLoadActiveRecord.rb +3 -0
  95. data/test/minirunit/testMysql.rb +83 -0
  96. data/test/minirunit/testRawSelect.rb +24 -0
  97. data/test/models/add_not_null_column_to_table.rb +12 -0
  98. data/test/models/auto_id.rb +18 -0
  99. data/test/models/data_types.rb +28 -0
  100. data/test/models/entry.rb +23 -0
  101. data/test/models/mixed_case.rb +20 -0
  102. data/test/models/reserved_word.rb +18 -0
  103. data/test/models/string_id.rb +18 -0
  104. data/test/models/validates_uniqueness_of_string.rb +19 -0
  105. data/test/mssql_simple_test.rb +6 -0
  106. data/test/mysql_db_create_test.rb +25 -0
  107. data/test/mysql_multibyte_test.rb +10 -0
  108. data/test/mysql_nonstandard_primary_key_test.rb +42 -0
  109. data/test/mysql_simple_test.rb +32 -0
  110. data/test/oracle_simple_test.rb +29 -0
  111. data/test/pick_rails_version.rb +3 -0
  112. data/test/postgres_db_create_test.rb +21 -0
  113. data/test/postgres_mixed_case_test.rb +19 -0
  114. data/test/postgres_nonseq_pkey_test.rb +40 -0
  115. data/test/postgres_reserved_test.rb +22 -0
  116. data/test/postgres_schema_search_path_test.rb +46 -0
  117. data/test/postgres_simple_test.rb +13 -0
  118. data/test/simple.rb +475 -0
  119. data/test/sqlite3_simple_test.rb +233 -0
  120. data/test/sybase_jtds_simple_test.rb +6 -0
  121. metadata +188 -0
@@ -0,0 +1,377 @@
1
+ module ::JdbcSpec
2
+ module ActiveRecordExtensions
3
+ def oracle_connection(config)
4
+ config[:port] ||= 1521
5
+ config[:url] ||= "jdbc:oracle:thin:@#{config[:host]}:#{config[:port]}:#{config[:database]}"
6
+ config[:driver] ||= "oracle.jdbc.driver.OracleDriver"
7
+ jdbc_connection(config)
8
+ end
9
+ end
10
+
11
+ module Oracle
12
+ def self.extended(mod)
13
+ unless @lob_callback_added
14
+ ActiveRecord::Base.class_eval do
15
+ def after_save_with_oracle_lob
16
+ self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each do |c|
17
+ value = self[c.name]
18
+ value = value.to_yaml if unserializable_attribute?(c.name, c)
19
+ next if value.nil? || (value == '')
20
+
21
+ connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
22
+ end
23
+ end
24
+ end
25
+
26
+ ActiveRecord::Base.after_save :after_save_with_oracle_lob
27
+ @lob_callback_added = true
28
+ end
29
+ mod.class.class_eval do
30
+ alias_chained_method :insert, :query_dirty, :jdbc_oracle_insert
31
+ end
32
+ end
33
+
34
+ def self.adapter_matcher(name, *)
35
+ name =~ /oracle/i ? self : false
36
+ end
37
+
38
+ def self.column_selector
39
+ [/oracle/i, lambda {|cfg,col| col.extend(::JdbcSpec::Oracle::Column)}]
40
+ end
41
+
42
+ module Column
43
+ def type_cast(value)
44
+ return nil if value.nil?
45
+ case type
46
+ when :datetime then JdbcSpec::Oracle::Column.string_to_time(value, self.class)
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ def type_cast_code(var_name)
53
+ case type
54
+ when :datetime then "JdbcSpec::Oracle::Column.string_to_time(#{var_name}, self.class)"
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ def self.string_to_time(string, klass)
61
+ time = klass.string_to_time(string)
62
+ guess_date_or_time(time)
63
+ end
64
+
65
+ def self.guess_date_or_time(value)
66
+ (value.hour == 0 && value.min == 0 && value.sec == 0) ?
67
+ new_date(value.year, value.month, value.day) : value
68
+ end
69
+
70
+ private
71
+ def simplified_type(field_type)
72
+ case field_type
73
+ when /^number\(1\)$/i then :boolean
74
+ when /char/i then :string
75
+ when /float|double/i then :float
76
+ when /int/i then :integer
77
+ when /num|dec|real/i then @scale == 0 ? :integer : :decimal
78
+ when /date|time/i then :datetime
79
+ when /clob/i then :text
80
+ when /blob/i then :binary
81
+ end
82
+ end
83
+
84
+ # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
85
+ def default_value(value)
86
+ return nil unless value
87
+
88
+ # Not sure why we need this for Oracle?
89
+ value = value.strip
90
+
91
+ return nil if value == "null"
92
+
93
+ # sysdate default should be treated like a null value
94
+ return nil if value.downcase == "sysdate"
95
+
96
+ # jdbc returns column default strings with actual single quotes around the value.
97
+ return $1 if value =~ /^'(.*)'$/
98
+
99
+ value
100
+ end
101
+ end
102
+
103
+ def adapter_name
104
+ 'oracle'
105
+ end
106
+
107
+ def table_alias_length
108
+ 30
109
+ end
110
+
111
+ def default_sequence_name(table, column = nil) #:nodoc:
112
+ "#{table}_seq"
113
+ end
114
+
115
+ def create_table(name, options = {}) #:nodoc:
116
+ super(name, options)
117
+ seq_name = options[:sequence_name] || "#{name}_seq"
118
+ raise ActiveRecord::StatementInvalid.new("name #{seq_name} too long") if seq_name.length > table_alias_length
119
+ execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
120
+ end
121
+
122
+ def rename_table(name, new_name) #:nodoc:
123
+ execute "RENAME #{name} TO #{new_name}"
124
+ execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
125
+ end
126
+
127
+ def drop_table(name, options = {}) #:nodoc:
128
+ super(name)
129
+ seq_name = options[:sequence_name] || "#{name}_seq"
130
+ execute "DROP SEQUENCE #{seq_name}" rescue nil
131
+ end
132
+
133
+ def recreate_database(name)
134
+ tables.each{ |table| drop_table(table) }
135
+ end
136
+
137
+ def drop_database(name)
138
+ recreate_database(name)
139
+ end
140
+
141
+ def jdbc_oracle_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
142
+ if id_value || pk.nil? # Pre-assigned id or table without a primary key
143
+ execute sql, name
144
+ else # Assume the sql contains a bind-variable for the id
145
+ # Extract the table from the insert sql. Yuck.
146
+ table = sql.split(" ", 4)[2].gsub('"', '')
147
+ sequence_name ||= default_sequence_name(table)
148
+ id_value = select_one("select #{sequence_name}.nextval id from dual")['id'].to_i
149
+ log(sql, name) do
150
+ @connection.execute_id_insert(sql,id_value)
151
+ end
152
+ end
153
+ id_value
154
+ end
155
+
156
+ def indexes(table, name = nil)
157
+ @connection.indexes(table, name, @connection.connection.meta_data.user_name)
158
+ end
159
+
160
+ def _execute(sql, name = nil)
161
+ case sql.strip
162
+ when /\A\(?\s*(select|show)/i then
163
+ @connection.execute_query(sql)
164
+ else
165
+ @connection.execute_update(sql)
166
+ end
167
+ end
168
+
169
+ def modify_types(tp)
170
+ tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
171
+ tp[:integer] = { :name => "NUMBER", :limit => 38 }
172
+ tp[:datetime] = { :name => "DATE" }
173
+ tp[:timestamp] = { :name => "DATE" }
174
+ tp[:time] = { :name => "DATE" }
175
+ tp[:date] = { :name => "DATE" }
176
+ tp
177
+ end
178
+
179
+ def add_limit_offset!(sql, options) #:nodoc:
180
+ offset = options[:offset] || 0
181
+
182
+ if limit = options[:limit]
183
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
184
+ elsif offset > 0
185
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
186
+ end
187
+ end
188
+
189
+ def current_database #:nodoc:
190
+ select_one("select sys_context('userenv','db_name') db from dual")["db"]
191
+ end
192
+
193
+ def remove_index(table_name, options = {}) #:nodoc:
194
+ execute "DROP INDEX #{index_name(table_name, options)}"
195
+ end
196
+
197
+ def change_column_default(table_name, column_name, default) #:nodoc:
198
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
199
+ end
200
+
201
+ def add_column_options!(sql, options) #:nodoc:
202
+ # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
203
+ if options_include_default?(options) && (column = options[:column]) && column.type == :text
204
+ sql << " DEFAULT #{quote(options.delete(:default))}"
205
+ end
206
+ super
207
+ end
208
+
209
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
210
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
211
+ add_column_options!(change_column_sql, options)
212
+ execute(change_column_sql)
213
+ end
214
+
215
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
216
+ execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
217
+ end
218
+
219
+ def remove_column(table_name, column_name) #:nodoc:
220
+ execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
221
+ end
222
+
223
+ def structure_dump #:nodoc:
224
+ s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
225
+ structure << "create sequence #{seq.to_a.first.last};\n\n"
226
+ end
227
+
228
+ select_all("select table_name from user_tables").inject(s) do |structure, table|
229
+ ddl = "create table #{table.to_a.first.last} (\n "
230
+ cols = select_all(%Q{
231
+ select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
232
+ from user_tab_columns
233
+ where table_name = '#{table.to_a.first.last}'
234
+ order by column_id
235
+ }).map do |row|
236
+ row = row.inject({}) do |h,args|
237
+ h[args[0].downcase] = args[1]
238
+ h
239
+ end
240
+ col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
241
+ if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
242
+ col << "(#{row['data_precision'].to_i}"
243
+ col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
244
+ col << ')'
245
+ elsif row['data_type'].include?('CHAR')
246
+ col << "(#{row['data_length'].to_i})"
247
+ end
248
+ col << " default #{row['data_default']}" if !row['data_default'].nil?
249
+ col << ' not null' if row['nullable'] == 'N'
250
+ col
251
+ end
252
+ ddl << cols.join(",\n ")
253
+ ddl << ");\n\n"
254
+ structure << ddl
255
+ end
256
+ end
257
+
258
+ def structure_drop #:nodoc:
259
+ s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
260
+ drop << "drop sequence #{seq.to_a.first.last};\n\n"
261
+ end
262
+
263
+ select_all("select table_name from user_tables").inject(s) do |drop, table|
264
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
265
+ end
266
+ end
267
+
268
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
269
+ #
270
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
271
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
272
+ # won't actually get a distinct list of the column you want (presuming the column
273
+ # has duplicates with multiple values for the ordered-by columns. So we use the
274
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
275
+ # making every row the same.
276
+ #
277
+ # distinct("posts.id", "posts.created_at desc")
278
+ def distinct(columns, order_by)
279
+ return "DISTINCT #{columns}" if order_by.blank?
280
+
281
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
282
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
283
+ order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
284
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
285
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
286
+ end
287
+ sql = "DISTINCT #{columns}, "
288
+ sql << order_columns * ", "
289
+ end
290
+
291
+ # ORDER BY clause for the passed order option.
292
+ #
293
+ # Uses column aliases as defined by #distinct.
294
+ def add_order_by_for_association_limiting!(sql, options)
295
+ return sql if options[:order].blank?
296
+
297
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
298
+ order.map! {|s| $1 if s =~ / (.*)/}
299
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
300
+
301
+ sql << "ORDER BY #{order}"
302
+ end
303
+
304
+ def tables
305
+ @connection.tables(nil, oracle_schema)
306
+ end
307
+
308
+ def columns(table_name, name=nil)
309
+ @connection.columns_internal(table_name, name, oracle_schema)
310
+ end
311
+
312
+ # QUOTING ==================================================
313
+ #
314
+ # see: abstract/quoting.rb
315
+
316
+ # Camelcase column names need to be quoted.
317
+ # Nonquoted identifiers can contain only alphanumeric characters from your
318
+ # database character set and the underscore (_), dollar sign ($), and pound sign (#).
319
+ # Database links can also contain periods (.) and "at" signs (@).
320
+ # Oracle strongly discourages you from using $ and # in nonquoted identifiers.
321
+ # Source: http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/sql_elements008.htm
322
+ def quote_column_name(name) #:nodoc:
323
+ name.to_s =~ /^[a-z0-9_$#]+$/ ? name.to_s : "\"#{name}\""
324
+ end
325
+
326
+ def quote_string(string) #:nodoc:
327
+ string.gsub(/'/, "''")
328
+ end
329
+
330
+ def quote(value, column = nil) #:nodoc:
331
+ if column && [:text, :binary].include?(column.type)
332
+ if /(.*?)\([0-9]+\)/ =~ column.sql_type
333
+ %Q{empty_#{ $1.downcase }()}
334
+ else
335
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
336
+ end
337
+ else
338
+ if column.respond_to?(:primary) && column.primary
339
+ return value.to_i.to_s
340
+ end
341
+ quoted = super
342
+ if value.acts_like?(:date) || value.acts_like?(:time)
343
+ quoted = "#{quoted_date(value)}"
344
+ end
345
+ quoted
346
+ end
347
+ end
348
+
349
+ def quoted_date(value)
350
+ %Q{TIMESTAMP'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
351
+ end
352
+
353
+ def quoted_true #:nodoc:
354
+ '1'
355
+ end
356
+
357
+ def quoted_false #:nodoc:
358
+ '0'
359
+ end
360
+
361
+ private
362
+ # In Oracle, schemas are created under your username:
363
+ # http://www.oracle.com/technology/obe/2day_dba/schema/schema.htm
364
+ def oracle_schema
365
+ @config[:username].to_s if @config[:username]
366
+ end
367
+
368
+ def select(sql, name=nil)
369
+ records = execute(sql,name)
370
+ records.each do |col|
371
+ col.delete('raw_rnum_')
372
+ end
373
+ records
374
+ end
375
+ end
376
+ end
377
+
@@ -0,0 +1,498 @@
1
+
2
+ module ::JdbcSpec
3
+ # Don't need to load native postgres adapter
4
+ $LOADED_FEATURES << "active_record/connection_adapters/postgresql_adapter.rb"
5
+
6
+ module ActiveRecordExtensions
7
+ add_method_to_remove_from_ar_base(:postgresql_connection)
8
+
9
+ def postgresql_connection(config)
10
+ require File.dirname(__FILE__) + "/../active_record/connection_adapters/postgresql_adapter"
11
+ config[:host] ||= "localhost"
12
+ config[:port] ||= 5432
13
+ config[:url] ||= "jdbc:postgresql://#{config[:host]}:#{config[:port]}/#{config[:database]}"
14
+ config[:url] << config[:pg_params] if config[:pg_params]
15
+ config[:driver] ||= "org.postgresql.Driver"
16
+ conn = jdbc_connection(config)
17
+ conn.execute("SET SEARCH_PATH TO #{config[:schema_search_path]}") if config[:schema_search_path]
18
+ conn
19
+ end
20
+ end
21
+
22
+ module PostgreSQL
23
+ def self.extended(mod)
24
+ mod.class.class_eval do
25
+ alias_chained_method :insert, :query_dirty, :insert
26
+ end
27
+ end
28
+
29
+ def self.adapter_matcher(name, *)
30
+ name =~ /postgre/i ? self : false
31
+ end
32
+
33
+ def self.column_selector
34
+ [/postgre/i, lambda {|cfg,col| col.extend(::JdbcSpec::PostgreSQL::Column)}]
35
+ end
36
+
37
+ def self.jdbc_connection_class
38
+ ::ActiveRecord::ConnectionAdapters::PostgresJdbcConnection
39
+ end
40
+
41
+ module Column
42
+ def type_cast(value)
43
+ case type
44
+ when :boolean then cast_to_boolean(value)
45
+ else super
46
+ end
47
+ end
48
+
49
+ def simplified_type(field_type)
50
+ return :integer if field_type =~ /^serial/i
51
+ return :string if field_type =~ /\[\]$/i || field_type =~ /^interval/i
52
+ return :string if field_type =~ /^(?:point|lseg|box|"?path"?|polygon|circle)/i
53
+ return :datetime if field_type =~ /^timestamp/i
54
+ return :float if field_type =~ /^real|^money/i
55
+ return :binary if field_type =~ /^bytea/i
56
+ return :boolean if field_type =~ /^bool/i
57
+ super
58
+ end
59
+
60
+ def cast_to_boolean(value)
61
+ return nil if value.nil?
62
+ if value == true || value == false
63
+ value
64
+ else
65
+ %w(true t 1).include?(value.to_s.downcase)
66
+ end
67
+ end
68
+
69
+ # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
70
+ def default_value(value)
71
+ # Boolean types
72
+ return "t" if value =~ /true/i
73
+ return "f" if value =~ /false/i
74
+
75
+ # Char/String/Bytea type values
76
+ return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
77
+
78
+ # Numeric values
79
+ return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
80
+
81
+ # Fixed dates / timestamp
82
+ return $1 if value =~ /^'(.+)'::(date|timestamp)/
83
+
84
+ # Anything else is blank, some user type, or some function
85
+ # and we can't know the value of that, so return nil.
86
+ return nil
87
+ end
88
+ end
89
+
90
+ def modify_types(tp)
91
+ tp[:primary_key] = "serial primary key"
92
+ tp[:string][:limit] = 255
93
+ tp[:integer][:limit] = nil
94
+ tp[:boolean][:limit] = nil
95
+ tp
96
+ end
97
+
98
+ def postgresql_version
99
+ @postgresql_version ||=
100
+ begin
101
+ value = select_value('SELECT version()')
102
+ if value =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
103
+ ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
104
+ else
105
+ 0
106
+ end
107
+ end
108
+ end
109
+
110
+ # Does PostgreSQL support migrations?
111
+ def supports_migrations?
112
+ true
113
+ end
114
+
115
+ # Does PostgreSQL support standard conforming strings?
116
+ def supports_standard_conforming_strings?
117
+ # Temporarily set the client message level above error to prevent unintentional
118
+ # error messages in the logs when working on a PostgreSQL database server that
119
+ # does not support standard conforming strings.
120
+ client_min_messages_old = client_min_messages
121
+ self.client_min_messages = 'panic'
122
+
123
+ # postgres-pr does not raise an exception when client_min_messages is set higher
124
+ # than error and "SHOW standard_conforming_strings" fails, but returns an empty
125
+ # PGresult instead.
126
+ has_support = select('SHOW standard_conforming_strings').to_a[0][0] rescue false
127
+ self.client_min_messages = client_min_messages_old
128
+ has_support
129
+ end
130
+
131
+ def supports_insert_with_returning?
132
+ postgresql_version >= 80200
133
+ end
134
+
135
+ def supports_ddl_transactions?
136
+ true
137
+ end
138
+
139
+ def supports_savepoints?
140
+ true
141
+ end
142
+
143
+ # Returns the configured supported identifier length supported by PostgreSQL,
144
+ # or report the default of 63 on PostgreSQL 7.x.
145
+ def table_alias_length
146
+ @table_alias_length ||= (postgresql_version >= 80000 ? select('SHOW max_identifier_length').to_a[0][0].to_i : 63)
147
+ end
148
+
149
+ def default_sequence_name(table_name, pk = nil)
150
+ default_pk, default_seq = pk_and_sequence_for(table_name)
151
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
152
+ end
153
+
154
+ # Resets sequence to the max value of the table's pk if present.
155
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
156
+ unless pk and sequence
157
+ default_pk, default_sequence = pk_and_sequence_for(table)
158
+ pk ||= default_pk
159
+ sequence ||= default_sequence
160
+ end
161
+ if pk
162
+ if sequence
163
+ quoted_sequence = quote_column_name(sequence)
164
+
165
+ select_value <<-end_sql, 'Reset sequence'
166
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
167
+ end_sql
168
+ else
169
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
170
+ end
171
+ end
172
+ end
173
+
174
+ def quote_regclass(table_name)
175
+ table_name.to_s.split('.').map do |part|
176
+ part =~ /".*"/i ? part : quote_table_name(part)
177
+ end.join('.')
178
+ end
179
+
180
+ # Find a table's primary key and sequence.
181
+ def pk_and_sequence_for(table) #:nodoc:
182
+ # First try looking for a sequence with a dependency on the
183
+ # given table's primary key.
184
+ result = select(<<-end_sql, 'PK and serial sequence')[0]
185
+ SELECT attr.attname, seq.relname
186
+ FROM pg_class seq,
187
+ pg_attribute attr,
188
+ pg_depend dep,
189
+ pg_namespace name,
190
+ pg_constraint cons
191
+ WHERE seq.oid = dep.objid
192
+ AND seq.relkind = 'S'
193
+ AND attr.attrelid = dep.refobjid
194
+ AND attr.attnum = dep.refobjsubid
195
+ AND attr.attrelid = cons.conrelid
196
+ AND attr.attnum = cons.conkey[1]
197
+ AND cons.contype = 'p'
198
+ AND dep.refobjid = '#{table}'::regclass
199
+ end_sql
200
+
201
+ if result.nil? or result.empty?
202
+ # If that fails, try parsing the primary key's default value.
203
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
204
+ # the 8.1+ nextval('foo'::regclass).
205
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
206
+ SELECT attr.attname,
207
+ CASE
208
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
209
+ substr(split_part(def.adsrc, '''', 2),
210
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
211
+ ELSE split_part(def.adsrc, '''', 2)
212
+ END as relname
213
+ FROM pg_class t
214
+ JOIN pg_attribute attr ON (t.oid = attrelid)
215
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
216
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
217
+ WHERE t.oid = '#{table}'::regclass
218
+ AND cons.contype = 'p'
219
+ AND def.adsrc ~* 'nextval'
220
+ end_sql
221
+ end
222
+
223
+ [result["attname"], result["relname"]]
224
+ rescue
225
+ nil
226
+ end
227
+
228
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
229
+ # Extract the table from the insert sql. Yuck.
230
+ table = sql.split(" ", 4)[2].gsub('"', '')
231
+
232
+ # Try an insert with 'returning id' if available (PG >= 8.2)
233
+ if supports_insert_with_returning? && id_value.nil?
234
+ pk, sequence_name = *pk_and_sequence_for(table) unless pk
235
+ if pk
236
+ id_value = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
237
+ clear_query_cache #FIXME: Why now?
238
+ return id_value
239
+ end
240
+ end
241
+
242
+ # Otherwise, plain insert
243
+ execute(sql, name)
244
+
245
+ # Don't need to look up id_value if we already have it.
246
+ # (and can't in case of non-sequence PK)
247
+ unless id_value
248
+ # If neither pk nor sequence name is given, look them up.
249
+ unless pk || sequence_name
250
+ pk, sequence_name = *pk_and_sequence_for(table)
251
+ end
252
+
253
+ # If a pk is given, fallback to default sequence name.
254
+ # Don't fetch last insert id for a table without a pk.
255
+ if pk && sequence_name ||= default_sequence_name(table, pk)
256
+ id_value = last_insert_id(table, sequence_name)
257
+ end
258
+ end
259
+ id_value
260
+ end
261
+
262
+ def columns(table_name, name=nil)
263
+ schema_name = @config[:schema_search_path]
264
+ if table_name =~ /\./
265
+ parts = table_name.split(/\./)
266
+ table_name = parts.pop
267
+ schema_name = parts.join(".")
268
+ end
269
+ @connection.columns_internal(table_name, name, schema_name)
270
+ end
271
+
272
+ # From postgresql_adapter.rb
273
+ def indexes(table_name, name = nil)
274
+ result = select_rows(<<-SQL, name)
275
+ SELECT i.relname, d.indisunique, a.attname
276
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a
277
+ WHERE i.relkind = 'i'
278
+ AND d.indexrelid = i.oid
279
+ AND d.indisprimary = 'f'
280
+ AND t.oid = d.indrelid
281
+ AND t.relname = '#{table_name}'
282
+ AND a.attrelid = t.oid
283
+ AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
284
+ OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
285
+ OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
286
+ OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
287
+ OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
288
+ ORDER BY i.relname
289
+ SQL
290
+
291
+ current_index = nil
292
+ indexes = []
293
+
294
+ result.each do |row|
295
+ if current_index != row[0]
296
+ indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", [])
297
+ current_index = row[0]
298
+ end
299
+
300
+ indexes.last.columns << row[2]
301
+ end
302
+
303
+ indexes
304
+ end
305
+
306
+ def last_insert_id(table, sequence_name)
307
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
308
+ end
309
+
310
+ def recreate_database(name)
311
+ drop_database(name)
312
+ create_database(name)
313
+ end
314
+
315
+ def create_database(name, options = {})
316
+ execute "CREATE DATABASE \"#{name}\" ENCODING='#{options[:encoding] || 'utf8'}'"
317
+ end
318
+
319
+ def drop_database(name)
320
+ execute "DROP DATABASE \"#{name}\""
321
+ end
322
+
323
+ def structure_dump
324
+ database = @config[:database]
325
+ if database.nil?
326
+ if @config[:url] =~ /\/([^\/]*)$/
327
+ database = $1
328
+ else
329
+ raise "Could not figure out what database this url is for #{@config["url"]}"
330
+ end
331
+ end
332
+
333
+ ENV['PGHOST'] = @config[:host] if @config[:host]
334
+ ENV['PGPORT'] = @config[:port].to_s if @config[:port]
335
+ ENV['PGPASSWORD'] = @config[:password].to_s if @config[:password]
336
+ search_path = @config[:schema_search_path]
337
+ search_path = "--schema=#{search_path}" if search_path
338
+
339
+ @connection.connection.close
340
+ begin
341
+ definition = `pg_dump -i -U "#{@config[:username]}" -s -x -O #{search_path} #{database}`
342
+ raise "Error dumping database" if $?.exitstatus == 1
343
+
344
+ # need to patch away any references to SQL_ASCII as it breaks the JDBC driver
345
+ definition.gsub(/SQL_ASCII/, 'UNICODE')
346
+ ensure
347
+ reconnect!
348
+ end
349
+ end
350
+
351
+ def _execute(sql, name = nil)
352
+ case sql.strip
353
+ when /\A\(?\s*(select|show)/i then
354
+ @connection.execute_query(sql)
355
+ else
356
+ @connection.execute_update(sql)
357
+ end
358
+ end
359
+
360
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
361
+ #
362
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
363
+ # requires that the ORDER BY include the distinct column.
364
+ #
365
+ # distinct("posts.id", "posts.created_at desc")
366
+ def distinct(columns, order_by)
367
+ return "DISTINCT #{columns}" if order_by.blank?
368
+
369
+ # construct a clean list of column names from the ORDER BY clause, removing
370
+ # any asc/desc modifiers
371
+ order_columns = order_by.split(',').collect { |s| s.split.first }
372
+ order_columns.delete_if(&:blank?)
373
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
374
+
375
+ # return a DISTINCT ON() clause that's distinct on the columns we want but includes
376
+ # all the required columns for the ORDER BY to work properly
377
+ sql = "DISTINCT ON (#{columns}) #{columns}, "
378
+ sql << order_columns * ', '
379
+ end
380
+
381
+ # ORDER BY clause for the passed order option.
382
+ #
383
+ # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
384
+ # by wrapping the sql as a sub-select and ordering in that query.
385
+ def add_order_by_for_association_limiting!(sql, options)
386
+ return sql if options[:order].blank?
387
+
388
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
389
+ order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
390
+ order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
391
+
392
+ sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
393
+ end
394
+
395
+ def quote(value, column = nil)
396
+ return value.quoted_id if value.respond_to?(:quoted_id)
397
+
398
+ if value.kind_of?(String) && column && column.type == :binary
399
+ "'#{escape_bytea(value)}'"
400
+ elsif column && column.type == :primary_key
401
+ return value.to_s
402
+ else
403
+ super
404
+ end
405
+ end
406
+
407
+ def escape_bytea(s)
408
+ if s
409
+ result = ''
410
+ s.each_byte { |c| result << sprintf('\\\\%03o', c) }
411
+ result
412
+ end
413
+ end
414
+
415
+ def quote_column_name(name)
416
+ %("#{name}")
417
+ end
418
+
419
+ def quoted_date(value) #:nodoc:
420
+ if value.acts_like?(:time) && value.respond_to?(:usec)
421
+ "#{super}.#{sprintf("%06d", value.usec)}"
422
+ else
423
+ super
424
+ end
425
+ end
426
+
427
+ def disable_referential_integrity(&block) #:nodoc:
428
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
429
+ yield
430
+ ensure
431
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
432
+ end
433
+
434
+ def rename_table(name, new_name)
435
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
436
+ end
437
+
438
+ def add_column(table_name, column_name, type, options = {})
439
+ execute("ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}")
440
+ change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
441
+ if options[:null] == false
442
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)} = '#{options[:default]}'") if options[:default]
443
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} SET NOT NULL")
444
+ end
445
+ end
446
+
447
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
448
+ begin
449
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit])}"
450
+ rescue ActiveRecord::StatementInvalid
451
+ # This is PG7, so we use a more arcane way of doing it.
452
+ begin_db_transaction
453
+ add_column(table_name, "#{column_name}_ar_tmp", type, options)
454
+ execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
455
+ remove_column(table_name, column_name)
456
+ rename_column(table_name, "#{column_name}_ar_tmp", column_name)
457
+ commit_db_transaction
458
+ end
459
+ change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
460
+ end
461
+
462
+ def change_column_default(table_name, column_name, default) #:nodoc:
463
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT '#{default}'"
464
+ end
465
+
466
+ def change_column_null(table_name, column_name, null, default = nil)
467
+ unless null || default.nil?
468
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
469
+ end
470
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
471
+ end
472
+
473
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
474
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
475
+ end
476
+
477
+ def remove_index(table_name, options) #:nodoc:
478
+ execute "DROP INDEX #{index_name(table_name, options)}"
479
+ end
480
+
481
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
482
+ return super unless type.to_s == 'integer'
483
+
484
+ if limit.nil? || limit == 4
485
+ 'integer'
486
+ elsif limit < 4
487
+ 'smallint'
488
+ else
489
+ 'bigint'
490
+ end
491
+ end
492
+
493
+ def tables
494
+ @connection.tables(database_name, nil, nil, ["TABLE"])
495
+ end
496
+ end
497
+ end
498
+