activerecord-jdbc-adapter 0.9.0.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/History.txt +31 -0
  2. data/Manifest.txt +7 -0
  3. data/README.txt +15 -2
  4. data/Rakefile +28 -30
  5. data/lib/active_record/connection_adapters/h2_adapter.rb +13 -1
  6. data/lib/active_record/connection_adapters/jdbc_adapter.rb +78 -96
  7. data/lib/jdbc_adapter/jdbc.rake +15 -5
  8. data/lib/jdbc_adapter/jdbc_adapter_internal.jar +0 -0
  9. data/lib/jdbc_adapter/jdbc_cachedb.rb +4 -4
  10. data/lib/jdbc_adapter/jdbc_db2.rb +5 -7
  11. data/lib/jdbc_adapter/jdbc_derby.rb +57 -30
  12. data/lib/jdbc_adapter/jdbc_firebird.rb +2 -2
  13. data/lib/jdbc_adapter/jdbc_hsqldb.rb +53 -46
  14. data/lib/jdbc_adapter/jdbc_informix.rb +4 -5
  15. data/lib/jdbc_adapter/jdbc_mimer.rb +2 -2
  16. data/lib/jdbc_adapter/jdbc_mssql.rb +25 -23
  17. data/lib/jdbc_adapter/jdbc_mysql.rb +20 -22
  18. data/lib/jdbc_adapter/jdbc_oracle.rb +115 -117
  19. data/lib/jdbc_adapter/jdbc_postgre.rb +129 -59
  20. data/lib/jdbc_adapter/jdbc_sqlite3.rb +149 -28
  21. data/lib/jdbc_adapter/jdbc_sybase.rb +13 -2
  22. data/lib/jdbc_adapter/missing_functionality_helper.rb +12 -3
  23. data/lib/jdbc_adapter/version.rb +1 -1
  24. data/src/java/jdbc_adapter/JdbcAdapterInternalService.java +6 -1101
  25. data/src/java/jdbc_adapter/JdbcDerbySpec.java +26 -23
  26. data/src/java/jdbc_adapter/JdbcMySQLSpec.java +79 -28
  27. data/src/java/jdbc_adapter/PostgresRubyJdbcConnection.java +35 -0
  28. data/src/java/jdbc_adapter/RubyJdbcConnection.java +1149 -0
  29. data/src/java/jdbc_adapter/SQLBlock.java +12 -3
  30. data/src/java/jdbc_adapter/Sqlite3RubyJdbcConnection.java +41 -0
  31. data/test/activerecord/connection_adapters/type_conversion_test.rb +1 -1
  32. data/test/db/derby.rb +0 -3
  33. data/test/db/h2.rb +0 -3
  34. data/test/db/hsqldb.rb +1 -4
  35. data/test/db/mysql.rb +1 -0
  36. data/test/db/oracle.rb +5 -0
  37. data/test/db/sqlite3.rb +7 -3
  38. data/test/derby_migration_test.rb +21 -0
  39. data/test/has_many_through.rb +11 -4
  40. data/test/jdbc_common.rb +13 -1
  41. data/test/models/data_types.rb +11 -1
  42. data/test/models/mixed_case.rb +20 -0
  43. data/test/mysql_multibyte_test.rb +4 -0
  44. data/test/oracle_simple_test.rb +1 -1
  45. data/test/postgres_mixed_case_test.rb +19 -0
  46. data/test/simple.rb +220 -41
  47. data/test/sqlite3_simple_test.rb +83 -0
  48. data/test/sybase_jtds_simple_test.rb +6 -0
  49. metadata +12 -10
@@ -7,24 +7,27 @@ module ::JdbcSpec
7
7
  module ActiveRecordExtensions
8
8
  def mysql_connection(config)
9
9
  config[:port] ||= 3306
10
+ url_options = "zeroDateTimeBehavior=convertToNull&jdbcCompliantTruncation=false&useUnicode=true&characterEncoding="
11
+ url_options << (config[:encoding] || 'utf8')
10
12
  if config[:url]
11
- config[:url] = config[:url]['?'] ? "#{config[:url]}&#{MySQL::URL_OPTIONS}" : "#{config[:url]}?#{MySQL::URL_OPTIONS}"
13
+ config[:url] = config[:url]['?'] ? "#{config[:url]}&#{url_options}" : "#{config[:url]}?#{url_options}"
12
14
  else
