activerecord-jdbc-adapter 1.0.0.beta1-java → 1.0.0.beta2-java

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