ActiveRecord-JDBC 0.0.1 → 0.2.0

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.
data/LICENSE CHANGED
@@ -1,4 +1,5 @@
1
1
  Copyright (c) 2006 Nick Sieger <nick@nicksieger.com>
2
+ Copyright (c) 2006 Ola Bini <ola@ologix.com>
2
3
 
3
4
  Permission is hereby granted, free of charge, to any person obtaining
4
5
  a copy of this software and associated documentation files (the
data/install.rb CHANGED
@@ -1,25 +1,25 @@
1
-
2
- require 'fileutils'
3
-
4
- from_d=File.expand_path(File.join(File.dirname(__FILE__),'lib','active_record'))
5
- to_d=File.expand_path(File.join(RAILS_ROOT,'lib','active_record'))
6
-
7
- FileUtils.cp_r from_d, to_d
8
-
9
- env_file = File.expand_path(File.join(RAILS_ROOT,"config","environment.rb"))
10
- bck_file = File.expand_path(File.join(RAILS_ROOT,"config","~.environment.rb.before_jdbc"))
11
-
12
- FileUtils.mv env_file,bck_file
13
-
14
- File.open(bck_file,"r") {|inf|
15
- File.open(env_file,"w") {|out|
16
- inf.each_line do |ln|
17
- if ln =~ /^Rails::Initializer\.run/
18
- out.puts "# Added by ActiveRecord JDBC plugin"
19
- out.puts "RAILS_CONNECTION_ADAPTERS = %w( jdbc mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase )"
20
- out.puts
21
- end
22
- out.puts ln
23
- end
24
- }
25
- }
1
+
2
+ require 'fileutils'
3
+
4
+ from_d=File.expand_path(File.join(File.dirname(__FILE__),'lib','active_record'))
5
+ to_d=File.expand_path(File.join(RAILS_ROOT,'lib','active_record'))
6
+
7
+ FileUtils.cp_r from_d, to_d
8
+
9
+ env_file = File.expand_path(File.join(RAILS_ROOT,"config","environment.rb"))
10
+ bck_file = File.expand_path(File.join(RAILS_ROOT,"config","~.environment.rb.before_jdbc"))
11
+
12
+ FileUtils.mv env_file,bck_file
13
+
14
+ File.open(bck_file,"r") {|inf|
15
+ File.open(env_file,"w") {|out|
16
+ inf.each_line do |ln|
17
+ if ln =~ /^Rails::Initializer\.run/
18
+ out.puts "# Added by ActiveRecord JDBC plugin"
19
+ out.puts "require 'jdbc_adapter'"
20
+ out.puts
21
+ end
22
+ out.puts ln
23
+ end
24
+ }
25
+ }
@@ -1,10 +1,20 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/jdbc_adapter_spec'
2
3
 
3
4
  module ActiveRecord
4
5
  class Base
5
6
  def self.jdbc_connection(config)
6
7
  ConnectionAdapters::JdbcAdapter.new(ConnectionAdapters::JdbcConnection.new(config), logger, config)
7
8
  end
9
+
10
+ alias :attributes_with_quotes_pre_oracle :attributes_with_quotes
11
+ def attributes_with_quotes(include_primary_key = true) #:nodoc:
12
+ aq = attributes_with_quotes_pre_oracle(include_primary_key)
13
+ if connection.class == ConnectionAdapters::JdbcAdapter && connection.is_a?(JdbcSpec::Oracle)
14
+ aq[self.class.primary_key] = "?" if include_primary_key && aq[self.class.primary_key].nil?
15
+ end
16
+ aq
17
+ end
8
18
  end
9
19
 
10
20
  module ConnectionAdapters
@@ -29,25 +39,37 @@ module ActiveRecord
29
39
  # type left. If all the selectors are applied and there is still more than one
30
40
  # type, an exception will be raised.
31
41
  AR_TO_JDBC_TYPES = {
32
- :string => [ proc {|r| Jdbc::Types::VARCHAR == r['data_type']},
33
- proc {|r| r['type_name'] =~ /^varchar/i} ],
34
- :text => [ proc {|r| [Jdbc::Types::LONGVARCHAR, Jdbc::Types::CLOB].include?(r['data_type'])},
35
- proc {|r| r['type_name'] =~ /^(text|clob)/i} ],
36
- :integer => [ proc {|r| Jdbc::Types::INTEGER == r['data_type']},
37
- proc {|r| r['type_name'] =~ /^integer/i} ],
38
- :float => [ proc {|r| [Jdbc::Types::FLOAT,Jdbc::Types::DOUBLE].include?(r['data_type'])},
39
- proc {|r| r['type_name'] =~ /^float/i},
40
- proc {|r| r['type_name'] =~ /^double$/i} ],
41
- :datetime => [ proc {|r| Jdbc::Types::TIMESTAMP == r['data_type']},
42
- proc {|r| r['type_name'] =~ /^datetime/i} ],
43
- :timestamp => [ proc {|r| Jdbc::Types::TIMESTAMP == r['data_type']},
44
- proc {|r| r['type_name'] =~ /^timestamp/i},
45
- proc {|r| r['type_name'] =~ /^datetime/i} ],
46
- :time => [ proc {|r| Jdbc::Types::TIME == r['data_type']} ],
47
- :date => [ proc {|r| Jdbc::Types::DATE == r['data_type']} ],
48
- :binary => [ proc {|r| Jdbc::Types::LONGVARBINARY == r['data_type']},
49
- proc {|r| r['type_name'] =~ /^blob/i} ],
50
- :boolean => [ proc {|r| Jdbc::Types::TINYINT == r['data_type']} ]
42
+ :string => [ lambda {|r| Jdbc::Types::VARCHAR == r['data_type']},
43
+ lambda {|r| r['type_name'] =~ /^varchar/i},
44
+ lambda {|r| r['type_name'] =~ /^varchar$/i}],
45
+ :text => [ lambda {|r| [Jdbc::Types::LONGVARCHAR, Jdbc::Types::CLOB].include?(r['data_type'])},
46
+ lambda {|r| r['type_name'] =~ /^(text|clob)/i} ],
47
+ :integer => [ lambda {|r| Jdbc::Types::INTEGER == r['data_type']},
48
+ lambda {|r| r['type_name'] =~ /^integer$/i},
49
+ lambda {|r| r['type_name'] =~ /^int4$/i},
50
+ lambda {|r| r['type_name'] =~ /^int$/i}],
51
+ :float => [ lambda {|r| [Jdbc::Types::FLOAT,Jdbc::Types::DOUBLE].include?(r['data_type'])},
52
+ lambda {|r| r['type_name'] =~ /^float/i},
53
+ lambda {|r| r['type_name'] =~ /^double$/i} ],
54
+ :datetime => [ lambda {|r| Jdbc::Types::TIMESTAMP == r['data_type']},
55
+ lambda {|r| r['type_name'] =~ /^datetime/i},
56
+ lambda {|r| r['type_name'] =~ /^timestamp$/i}],
57
+ :timestamp => [ lambda {|r| Jdbc::Types::TIMESTAMP == r['data_type']},
58
+ lambda {|r| r['type_name'] =~ /^timestamp$/i},
59
+ lambda {|r| r['type_name'] =~ /^datetime/i} ],
60
+ :time => [ lambda {|r| Jdbc::Types::TIME == r['data_type']},
61
+ lambda {|r| r['type_name'] =~ /^time$/i},
62
+ lambda {|r| r['type_name'] =~ /^datetime$/i}],
63
+ :date => [ lambda {|r| Jdbc::Types::DATE == r['data_type']},
64
+ lambda {|r| r['type_name'] =~ /^datetime$/i}],
65
+ :binary => [ lambda {|r| [Jdbc::Types::LONGVARBINARY,Jdbc::Types::BINARY,Jdbc::Types::BLOB].include?(r['data_type'])},
66
+ lambda {|r| r['type_name'] =~ /^blob/i},
67
+ lambda {|r| r['type_name'] =~ /sub_type 0$/i}, # For FireBird
68
+ lambda {|r| r['type_name'] =~ /^binary$/i}, ],
69
+ :boolean => [ lambda {|r| [Jdbc::Types::TINYINT].include?(r['data_type'])},
70
+ lambda {|r| r['type_name'] =~ /^bool/i},
71
+ lambda {|r| r['type_name'] =~ /^tinyint$/i},
72
+ lambda {|r| r['type_name'] =~ /^decimal$/i}]
51
73
  }
52
74
 
53
75
  def initialize(types)
@@ -59,7 +81,7 @@ module ActiveRecord
59
81
  AR_TO_JDBC_TYPES.each_key do |k|
60
82
  typerow = choose_type(k)
61
83
  type_map[k] = { :name => typerow['type_name'] }
62
- type_map[k][:limit] = typerow['precision'] if [:integer,:string].include?(k)
84
+ type_map[k][:limit] = typerow['precision'] if [:integer, :string].include?(k)
63
85
  type_map[k][:limit] = 1 if k == :boolean
64
86
  end
65
87
  type_map
@@ -73,7 +95,7 @@ module ActiveRecord
73
95
  return new_types.first if new_types.length == 1
74
96
  types = new_types if new_types.length > 0
75
97
  end
76
- raise "unable to choose type from: #{types.collect{|t| t['type_name']}.inspect}"
98
+ raise "unable to choose type from: #{types.collect{|t| t['type_name']}.inspect} for #{ar_type}"
77
99
  end
78
100
  end
79
101
 
@@ -90,13 +112,36 @@ module ActiveRecord
90
112
  end
91
113
  end
92
114
 
115
+ class JdbcColumn < Column
116
+ def initialize(config, name, default, *args)
117
+ case config[:driver].to_s
118
+ when /oracle/i: self.extend(JdbcSpec::Oracle::Column)
119
+ when /postgre/i: self.extend(JdbcSpec::PostgreSQL::Column)
120
+ when /sqlserver|tds/i: self.extend(JdbcSpec::MsSQL::Column)
121
+ when /hsqldb/i: self.extend(JdbcSpec::HSQLDB::Column)
122
+ when /derby/i: self.extend(JdbcSpec::Derby::Column)
123
+ when /db2/i:
124
+ if config[:url] =~ /^jdbc:derby:net:/
125
+ self.extend(JdbcSpec::Derby::Column)
126
+ else
127
+ self.extend(JdbcSpec::DB2::Column)
128
+ end
129
+ end
130
+ super(name,default_value(default),*args)
131
+ end
132
+
133
+ def default_value(val)
134
+ val
135
+ end
136
+ end
137
+
93
138
  class JdbcConnection
94
139
  def initialize(config)
95
- config = config.symbolize_keys
96
- driver = config[:driver].to_s
97
- user = config[:username].to_s
98
- pass = config[:password].to_s
99
- url = config[:url].to_s
140
+ @config = config.symbolize_keys
141
+ driver = @config[:driver].to_s
142
+ user = @config[:username].to_s
143
+ pass = @config[:password].to_s
144
+ url = @config[:url].to_s
100
145
 
101
146
  unless driver && url
102
147
  raise ArgumentError, "jdbc adapter requires driver class and url"
@@ -105,29 +150,35 @@ module ActiveRecord
105
150
  JdbcDriver.load(driver)
106
151
  @connection = Jdbc::DriverManager.getConnection(url, user, pass)
107
152
  set_native_database_types
153
+
154
+ @stmts = {}
155
+ rescue Exception => e
156
+ raise "The driver encounter an error: #{e}"
108
157
  end
109
158
 
159
+ def ps(sql)
160
+ @connection.prepareStatement(sql)
161
+ end
162
+
110
163
  def set_native_database_types
111
164
  types = unmarshal_result(@connection.getMetaData.getTypeInfo)
112
165
  @native_types = JdbcTypeConverter.new(types).choose_best_types
113
166
  end
114
167
 
115
- def native_database_types
116
- types = {
117
- # TODO: this is copied from MySQL -- figure out how to
118
- # generalize the primary key type
119
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
120
- }
168
+ def native_database_types(adapt)
169
+ types = {}
121
170
  @native_types.each_pair {|k,v| types[k] = v.inject({}) {|memo,kv| memo.merge({kv.first => kv.last.dup})}}
122
- types
171
+ adapt.modify_types(types)
123
172
  end
124
-
173
+
125
174
  def columns(table_name, name = nil)
126
175
  metadata = @connection.getMetaData
176
+ table_name.upcase! if metadata.storesUpperCaseIdentifiers
177
+ table_name.downcase! if metadata.storesLowerCaseIdentifiers
127
178
  results = metadata.getColumns(nil, nil, table_name, nil)
128
179
  columns = []
129
180
  unmarshal_result(results).each do |col|
130
- columns << ActiveRecord::ConnectionAdapters::Column.new(col['column_name'], col['column_def'],
181
+ columns << ActiveRecord::ConnectionAdapters::JdbcColumn.new(@config,col['column_name'].downcase, col['column_def'],
131
182
  "#{col['type_name']}(#{col['column_size']})", col['is_nullable'] != 'NO')
132
183
  end
133
184
  columns
@@ -141,7 +192,7 @@ module ActiveRecord
141
192
 
142
193
  def execute_insert(sql, pk)
143
194
  stmt = @connection.createStatement
144
- stmt.executeUpdate(sql, Jdbc::Statement::RETURN_GENERATED_KEYS)
195
+ stmt.executeUpdate(sql,Jdbc::Statement::RETURN_GENERATED_KEYS)
145
196
  row = unmarshal_result(stmt.getGeneratedKeys)
146
197
  row.first && row.first.values.first
147
198
  ensure
@@ -218,18 +269,22 @@ module ActiveRecord
218
269
  decimal.to_f
219
270
  else
220
271
  case type
221
- when Jdbc::Types::CHAR, Jdbc::Types::VARCHAR, Jdbc::Types::LONGVARCHAR
272
+ when Jdbc::Types::CHAR, Jdbc::Types::VARCHAR, Jdbc::Types::LONGVARCHAR, Jdbc::Types::CLOB
222
273
  resultset.getString(row)
223
274
  when Jdbc::Types::SMALLINT, Jdbc::Types::INTEGER, Jdbc::Types::NUMERIC, Jdbc::Types::BIGINT
224
275
  resultset.getInt(row)
225
- when Jdbc::Types::BIT, Jdbc::Types::BOOLEAN, Jdbc::Types::TINYINT
276
+ when Jdbc::Types::BIT, Jdbc::Types::BOOLEAN, Jdbc::Types::TINYINT, Jdbc::Types::DECIMAL
226
277
  resultset.getBoolean(row)
278
+ when Jdbc::Types::FLOAT, Jdbc::Types::DOUBLE
279
+ resultset.getDouble(row)
227
280
  when Jdbc::Types::TIMESTAMP
228
281
  to_ruby_time(resultset.getTimestamp(row))
229
282
  when Jdbc::Types::TIME
230
283
  to_ruby_time(resultset.getTime(row))
231
284
  when Jdbc::Types::DATE
232
285
  to_ruby_time(resultset.getDate(row))
286
+ when Jdbc::Types::LONGVARBINARY, Jdbc::Types::BLOB, Jdbc::Types::BINARY
287
+ resultset.getString(row)
233
288
  else
234
289
  types = Jdbc::Types.constants
235
290
  name = types.find {|t| Jdbc::Types.const_get(t.to_sym) == type}
@@ -243,8 +298,27 @@ module ActiveRecord
243
298
  def initialize(connection, logger, config)
244
299
  super(connection, logger)
245
300
  @config = config
301
+ case config[:driver].to_s
302
+ when /oracle/i: self.extend(JdbcSpec::Oracle)
303
+ when /postgre/i: self.extend(JdbcSpec::PostgreSQL)
304
+ when /mysql/i: self.extend(JdbcSpec::MySQL)
305
+ when /sqlserver|tds/i: self.extend(JdbcSpec::MsSQL)
306
+ when /hsqldb/i: self.extend(JdbcSpec::HSQLDB)
307
+ when /derby/i: self.extend(JdbcSpec::Derby)
308
+ when /db2/i:
309
+ if config[:url] =~ /^jdbc:derby:net:/
310
+ self.extend(JdbcSpec::Derby)
311
+ else
312
+ self.extend(JdbcSpec::DB2)
313
+ end
314
+ when /firebird/i: self.extend(JdbcSpec::FireBird)
315
+ end
246
316
  end
247
317
 
318
+ def modify_types(tp)
319
+ tp
320
+ end
321
+
248
322
  def adapter_name #:nodoc:
249
323
  'JDBC'
250
324
  end
@@ -254,16 +328,51 @@ module ActiveRecord
254
328
  end
255
329
 
256
330
  def native_database_types #:nodoc
257
- @connection.native_database_types
331
+ @connection.native_database_types(self)
258
332
  end
259
333
 
334
+ def native_sql_to_type(tp)
335
+ if /^(.*?)\(([0-9]+)\)/ =~ tp
336
+ tname = $1
337
+ limit = $2.to_i
338
+ ntype = native_database_types
339
+ if ntype[:primary_key] == tp
340
+ return :primary_key,nil
341
+ else
342
+ ntype.each do |name,val|
343
+ if name == :primary_key
344
+ next
345
+ end
346
+ if val[:name].downcase == tname.downcase && (val[:limit].nil? || val[:limit].to_i == limit)
347
+ return name,limit
348
+ end
349
+ end
350
+ end
351
+ elsif /^(.*?)/ =~ tp
352
+ tname = $1
353
+ ntype = native_database_types
354
+ if ntype[:primary_key] == tp
355
+ return :primary_key,nil
356
+ else
357
+ ntype.each do |name,val|
358
+ if val[:name].downcase == tname.downcase && val[:limit].nil?
359
+ return name,nil
360
+ end
361
+ end
362
+ end
363
+ else
364
+ return :string,255
365
+ end
366
+ return nil,nil
367
+ end
368
+
260
369
  def active?
261
370
  true
262
371
  end
263
372
 
264
373
  def reconnect!
265
374
  @connection.close rescue nil
266
- @connection = JdbcConnection.new(@config)
375
+ @connection = JdbcConnection.new(@config,self)
267
376
  end
268
377
 
269
378
  def select_all(sql, name = nil)
@@ -276,7 +385,7 @@ module ActiveRecord
276
385
 
277
386
  def execute(sql, name = nil)
278
387
  log_no_bench(sql, name) do
279
- if sql =~ /^select/i
388
+ if sql =~ /^(select|show)/i
280
389
  @connection.execute_query(sql)
281
390
  else
282
391
  @connection.execute_update(sql)
@@ -284,21 +393,6 @@ module ActiveRecord
284
393
  end
285
394
  end
286
395
 
287
- # Oracle doesn't support limit and offset. Fake it instead.
288
- def add_limit_offset!(sql, options) #:nodoc:
289
- if /oracle/ =~ @config[:driver]
290
- offset = options[:offset] || 0
291
-
292
- if limit = options[:limit]
293
- sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
294
- elsif offset > 0
295
- sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
296
- end
297
- else
298
- super
299
- end
300
- end
301
-
302
396
  alias :update :execute
303
397
  alias :delete :execute
304
398
 
@@ -330,7 +424,7 @@ module ActiveRecord
330
424
  end
331
425
 
332
426
  private
333
- def select(sql, name)
427
+ def select(sql, name=nil)
334
428
  log_no_bench(sql, name) { @connection.execute_query(sql) }
335
429
  end
336
430