13
- config[:url] = "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}?#{MySQL::URL_OPTIONS}"
15
+ config[:url] = "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}?#{url_options}"
14
16
  end
15
17
  config[:driver] = "com.mysql.jdbc.Driver"
16
- jdbc_connection(config)
18
+ connection = jdbc_connection(config)
19
+ ::JdbcSpec::MySQL.kill_cancel_timer(connection.raw_connection)
20
+ connection
17
21
  end
18
22
  end
19
23
 
20
24
  module MySQL
21
- URL_OPTIONS = "zeroDateTimeBehavior=convertToNull&jdbcCompliantTruncation=false&useUnicode=true&characterEncoding=utf8"
22
- def self.column_selector
23
- [/mysql/i, lambda {|cfg,col| col.extend(::JdbcSpec::MySQL::Column)}]
25
+ def self.adapter_matcher(name, *)
26
+ name =~ /mysql/i ? self : false
24
27
  end
25
28
 
26
- def self.adapter_selector
27
- [/mysql/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::MySQL)}]
29
+ def self.column_selector
30
+ [/mysql/i, lambda {|cfg,col| col.extend(::JdbcSpec::MySQL::Column)}]
28
31
  end
29
32
 
30
33
  def self.extended(adapter)
@@ -82,14 +85,6 @@ module ::JdbcSpec
82
85
  end
83
86
  end
84
87
 
85
- def quote_column_name(name) #:nodoc:
86
- "`#{name}`"
87
- end
88
-
89
- def quote_table_name(name) #:nodoc:
90
- quote_column_name(name).gsub('.', '`.`')
91
- end
92
-
93
88
  def quoted_true
94
89
  "1"
95
90
  end
@@ -153,12 +148,15 @@ module ::JdbcSpec
153
148
  create_database(name)
154
149
  end
155
150
 
151
+ def character_set(options) #:nodoc:
152
+ str = "CHARACTER SET `#{options[:charset] || 'utf8'}`"
153
+ str += " COLLATE `#{options[:collation]}`" if options[:collation]
154
+ str
155
+ end
156
+ private :character_set
157
+
156
158
  def create_database(name, options = {}) #:nodoc:
157
- if options[:collation]
158
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
159
- else
160
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
161
- end
159
+ execute "CREATE DATABASE `#{name}` DEFAULT #{character_set(options)}"
162
160
  end
163
161
 
164
162
  def drop_database(name) #:nodoc:
@@ -170,7 +168,7 @@ module ::JdbcSpec
170
168
  end
171
169
 
172
170
  def create_table(name, options = {}) #:nodoc:
173
- super(name, {:options => "ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin"}.merge(options))
171
+ super(name, {:options => "ENGINE=InnoDB #{character_set(options)}"}.merge(options))
174
172
  end
175
173
 
176
174
  def rename_table(name, new_name)
@@ -1,19 +1,3 @@
1
- module ::ActiveRecord
2
- class Base
3
- def after_save_with_oracle_lob() #:nodoc:
4
- if connection.is_a?(JdbcSpec::Oracle)
5
- self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each { |c|
6
- value = self[c.name]
7
- value = value.to_yaml if unserializable_attribute?(c.name, c)
8
- next if value.nil? || (value == '')
9
-
10
- connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
11
- }
12
- end
13
- end
14
- end
15
- end
16
-
17
1
  module ::JdbcSpec
18
2
  module ActiveRecordExtensions
19
3
  def oracle_connection(config)
@@ -26,58 +10,59 @@ module ::JdbcSpec
26
10
 
27
11
  module Oracle
28
12
  def self.extended(mod)
29
- ActiveRecord::Base.after_save :after_save_with_oracle_lob unless @lob_callback_added
30
- @lob_callback_added = true
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
+ end
30
+
31
+ def self.adapter_matcher(name, *)
32
+ name =~ /oracle/i ? self : false
31
33
  end
32
34
 
33
35
  def self.column_selector
34
36
  [/oracle/i, lambda {|cfg,col| col.extend(::JdbcSpec::Oracle::Column)}]
35
37
  end
36
38
 
