activerecord-jdbc-adapter 0.9.0.1 → 0.9.1

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