activerecord-jdbc-adapter 1.0.0.beta1-java → 1.0.0.beta2-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 (57) hide show
  1. data/History.txt +37 -0
  2. data/Manifest.txt +8 -0
  3. data/README.txt +41 -88
  4. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  5. data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +9 -0
  6. data/lib/arel/engines/sql/compilers/mssql_compiler.rb +34 -0
  7. data/lib/arjdbc/db2/adapter.rb +232 -52
  8. data/lib/arjdbc/derby/adapter.rb +28 -1
  9. data/lib/arjdbc/derby/connection_methods.rb +1 -1
  10. data/lib/arjdbc/discover.rb +1 -1
  11. data/lib/arjdbc/firebird/adapter.rb +26 -0
  12. data/lib/arjdbc/h2/adapter.rb +13 -0
  13. data/lib/arjdbc/hsqldb/adapter.rb +8 -6
  14. data/lib/arjdbc/informix/adapter.rb +4 -0
  15. data/lib/arjdbc/jdbc/adapter.rb +27 -5
  16. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  17. data/lib/arjdbc/jdbc/connection.rb +76 -45
  18. data/lib/arjdbc/jdbc/jdbc.rake +22 -20
  19. data/lib/arjdbc/jdbc/type_converter.rb +9 -2
  20. data/lib/arjdbc/mssql/adapter.rb +102 -24
  21. data/lib/arjdbc/mssql/connection_methods.rb +19 -2
  22. data/lib/arjdbc/mssql/tsql_helper.rb +1 -0
  23. data/lib/arjdbc/mysql/adapter.rb +6 -0
  24. data/lib/arjdbc/mysql/connection_methods.rb +8 -7
  25. data/lib/arjdbc/oracle/adapter.rb +8 -6
  26. data/lib/arjdbc/postgresql/adapter.rb +51 -19
  27. data/lib/arjdbc/version.rb +1 -1
  28. data/lib/jdbc_adapter/rake_tasks.rb +3 -0
  29. data/rails_generators/templates/lib/tasks/jdbc.rake +2 -2
  30. data/rakelib/package.rake +2 -0
  31. data/rakelib/test.rake +6 -3
  32. data/src/java/arjdbc/derby/DerbyModule.java +30 -1
  33. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +74 -0
  34. data/src/java/arjdbc/jdbc/AdapterJavaService.java +7 -3
  35. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +45 -30
  36. data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +54 -1
  37. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +85 -0
  38. data/test/abstract_db_create.rb +6 -1
  39. data/test/db/jndi_config.rb +20 -10
  40. data/test/db2_simple_test.rb +34 -1
  41. data/test/derby_simple_test.rb +78 -0
  42. data/test/generic_jdbc_connection_test.rb +21 -1
  43. data/test/jndi_callbacks_test.rb +2 -1
  44. data/test/jndi_test.rb +1 -11
  45. data/test/models/entry.rb +20 -0
  46. data/test/mssql_limit_offset_test.rb +28 -0
  47. data/test/mssql_simple_test.rb +7 -1
  48. data/test/mysql_info_test.rb +49 -6
  49. data/test/mysql_simple_test.rb +4 -0
  50. data/test/oracle_simple_test.rb +3 -47
  51. data/test/oracle_specific_test.rb +83 -0
  52. data/test/postgres_db_create_test.rb +6 -0
  53. data/test/postgres_drop_db_test.rb +16 -0
  54. data/test/postgres_simple_test.rb +17 -0
  55. data/test/postgres_table_alias_length_test.rb +15 -0
  56. data/test/simple.rb +17 -4
  57. metadata +33 -7
@@ -55,6 +55,25 @@ module ::ArJdbc
55
55
  tp
56
56
  end
57
57
 