37
- def self.adapter_selector
38
- [/oracle/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::Oracle)
39
- =begin
40
- (adapt.methods - %w(send __send__ id class methods is_a? kind_of? verify! active?)).each do |name|
41
- new_name = "__#{name}"
42
- (class << adapt; self; end).send :alias_method, new_name, name
43
- (class << adapt; self; end).send :define_method, name do |*args|
44
- puts "#{name}(#{args.inspect})"
45
- adapt.send new_name, *args
46
- end
47
- end
48
- =end
49
- }]
50
- end
51
-
52
39
  module Column
53
40
  def type_cast(value)
54
41
  return nil if value.nil?
55
42
  case type
56
- when :string then value
57
- when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
58
- when :primary_key then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
59
- when :float then value.to_f
60
- when :datetime then JdbcSpec::Oracle::Column.cast_to_date_or_time(value)
61
- when :time then JdbcSpec::Oracle::Column.cast_to_time(value)
62
- when :decimal then self.class.value_to_decimal(value)
63
- when :boolean then self.class.value_to_boolean(value)
64
- else value
43
+ when :datetime then JdbcSpec::Oracle::Column.string_to_time(value, self.class)
44
+ else
45
+ super
65
46
  end
66
47
  end
67
-
48
+
68
49
  def type_cast_code(var_name)
69
50
  case type
70
- when :string then nil
71
- when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
72
- when :primary_key then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
73
- when :float then "#{var_name}.to_f"
74
- when :datetime then "JdbcSpec::Oracle::Column.cast_to_date_or_time(#{var_name})"
75
- when :time then "JdbcSpec::Oracle::Column.cast_to_time(#{var_name})"
76
- when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
77
- when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
78
- else nil
51
+ when :datetime then "JdbcSpec::Oracle::Column.string_to_time(#{var_name}, self.class)"
52
+ else
53
+ super
79
54
  end
80
- end
55
+ end
56
+
57
+ def self.string_to_time(string, klass)
58
+ time = klass.string_to_time(string)
59
+ guess_date_or_time(time)
60
+ end
61
+
62
+ def self.guess_date_or_time(value)
63
+ (value.hour == 0 && value.min == 0 && value.sec == 0) ?
64
+ new_date(value.year, value.month, value.day) : value
65
+ end
81
66
 
82
67
  private
83
68
  def simplified_type(field_type)
@@ -93,33 +78,34 @@ module ::JdbcSpec
93
78
  end
94
79
  end
95
80
 
96
- def self.cast_to_date_or_time(value)
97
- return value if value.is_a? Date
98
- return nil if value.blank?
99
- guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value))
100
- end
81
+ # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
82
+ def default_value(value)
83
+ return nil unless value
101
84
 
102
- def self.cast_to_time(value)
103
- return value if value.is_a? Time
104
- time_array = ParseDate.parsedate value
105
- time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
106
- Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
107
- end
85
+ # Not sure why we need this for Oracle?
86
+ value = value.strip
108
87
 
109
- def self.guess_date_or_time(value)
110
- (value.hour == 0 and value.min == 0 and value.sec == 0) ?
111
- Date.new(value.year, value.month, value.day) : value
88
+ return nil if value == "null"
89
+
90
+ # jdbc returns column default strings with actual single quotes around the value.
91
+ return $1 if value =~ /^'(.*)'$/
92
+
93
+ value
112
94
  end
113
95
  end
114
96
 
97
+ def adapter_name
98
+ 'oracle'
99
+ end
100
+
115
101
  def table_alias_length
116
102
  30
117
103
  end
118
104
 
119
- def default_sequence_name(table, column) #:nodoc:
105
+ def default_sequence_name(table, column = nil) #:nodoc:
120
106
  "#{table}_seq"
121
107
  end
122
-
108
+
123
109
  def create_table(name, options = {}) #:nodoc:
124
110
  super(name, options)
125
111
  seq_name = options[:sequence_name] || "#{name}_seq"
@@ -130,7 +116,7 @@ module ::JdbcSpec
130
116
  def rename_table(name, new_name) #:nodoc:
131
117
  execute "RENAME #{name} TO #{new_name}"
132
118
  execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
133
- end
119
+ end
134
120
 
135
121
  def drop_table(name, options = {}) #:nodoc:
136
122
  super(name)
