ActiveRecord-JDBC 0.0.1 → 0.2.0

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