58
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
59
+ # MSSQL's NVARCHAR(n | max) column supports either a number between 1 and
60
+ # 4000, or the word "MAX", which corresponds to 2**30-1 UCS-2 characters.
61
+ #
62
+ # It does not accept NVARCHAR(1073741823) here, so we have to change it
63
+ # to NVARCHAR(MAX), even though they are logically equivalent.
64
+ #
65
+ # MSSQL Server 2000 is skipped here because I don't know how it will behave.
66
+ #
67
+ # See: http://msdn.microsoft.com/en-us/library/ms186939.aspx
68
+ if type.to_s == 'string' and limit == 1073741823 and sqlserver_version != "2000"
69
+ 'NVARCHAR(MAX)'
70
+ elsif %w( boolean date datetime ).include?(type.to_s)
71
+ super(type) # cannot specify limit/precision/scale with these types
72
+ else
73
+ super
74
+ end
75
+ end
76
+
58
77
  module Column
59
78
  attr_accessor :identity, :is_special
60
79
 
@@ -67,9 +86,9 @@ module ::ArJdbc
67
86
  when /timestamp/i then :timestamp
68
87
  when /time/i then :time
69
88
  when /date/i then :date
70
- when /text|ntext/i then :text
89
+ when /text|ntext|xml/i then :text
71
90
  when /binary|image|varbinary/i then :binary
72
- when /char|nchar|nvarchar|string|varchar/i then :string
91
+ when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
73
92
  when /bit/i then :boolean
74
93
  when /uniqueidentifier/i then :string
75
94
  end
@@ -94,7 +113,15 @@ module ::ArJdbc
94
113
  when :binary then unquote value
95
114
  else value
96
115
  end
116
+ end
97
117
 
118
+ def extract_limit(sql_type)
119
+ case sql_type
120
+ when /text|ntext|xml|binary|image|varbinary|bit/
121
+ nil
122
+ else
123
+ super
124
+ end
98
125
  end
99
126
 
100
127
  def is_utf8?
@@ -108,10 +135,14 @@ module ::ArJdbc
108
135
  def cast_to_time(value)
109
136
  return value if value.is_a?(Time)
110
137
  time_array = ParseDate.parsedate(value)
138
+ return nil if !time_array.any?
111
139
  time_array[0] ||= 2000
112
140
  time_array[1] ||= 1
113
141
  time_array[2] ||= 1
114
- Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
142
+ return Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
143
+
144
+ # Try DateTime instead - the date may be outside the time period support by Time.
145
+ DateTime.new(*time_array[0..5]) rescue nil
115
146
  end
116
147
 
117
148
  def cast_to_date(value)
@@ -127,8 +158,18 @@ module ::ArJdbc
127
158
  return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
128
159
  end
129
160
  end
161
+ if value.is_a?(DateTime)
162
+ begin
163
+ # Attempt to convert back to a Time, but it could fail for dates significantly in the past/future.
164
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
165
+ rescue ArgumentError
166
+ return value
167
+ end
168
+ end
169
+
130
170
  return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
131
- value
171
+
172
+ return value.is_a?(Date) ? value : nil
132
173
  end
133
174
 
134
175
  # These methods will only allow the adapter to insert binary data with a length of 7K or less
@@ -143,7 +184,9 @@ module ::ArJdbc
143
184
  return value.quoted_id if value.respond_to?(:quoted_id)
144
185
 
145
186
  case value
146
- when String, ActiveSupport::Multibyte::Chars
187
+ # SQL Server 2000 doesn't let you insert an integer into a NVARCHAR
188
+ # column, so we include Integer here.
189
+ when String, ActiveSupport::Multibyte::Chars, Integer
147
190
  value = value.to_s
148
191
  if column && column.type == :binary
149
192
  "'#{quote_string(ArJdbc::MsSQL::Column.string_to_binary(value))}'" # ' (for ruby-mode)
@@ -181,6 +224,10 @@ module ::ArJdbc
181
224
  quote false
182
225
  end
183
226
 
227
+ def adapter_name #:nodoc:
228
+ 'MsSQL'
229
+ end
230
+
184
231
  module SqlServer2000LimitOffset
185
232
  def add_limit_offset!(sql, options)
186
233
  limit = options[:limit]
@@ -205,7 +252,13 @@ module ::ArJdbc
205
252
  #I am not sure this will cover all bases. but all the tests pass
206
253
  new_order = "#{order}, #{table_name}.id" if order.index("#{table_name}.id").nil?