@@ -141,17 +127,22 @@ module ::JdbcSpec
141
127
  def recreate_database(name)
142
128
  tables.each{ |table| drop_table(table) }
143
129
  end
144
-
130
+
131
+ def drop_database(name)
132
+ recreate_database(name)
133
+ end
134
+
145
135
  def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
146
- if pk.nil? # Who called us? What does the sql look like? No idea!
147
- execute sql, name
148
- elsif id_value # Pre-assigned id
136
+ if id_value # Pre-assigned id
149
137
  execute sql, name
150
138
  else # Assume the sql contains a bind-variable for the id
139
+ # Extract the table from the insert sql. Yuck.
140
+ table = sql.split(" ", 4)[2].gsub('"', '')
141
+ sequence_name ||= default_sequence_name(table)
151
142
  id_value = select_one("select #{sequence_name}.nextval id from dual")['id'].to_i
152
- log(sql, name) {
143
+ log(sql, name) do
153
144
  @connection.execute_id_insert(sql,id_value)
154
- }
145
+ end
155
146
  end
156
147
  id_value
157
148
  end
@@ -159,7 +150,7 @@ module ::JdbcSpec
159
150
  def indexes(table, name = nil)
160
151
  @connection.indexes(table, name, @connection.connection.meta_data.user_name)
161
152
  end
162
-
153
+
163
154
  def _execute(sql, name = nil)
164
155
  case sql.strip
165
156
  when /\A\(?\s*(select|show)/i:
@@ -168,7 +159,7 @@ module ::JdbcSpec
168
159
  @connection.execute_update(sql)
169
160
  end
170
161
  end
171
-
162
+
172
163
  def modify_types(tp)
173
164
  tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
174
165
  tp[:integer] = { :name => "NUMBER", :limit => 38 }
@@ -181,7 +172,7 @@ module ::JdbcSpec
181
172
 
182
173
  def add_limit_offset!(sql, options) #:nodoc:
183
174
  offset = options[:offset] || 0
184
-
175
+
185
176
  if limit = options[:limit]
186
177
  sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
187
178
  elsif offset > 0
@@ -204,7 +195,7 @@ module ::JdbcSpec
204
195
  def add_column_options!(sql, options) #:nodoc:
205
196
  # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
206
197
  if options_include_default?(options) && (column = options[:column]) && column.type == :text
207
- sql << " DEFAULT #{quote(options.delete(:default))}"
198
+ sql << " DEFAULT #{quote(options.delete(:default))}"
208
199
  end
209
200
  super
210
201
  end
@@ -214,7 +205,7 @@ module ::JdbcSpec
214
205
  add_column_options!(change_column_sql, options)
215
206
  execute(change_column_sql)
216
207
  end
217
-
208
+
218
209
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
219
210
  execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
220
211
  end
@@ -229,24 +220,24 @@ module ::JdbcSpec
229
220
  end
230
221
 
231
222
  select_all("select table_name from user_tables").inject(s) do |structure, table|
232
- ddl = "create table #{table.to_a.first.last} (\n "
223
+ ddl = "create table #{table.to_a.first.last} (\n "
233
224
  cols = select_all(%Q{
234
225
  select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
235
226
  from user_tab_columns
236
227
  where table_name = '#{table.to_a.first.last}'
237
228
  order by column_id
238
229
  }).map do |row|
239
- row = row.inject({}) do |h,args|
230
+ row = row.inject({}) do |h,args|
240
231
  h[args[0].downcase] = args[1]
241
- h
232
+ h
242
233
  end
243
- col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
234
+ col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
244
235
  if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
245
236
  col << "(#{row['data_precision'].to_i}"
246
237
  col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
247
238
  col << ')'
248
239
  elsif row['data_type'].include?('CHAR')
249
- col << "(#{row['data_length'].to_i})"
240
+ col << "(#{row['data_length'].to_i})"
250
241
  end
251
242
  col << " default #{row['data_default']}" if !row['data_default'].nil?
252
243
  col << ' not null' if row['nullable'] == 'N'
@@ -267,13 +258,13 @@ module ::JdbcSpec
267
258
  drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
268
259
  end
269
260
  end
270
-
261
+
271
262
  # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
272
263
  #
273
264
  # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
274
265
  # queries. However, with those columns included in the SELECT DISTINCT list, you
275
266
  # won't actually get a distinct list of the column you want (presuming the column
276
- # has duplicates with multiple values for the ordered-by columns. So we use the
267
+ # has duplicates with multiple values for the ordered-by columns. So we use the
277
268
  # FIRST_VALUE function to get a single (first) value for each column, effectively
278
269
  # making every row the same.
279
270
  #
@@ -292,7 +283,7 @@ module ::JdbcSpec
292
283
  end
293
284
 
294
285
  # ORDER BY clause for the passed order option.
295
- #
286
+ #
296
287
  # Uses column aliases as defined by #distinct.
297
288
  def add_order_by_for_association_limiting!(sql, options)
298
289
  return sql if options[:order].blank?
@@ -303,25 +294,34 @@ module ::JdbcSpec
303
294
 
304
295
  sql << "ORDER BY #{order}"
305
296
  end
306
-
307
-
297
+
298
+ def tables
299
+ @connection.tables(nil, oracle_schema)
300
+ end
301
+
302
+ def columns(table_name, name=nil)
303
+ @connection.columns_internal(table_name, name, oracle_schema)
304
+ end
305
+
308
306
  # QUOTING ==================================================
309
307
  #
310
308
  # see: abstract/quoting.rb
311
-
312
- # camelCase column names need to be quoted; not that anyone using Oracle
313
- # would really do this, but handling this case means we pass the test...
309
+
310
+ # Camelcase column names need to be quoted.
311
+ # Nonquoted identifiers can contain only alphanumeric characters from your
312
+ # database character set and the underscore (_), dollar sign ($), and pound sign (#).
313
+ # Database links can also contain periods (.) and "at" signs (@).
314
+ # Oracle strongly discourages you from using $ and # in nonquoted identifiers.
315
+ # Source: http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/sql_elements008.htm
314
316
  def quote_column_name(name) #:nodoc:
315
- name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : name.to_s
317
+ name.to_s =~ /^[a-z_$#]+$/ ? name.to_s : "\"#{name}\""
316
318
  end
317
319
 
318
320
  def quote_string(string) #:nodoc:
319
321
  string.gsub(/'/, "''")
320
322
  end
321
-
323
+
322
324
  def quote(value, column = nil) #:nodoc:
323
- return value.quoted_id if value.respond_to?(:quoted_id)
324
-
325
325
  if column && [:text, :binary].include?(column.type)
326
326
  if /(.*?)\([0-9]+\)/ =~ column.sql_type
327
327
  %Q{empty_#{ $1.downcase }()}
@@ -329,35 +329,33 @@ module ::JdbcSpec
329
329
  %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
330
330
  end
331
331
  else
332
- if column && column.type == :primary_key
333
- return value.to_s
334
- end
335
- case value
336
- when String, ActiveSupport::Multibyte::Chars
337
- if column.type == :datetime
338
- %Q{TIMESTAMP'#{value}'}
339
- else
340
- %Q{'#{quote_string(value)}'}
341
- end
342
- when NilClass : 'null'
343
- when TrueClass : '1'
344
- when FalseClass : '0'
345
- when Numeric : value.to_s
346
- when Date, Time : %Q{TIMESTAMP'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
347
- else %Q{'#{quote_string(value.to_yaml)}'}
332
+ quoted = super
333
+ if value.acts_like?(:date) || value.acts_like?(:time)
334
+ quoted = "#{quoted_date(value)}"
348
335
  end
336
+ quoted
349
337
  end
350
338
  end
351
-
339
+
340
+ def quoted_date(value)
341
+ %Q{TIMESTAMP'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
342
+ end
343
+
352
344
  def quoted_true #:nodoc:
353
345
  '1'
354
346
  end
355
-
347
+
356
348
  def quoted_false #:nodoc:
357
349
  '0'
358
350
  end
359
-
351
+
360
352
  private
353
+ # In Oracle, schemas are created under your username:
354
+ # http://www.oracle.com/technology/obe/2day_dba/schema/schema.htm
355
+ def oracle_schema
356
+ @config[:username].to_s
357
+ end
358
+
361
359
  def select(sql, name=nil)
362
360
  records = execute(sql,name)
363
361
  records.each do |col|