207
254
  new_order ||= order
208
- new_sql = "#{select} TOP #{limit} #{rest_of_query} WHERE #{table_name}.id NOT IN (#{select} TOP #{offset} #{table_name}.id #{rest} ORDER BY #{new_order}) ORDER BY #{order} "
255
+
256
+ if (rest_of_query.match(/WHERE/).nil?)
257
+ new_sql = "#{select} TOP #{limit} #{rest_of_query} WHERE #{table_name}.id NOT IN (#{select} TOP #{offset} #{table_name}.id #{rest} ORDER BY #{new_order}) ORDER BY #{order} "
258
+ else
259
+ new_sql = "#{select} TOP #{limit} #{rest_of_query} AND #{table_name}.id NOT IN (#{select} TOP #{offset} #{table_name}.id #{rest} ORDER BY #{new_order}) ORDER BY #{order} "
260
+ end
261
+
209
262
  sql.replace(new_sql)
210
263
  end
211
264
  end
@@ -223,8 +276,11 @@ module ::ArJdbc
223
276
  sql.sub!(/ ORDER BY.*$/i, '')
224
277
  find_select = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im
225
278
  whole, select, rest_of_query = find_select.match(sql).to_a
226
- new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(ORDER BY #{order}) AS row_num, #{rest_of_query}"
227
- new_sql << ") AS t WHERE t.row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
279
+ if rest_of_query.strip!.first == '*'
280
+ from_table = /.*FROM\s*\b(\w*)\b/i.match(rest_of_query).to_a[1]
281
+ end
282
+ new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(ORDER BY #{order}) AS _row_num, #{from_table + '.' if from_table}#{rest_of_query}"
283
+ new_sql << ") AS t WHERE t._row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
228
284
  sql.replace(new_sql)
229
285
  end
230
286
  end
@@ -260,12 +316,14 @@ module ::ArJdbc
260
316
  end
261
317
 
262
318
  def rename_table(name, new_name)
319
+ clear_cached_table(name)
263
320
  execute "EXEC sp_rename '#{name}', '#{new_name}'"
264
321
  end
265
322
 
266
323
  # Adds a new column to the named table.
267
324
  # See TableDefinition#column for details of the options you can use.
268
325
  def add_column(table_name, column_name, type, options = {})
326
+ clear_cached_table(table_name)
269
327
  add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
270
328
  add_column_options!(add_column_sql, options)
271
329
  # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
@@ -274,15 +332,18 @@ module ::ArJdbc
274
332
  end
275
333
 
276
334
  def rename_column(table, column, new_column_name)
335
+ clear_cached_table(table)
277
336
  execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
278
337
  end
279
338
 
280
339
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
340
+ clear_cached_table(table_name)
281
341
  change_column_type(table_name, column_name, type, options)
282
342
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
283
343
  end
284
344
 
285
345
  def change_column_type(table_name, column_name, type, options = {}) #:nodoc:
346
+ clear_cached_table(table_name)
286
347
  sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
287
348
  if options.has_key?(:null)
288
349
  sql += (options[:null] ? " NULL" : " NOT NULL")
@@ -291,6 +352,7 @@ module ::ArJdbc
291
352
  end
292
353
 
293
354
  def change_column_default(table_name, column_name, default) #:nodoc:
355
+ clear_cached_table(table_name)
294
356
  remove_default_constraint(table_name, column_name)
295
357
  unless default.nil?
296
358
  execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
@@ -298,12 +360,14 @@ module ::ArJdbc
298
360
  end
299
361
 
300
362
  def remove_column(table_name, column_name)
363
+ clear_cached_table(table_name)
301
364
  remove_check_constraints(table_name, column_name)
302
365
  remove_default_constraint(table_name, column_name)
303
366
  execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
304
367
  end
305
368
 
306
369
  def remove_default_constraint(table_name, column_name)
370
+ clear_cached_table(table_name)
307
371
  defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
308
372
  defaults.each {|constraint|
309
373
  execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
@@ -311,6 +375,7 @@ module ::ArJdbc
311
375
  end
312
376
 
313
377
  def remove_check_constraints(table_name, column_name)
378
+ clear_cached_table(table_name)
314
379
  # TODO remove all constraints in single method
315
380
  constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
316
381
  constraints.each do |constraint|
@@ -323,17 +388,31 @@ module ::ArJdbc
323
388
  end
324
389
 
325
390
  def columns(table_name, name = nil)
391
+ # It's possible for table_name to be an empty string, or nil, if something attempts to issue SQL
392
+ # which doesn't involve a table. IE. "SELECT 1" or "SELECT * from someFunction()".
393
+ return [] if table_name.blank?
394
+ table_name = table_name.to_s if table_name.is_a?(Symbol)
395
+
396
+ # Remove []'s from around the table name, valid in a select statement, but not when matching metadata.
397
+ table_name = table_name.gsub(/[\[\]]/, '')
398
+
326
399
  return [] if table_name =~ /^information_schema\./i
327
- cc = super
328
- cc.each do |col|
329
- col.identity = true if col.sql_type =~ /identity/i
330
- col.is_special = true if col.sql_type =~ /text|ntext|image/i
400
+ @table_columns = {} unless @table_columns
401
+ unless @table_columns[table_name]
402
+ @table_columns[table_name] = super
403
+ @table_columns[table_name].each do |col|
404
+ col.identity = true if col.sql_type =~ /identity/i
405
+ col.is_special = true if col.sql_type =~ /text|ntext|image|xml/i
406
+ end
331
407
  end
332
- cc
408
+ @table_columns[table_name]
333
409
  end
334
410
 
335
411
  def _execute(sql, name = nil)
336
- if sql.lstrip =~ /^insert/i
412
+ # Match the start of the sql to determine appropriate behaviour. Be aware of
413
+ # multi-line sql which might begin with 'create stored_proc' and contain 'insert into ...' lines.
414
+ # Possible improvements include ignoring comment blocks prior to the first statement.
415
+ if sql.lstrip =~ /\Ainsert/i
337
416
  if query_requires_identity_insert?(sql)
338
417
  table_name = get_table_name(sql)
339
418
  with_identity_insert_enabled(table_name) do
@@ -342,9 +421,9 @@ module ::ArJdbc
342
421
  else
343
422
  @connection.execute_insert(sql)
344
423
  end
345
- elsif sql.lstrip =~ /^(create|exec)/i
424
+ elsif sql.lstrip =~ /\A(create|exec)/i
346
425
  @connection.execute_update(sql)
347
- elsif sql.lstrip =~ /^\(?\s*(select|show)/i
426
+ elsif sql.lstrip =~ /\A\(?\s*(select|show)/i
348
427
  repair_special_columns(sql)
349
428
  @connection.execute_query(sql)
350
429
  else
@@ -392,12 +471,9 @@ module ::ArJdbc
392
471
  end
393
472
 
394
473
  def identity_column(table_name)
395
- @table_columns = {} unless @table_columns
396
- @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
397
- @table_columns[table_name].each do |col|
474
+ columns(table_name).each do |col|
398
475
  return col.name if col.identity
399
476
  end
400
-
401
477
  return nil
402
478
  end
403
479
 
@@ -420,9 +496,7 @@ module ::ArJdbc
420
496
 
421
497
  def get_special_columns(table_name)
422
498
  special = []
423
- @table_columns ||= {}
424
- @table_columns[table_name] ||= columns(table_name)
425
- @table_columns[table_name].each do |col|
499
+ columns(table_name).each do |col|
426
500
  special << col.name if col.is_special
427
501
  end
428
502
  special
@@ -449,7 +523,11 @@ module ::ArJdbc
449
523
  # Look for an id column. Return it, without changing case, to cover dbs with a case-sensitive collation.
450
524
  columns(table_name).each { |column| return column.name if column.name =~ /^id$/i }
451
525
  # Give up and provide something which is going to crash almost certainly
452
- "id"
526
+ columns(table_name)[0].name
527
+ end
528
+
529
+ def clear_cached_table(name)
530
+ (@table_columns ||= {}).delete(name)
453
531
  end
454
532
  end
455
533
  end
@@ -4,9 +4,26 @@ class ActiveRecord::Base
4
4
  require "arjdbc/mssql"
5
5
  config[:host] ||= "localhost"
6
6
  config[:port] ||= 1433
7
- config[:url] ||= "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
8
7
  config[:driver] ||= "net.sourceforge.jtds.jdbc.Driver"
9
- embedded_driver(config)
8
+
9
+ url = "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
10
+
11
+ # Instance is often a preferrable alternative to port when dynamic ports are used.
12
+ # If instance is specified then port is essentially ignored.
13
+ url << ";instance=#{config[:instance]}" if config[:instance]
14
+
15
+ # This will enable windows domain-based authentication and will require the JTDS native libraries be available.
16
+ url << ";domain=#{config[:domain]}" if config[:domain]
17
+
18
+ # AppName is shown in sql server as additional information against the connection.
19
+ url << ";appname=#{config[:appname]}" if config[:appname]
20
+ config[:url] ||= url
21
+
22
+ if !config[:domain]
23
+ config[:username] ||= "sa"
24
+ config[:password] ||= ""
25
+ end
26
+ jdbc_connection(config)
10
27
  end
11
28
  alias_method :jdbcmssql_connection, :mssql_connection
12
29
  end
@@ -10,6 +10,7 @@ module TSqlMethods
10
10
  end
11
11
 
12
12
  def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
13
+ limit = nil if %w(text binary).include? type.to_s
13
14
  return 'uniqueidentifier' if (type.to_s == 'uniqueidentifier')
14
15
  return super unless type.to_s == 'integer'
15
16
 
@@ -58,6 +58,8 @@ module ::ArJdbc
58
58
  when /^mediumint/i; 3
59
59
  when /^smallint/i; 2
60
60
  when /^tinyint/i; 1
61
+ when /^(datetime|timestamp)/
62
+ nil
61
63
  else
62
64
  super
63
65
  end
@@ -92,6 +94,10 @@ module ::ArJdbc
92
94
  "= BINARY"
93
95
  end
94
96
 
97
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
98
+ where_sql
99
+ end
100
+
95
101
  # QUOTING ==================================================
96
102
 
97
103
  def quote(value, column = nil)
@@ -1,18 +1,18 @@
1
1
  # Don't need to load native mysql adapter
2
2
  $LOADED_FEATURES << "active_record/connection_adapters/mysql_adapter.rb"
3
+ $LOADED_FEATURES << "active_record/connection_adapters/mysql2_adapter.rb"
3
4
 
4
5
  class ActiveRecord::Base
5
6
  class << self
6
7
  def mysql_connection(config)
7
8
  require "arjdbc/mysql"
8
9
  config[:port] ||= 3306
9
- url_options = "zeroDateTimeBehavior=convertToNull&jdbcCompliantTruncation=false&useUnicode=true&characterEncoding="
10
- url_options << (config[:encoding] || 'utf8')
11
- if config[:url]
12
- config[:url] = config[:url]['?'] ? "#{config[:url]}&#{url_options}" : "#{config[:url]}?#{url_options}"
13
- else
14
- config[:url] = "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}?#{url_options}"
15
- end
10
+ options = (config[:options] ||= {})
11
+ options['zeroDateTimeBehavior'] ||= 'convertToNull'
12
+ options['jdbcCompliantTruncation'] ||= 'false'
13
+ options['useUnicode'] ||= 'true'
14
+ options['characterEncoding'] = config[:encoding] || 'utf8'
15
+ config[:url] ||= "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}"
16
16
  config[:driver] ||= "com.mysql.jdbc.Driver"
17
17
  config[:adapter_class] = ActiveRecord::ConnectionAdapters::MysqlAdapter
18
18
  connection = jdbc_connection(config)
@@ -20,6 +20,7 @@ class ActiveRecord::Base
20
20
  connection
21
21
  end
22
22
  alias_method :jdbcmysql_connection, :mysql_connection
23
+ alias_method :mysql2_connection, :mysql_connection
23
24
  end
24
25
  end
25
26
 
@@ -33,6 +33,10 @@ module ::ArJdbc
33
33
  [/oracle/i, lambda {|cfg,col| col.extend(::ArJdbc::Oracle::Column)}]
34
34
  end
35
35
 
36
+ def self.jdbc_connection_class
37
+ ::ActiveRecord::ConnectionAdapters::OracleJdbcConnection
38
+ end
39
+
36
40
  module Column
37
41
  def primary=(val)
38
42
  super
@@ -351,17 +355,15 @@ module ::ArJdbc
351
355
  return value.to_i.to_s
352
356
  end
353
357
  quoted = super
354
- if value.acts_like?(:date) || value.acts_like?(:time)
355
- quoted = "#{quoted_date(value)}"
358
+ if value.acts_like?(:date)
359
+ quoted = %Q{DATE'#{quoted_date(value)}'}
360
+ elsif value.acts_like?(:time)
361
+ quoted = %Q{TIMESTAMP'#{quoted_date(value)}'}
356
362
  end
357
363
  quoted
358
364
  end
359
365
  end
360
366
 
361
- def quoted_date(value)
362
- %Q{TIMESTAMP'#{super}'}
363
- end
364
-
365
367
  def quoted_true #:nodoc:
366
368
  '1'
367
369
  end
@@ -31,7 +31,7 @@ module ::ArJdbc
31
31
  return :string if field_type =~ /\[\]$/i || field_type =~ /^interval/i
32
32
  return :string if field_type =~ /^(?:point|lseg|box|"?path"?|polygon|circle)/i
33
33
  return :datetime if field_type =~ /^timestamp/i
34
- return :float if field_type =~ /^real|^money/i
34
+ return :float if field_type =~ /^(?:real|double precision)$/i
35
35
  return :binary if field_type =~ /^bytea/i
36
36
  return :boolean if field_type =~ /^bool/i
37
37
  super
@@ -72,6 +72,8 @@ module ::ArJdbc
72
72
  tp[:string][:limit] = 255
73
73
  tp[:integer][:limit] = nil
74
74
  tp[:boolean][:limit] = nil
75
+ tp[:float] = { :name => "float" }
76
+ tp[:decimal] = { :name => "decimal" }
75
77
  tp
76
78
  end
77
79
 
@@ -124,6 +126,10 @@ module ::ArJdbc
124
126
  true
125
127
  end
126
128
 
129
+ def supports_count_distinct? #:nodoc:
130
+ false
131
+ end
132
+
127
133
  def create_savepoint
128
134
  execute("SAVEPOINT #{current_savepoint_name}")
129
135
  end
@@ -139,7 +145,7 @@ module ::ArJdbc
139
145
  # Returns the configured supported identifier length supported by PostgreSQL,
140
146
  # or report the default of 63 on PostgreSQL 7.x.
141
147
  def table_alias_length
142
- @table_alias_length ||= (postgresql_version >= 80000 ? select('SHOW max_identifier_length').to_a[0][0].to_i : 63)
148
+ @table_alias_length ||= (postgresql_version >= 80000 ? select_one('SHOW max_identifier_length')['max_identifier_length'].to_i : 63)
143
149
  end
144
150
 
145
151
  def default_sequence_name(table_name, pk = nil)
@@ -167,12 +173,6 @@ module ::ArJdbc
167
173
  end
168
174
  end
169
175
 
170
- def quote_regclass(table_name)
171
- table_name.to_s.split('.').map do |part|
172
- part =~ /".*"/i ? part : quote_table_name(part)
173
- end.join('.')
174
- end
175
-
176
176
  # Find a table's primary key and sequence.
177
177
  def pk_and_sequence_for(table) #:nodoc:
178
178
  # First try looking for a sequence with a dependency on the
@@ -191,7 +191,7 @@ module ::ArJdbc
191
191
  AND attr.attrelid = cons.conrelid
192
192
  AND attr.attnum = cons.conkey[1]
193
193
  AND cons.contype = 'p'
194
- AND dep.refobjid = '#{table}'::regclass
194
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
195
195
  end_sql
196
196
 
197
197
  if result.nil? or result.empty?
@@ -210,7 +210,7 @@ module ::ArJdbc
210
210
  JOIN pg_attribute attr ON (t.oid = attrelid)
211
211
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
212
212
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
213
- WHERE t.oid = '#{table}'::regclass
213
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
214
214
  AND cons.contype = 'p'
215
215
  AND def.adsrc ~* 'nextval'
216
216
  end_sql
@@ -313,7 +313,7 @@ module ::ArJdbc
313
313
  end
314
314
 
315
315
  def drop_database(name)
316
- execute "DROP DATABASE \"#{name}\""
316
+ execute "DROP DATABASE IF EXISTS \"#{name}\""
317
317
  end
318
318
 
319
319
  def create_schema(schema_name, pg_username)
@@ -396,13 +396,23 @@ module ::ArJdbc
396
396
  sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
397
397
  end
398
398
 
399
- def quote(value, column = nil)
400
- return value.quoted_id if value.respond_to?(:quoted_id)
399
+ def quote(value, column = nil) #:nodoc:
400
+ return super unless column
401
401
 
402
- if value.kind_of?(String) && column && column.type == :binary
402
+ if value.kind_of?(String) && column.type == :binary
403
403
  "'#{escape_bytea(value)}'"
404
- elsif column && column.type == :primary_key
405
- return value.to_s
404
+ elsif value.kind_of?(String) && column.sql_type == 'xml'
405
+ "xml '#{quote_string(value)}'"
406
+ elsif value.kind_of?(Numeric) && column.sql_type == 'money'
407
+ # Not truly string input, so doesn't require (or allow) escape string syntax.
408
+ "'#{value}'"
409
+ elsif value.kind_of?(String) && column.sql_type =~ /^bit/
410
+ case value
411
+ when /^[01]*$/
412
+ "B'#{value}'" # Bit-string notation
413
+ when /^[0-9A-F]*$/i
414
+ "X'#{value}'" # Hexadecimal notation
415
+ end
406
416
  else
407
417
  super
408
418
  end
@@ -416,6 +426,17 @@ module ::ArJdbc
416
426
  end
417
427
  end
418
428
 
429
+ def quote_table_name(name)
430
+ schema, name_part = extract_pg_identifier_from_name(name.to_s)
431
+
432
+ unless name_part
433
+ quote_column_name(schema)
434
+ else
435
+ table_name, name_part = extract_pg_identifier_from_name(name_part)
436
+ "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
437
+ end
438
+ end
439
+
419
440
  def quote_column_name(name)
420
441
  %("#{name}")
421
442
  end
@@ -440,7 +461,7 @@ module ::ArJdbc
440
461
  end
441
462
 
442
463
  def add_column(table_name, column_name, type, options = {})
443
- execute("ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}")
464
+ execute("ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
444
465
  change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
445
466
  if options[:null] == false
446
467
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)} = '#{options[:default]}'") if options[:default]
@@ -450,12 +471,12 @@ module ::ArJdbc
450
471
 
451
472
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
452
473
  begin
453
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit])}"
474
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
454
475
  rescue ActiveRecord::StatementInvalid
455
476
  # This is PG7, so we use a more arcane way of doing it.
456
477
  begin_db_transaction
457
478
  add_column(table_name, "#{column_name}_ar_tmp", type, options)
458
- execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
479
+ execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
459
480
  remove_column(table_name, column_name)
460
481
  rename_column(table_name, "#{column_name}_ar_tmp", column_name)
461
482
  commit_db_transaction
@@ -497,6 +518,17 @@ module ::ArJdbc
497
518
  def tables
498
519
  @connection.tables(database_name, nil, nil, ["TABLE"])
499
520
  end
521
+
522
+ private
523
+ def extract_pg_identifier_from_name(name)
524
+ match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
525
+
526
+ if match_data
527
+ rest = name[match_data[0].length..-1]
528
+ rest = rest[1..-1] if rest[0,1] == "."
529
+ [match_data[1], (rest.length > 0 ? rest : nil)]
530
+ end
531
+ end
500
532
  end
501
533
  end
502
534