activerecord-jdbc-adapter 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.travis.yml +3 -0
  2. data/Gemfile.lock +13 -15
  3. data/History.txt +19 -0
  4. data/README.rdoc +2 -0
  5. data/Rakefile +2 -1
  6. data/lib/arel/visitors/derby.rb +9 -2
  7. data/lib/arel/visitors/sql_server.rb +2 -0
  8. data/lib/arjdbc/db2/adapter.rb +3 -1
  9. data/lib/arjdbc/derby/adapter.rb +10 -3
  10. data/lib/arjdbc/jdbc/adapter.rb +5 -2
  11. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  12. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  13. data/lib/arjdbc/jdbc/connection.rb +5 -1
  14. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +1 -0
  15. data/lib/arjdbc/mssql/adapter.rb +31 -28
  16. data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
  17. data/lib/arjdbc/mysql/adapter.rb +110 -45
  18. data/lib/arjdbc/oracle/adapter.rb +7 -0
  19. data/lib/arjdbc/postgresql/adapter.rb +327 -153
  20. data/lib/arjdbc/sqlite3/adapter.rb +9 -4
  21. data/lib/arjdbc/version.rb +1 -1
  22. data/rakelib/db.rake +17 -5
  23. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +14 -4
  24. data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +25 -0
  25. data/test/db/jdbc.rb +4 -3
  26. data/test/db2_reset_column_information_test.rb +8 -0
  27. data/test/derby_reset_column_information_test.rb +8 -0
  28. data/test/derby_row_locking_test.rb +9 -0
  29. data/test/derby_simple_test.rb +40 -0
  30. data/test/h2_simple_test.rb +2 -2
  31. data/test/helper.rb +15 -2
  32. data/test/jdbc_common.rb +1 -0
  33. data/test/jndi_callbacks_test.rb +5 -9
  34. data/test/manualTestDatabase.rb +31 -31
  35. data/test/models/validates_uniqueness_of_string.rb +1 -1
  36. data/test/mssql_ignore_system_views_test.rb +27 -0
  37. data/test/mssql_null_test.rb +14 -0
  38. data/test/mssql_reset_column_information_test.rb +8 -0
  39. data/test/mssql_row_locking_sql_test.rb +159 -0
  40. data/test/mssql_row_locking_test.rb +9 -0
  41. data/test/mysql_reset_column_information_test.rb +8 -0
  42. data/test/mysql_simple_test.rb +69 -5
  43. data/test/oracle_reset_column_information_test.rb +8 -0
  44. data/test/oracle_specific_test.rb +1 -1
  45. data/test/postgres_nonseq_pkey_test.rb +1 -1
  46. data/test/postgres_reset_column_information_test.rb +8 -0
  47. data/test/postgres_simple_test.rb +72 -1
  48. data/test/row_locking.rb +90 -0
  49. data/test/simple.rb +82 -2
  50. data/test/sqlite3_reset_column_information_test.rb +8 -0
  51. data/test/sqlite3_simple_test.rb +47 -0
  52. data/test/sybase_reset_column_information_test.rb +8 -0
  53. metadata +33 -3
@@ -1,5 +1,8 @@
1
1
  rvm:
2
2
  - jruby
3
+ env:
4
+ - JRUBY_OPTS=
5
+ - JRUBY_OPTS=--1.9
3
6
  branches:
4
7
  only:
5
8
  - master
@@ -1,35 +1,33 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- activemodel (3.1.0)
5
- activesupport (= 3.1.0)
6
- bcrypt-ruby (~> 3.0.0)
4
+ activemodel (3.2.1)
5
+ activesupport (= 3.2.1)
7
6
  builder (~> 3.0.0)
8
- i18n (~> 0.6)
9
- activerecord (3.1.0)
10
- activemodel (= 3.1.0)
11
- activesupport (= 3.1.0)
12
- arel (~> 2.2.1)
7
+ activerecord (3.2.1)
8
+ activemodel (= 3.2.1)
9
+ activesupport (= 3.2.1)
10
+ arel (~> 3.0.0)
13
11
  tzinfo (~> 0.3.29)
14
- activesupport (3.1.0)
12
+ activesupport (3.2.1)
13
+ i18n (~> 0.6)
15
14
  multi_json (~> 1.0)
16
- arel (2.2.1)
17
- bcrypt-ruby (3.0.0-java)
15
+ arel (3.0.0)
18
16
  bouncy-castle-java (1.5.0146.1)
19
17
  builder (3.0.0)
20
18
  columnize (0.3.1)
21
19
  i18n (0.6.0)
22
- jruby-openssl (0.7.4)
23
- bouncy-castle-java
20
+ jruby-openssl (0.7.5)
21
+ bouncy-castle-java (>= 1.5.0146.1)
24
22
  mocha (0.9.8)
25
23
  rake
26
- multi_json (1.0.3)
24
+ multi_json (1.0.4)
27
25
  rake (0.9.2.2)
28
26
  ruby-debug (0.10.4)
29
27
  columnize (>= 0.1)
30
28
  ruby-debug-base (~> 0.10.4.0)
31
29
  ruby-debug-base (0.10.4-java)
32
- tzinfo (0.3.29)
30
+ tzinfo (0.3.31)
33
31
 
34
32
  PLATFORMS
35
33
  java
@@ -1,3 +1,22 @@
1
+ == 1.2.2 (01/27/12)
2
+
3
+ - Thanks George Murphy and Dwayne Litzenberger for their significant
4
+ work this release!
5
+ - AR 3.2.x compatibility via #156 (thanks George Murphy)
6
+ - #152: Bunch of derby and mssql fixes (thanks Dwayne Litzenberger)
7
+ - #137: Fix configure_arel2_visitors for vanilla JDBC adapters
8
+ - #136: query cache fix
9
+ - #138: error message improvement for #table_structure (threez)
10
+ - #130, #139: sqlite3 should log inserts (Uwe Kubosch)
11
+ - #141 column queries logging (George Murphy)
12
+ - #142 MySQL fixes for AR 3-1-stable tests (George Murphy)
13
+ - #147, #149 Improve speed of PG metadata queries (George Murphy)
14
+ - #148 PostgreSQL fixes for AR 3-1-stable tests (George Murphy)
15
+ - #128, #129 Fix for invalid :limit on date columns in schema.rb (Lenny Marks)
16
+ - #144 Stop using ParseDate (not 1.9 friendly) (Bill Koch)
17
+ - #146 Upgrade PG drivers (David Kellum)
18
+ - #150 avoid 'TypeError: can't dup Fixnum' for performance (Bruce Adams)
19
+
1
20
  == 1.2.1 (11/23/11)
2
21
 
3
22
  - #117: Skip ? substitution when no bind parameters are given
@@ -182,6 +182,8 @@ the ActiveRecord sources.
182
182
 
183
183
  jruby -S rake rails:test DRIVER=mysql RAILS=/path/activerecord_source_dir
184
184
 
185
+ == Travis Build Status {<img src="https://secure.travis-ci.org/jruby/activerecord-jdbc-adapter.png"/>}[http://travis-ci.org/#!/jruby/activerecord-jdbc-adapter]
186
+
185
187
  == Authors
186
188
 
187
189
  This project was written by Nick Sieger <nick@nicksieger.com> and Ola Bini
data/Rakefile CHANGED
@@ -6,6 +6,8 @@ require 'bundler'
6
6
  Bundler::GemHelper.install_tasks
7
7
  require 'bundler/setup'
8
8
 
9
+ require File.expand_path('../test/helper', __FILE__)
10
+
9
11
  task :default => [:jar, :test]
10
12
 
11
13
  #ugh, bundler doesn't use tasks, so gotta hook up to both tasks.
@@ -56,4 +58,3 @@ task "all:build" => ["build", *ADAPTERS.map { |f| "#{f}:build" }]
56
58
  task :filelist do
57
59
  puts FileList['pkg/**/*'].inspect
58
60
  end
59
-
@@ -5,10 +5,10 @@ module Arel
5
5
  class Derby < Arel::Visitors::ToSql
6
6
  def visit_Arel_Nodes_SelectStatement o
7
7
  [
8
- o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
8
+ o.cores.map { |x| visit(x) }.join,
9
9
  ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
10
- (visit_Arel_Nodes_Limit(o.limit) if o.limit),
11
10
  (visit(o.offset) if o.offset),
11
+ (visit(o.limit) if o.limit),
12
12
  (visit(o.lock) if o.lock),
13
13
  ].compact.join ' '
14
14
  end
@@ -20,6 +20,13 @@ module Arel
20
20
  def visit_Arel_Nodes_Offset o
21
21
  "OFFSET #{visit o.value} ROWS"
22
22
  end
23
+
24
+ # This generates SELECT...FOR UPDATE, but it will only work if the
25
+ # current transaction isolation level is set to SERIALIZABLE. Otherwise,
26
+ # locks aren't held for the entire transaction.
27
+ def visit_Arel_Nodes_Lock o
28
+ visit o.expr
29
+ end
23
30
  end
24
31
  end
25
32
  end
@@ -4,6 +4,7 @@ module Arel
4
4
  module Visitors
5
5
  class SQLServer < Arel::Visitors::ToSql
6
6
  include ArJdbc::MsSQL::LimitHelpers::SqlServerReplaceLimitOffset
7
+ include ArJdbc::MsSQL::LockHelpers::SqlServerAddLock
7
8
 
8
9
  def select_count? o
9
10
  sel = o.cores.length == 1 && o.cores.first
@@ -33,6 +34,7 @@ module Arel
33
34
  else
34
35
  sql = super
35
36
  end
37
+ add_lock!(sql, :lock => o.lock && true)
36
38
  sql
37
39
  end
38
40
  end
@@ -69,7 +69,9 @@ module ArJdbc
69
69
  def self.cast_to_time(value)
70
70
  return value if value.is_a? Time
71
71
  # AS400 returns a 2 digit year, LUW returns a 4 digit year, so comp = true to help out AS400
72
- time_array = ParseDate.parsedate(value, true)
72
+ time = DateTime.parse(value).to_time rescue nil
73
+ return nil unless time
74
+ time_array = [time.year, time.month, time.day, time.hour, time.min, time.sec]
73
75
  time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
74
76
  Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
75
77
  end
@@ -27,19 +27,25 @@ module ::ArJdbc
27
27
  end
28
28
  end
29
29
 
30
- def self.extended(*args)
30
+ def self.extended(adapter)
31
31
  monkey_rails
32
+ adapter.configure_connection
32
33
  end
33
34
 
34
35
  def self.included(*args)
35
36
  monkey_rails
36
37
  end
37
38
 
39
+ def configure_connection
40
+ execute("SET ISOLATION = SERIALIZABLE") # This must be done or SELECT...FOR UPDATE won't work how we expect
41
+ end
42
+
38
43
  module Column
39
44
  def simplified_type(field_type)
40
45
  case field_type
41
46
  when /smallint/i then :boolean
42
47
  when /real/i then :float
48
+ when /^timestamp/i then :datetime
43
49
  else
44
50
  super
45
51
  end
@@ -73,10 +79,10 @@ module ::ArJdbc
73
79
  # In Derby, the following cannot specify a limit:
74
80
  # - integer
75
81
  # - boolean (smallint)
76
- # - timestamp
82
+ # - datetime (timestamp)
77
83
  # - date
78
84
  def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
79
- return super unless [:integer, :boolean, :timestamp, :date].include? type
85
+ return super unless [:integer, :boolean, :timestamp, :datetime, :date].include? type
80
86
 
81
87
  native = native_database_types[type.to_s.downcase.to_sym]
82
88
  native.is_a?(Hash) ? native[:name] : native
@@ -87,6 +93,7 @@ module ::ArJdbc
87
93
  tp[:string][:limit] = 256
88
94
  tp[:integer][:limit] = nil
89
95
  tp[:boolean] = {:name => "smallint"}
96
+ tp[:datetime] = {:name => "timestamp", :limit => nil}
90
97
  tp[:timestamp][:limit] = nil
91
98
  tp[:date][:limit] = nil
92
99
  tp
@@ -12,6 +12,7 @@ require 'arjdbc/jdbc/column'
12
12
  require 'arjdbc/jdbc/connection'
13
13
  require 'arjdbc/jdbc/callbacks'
14
14
  require 'arjdbc/jdbc/extension'
15
+ require 'arjdbc/jdbc/base_ext'
15
16
  require 'bigdecimal'
16
17
 
17
18
  module ActiveRecord
@@ -111,7 +112,7 @@ module ActiveRecord
111
112
  if defined?(::Arel::Visitors::VISITORS)
112
113
  visitors = ::Arel::Visitors::VISITORS
113
114
  visitor = nil
114
- adapter_spec = config[:adapter_spec] || self
115
+ adapter_spec = config[:adapter_spec] || self.class
115
116
  adapter_spec.arel2_visitors(config).each do |k,v|
116
117
  visitor = v
117
118
  visitors[k] = v
@@ -119,6 +120,7 @@ module ActiveRecord
119
120
  if visitor && config[:adapter] =~ /^(jdbc|jndi)$/
120
121
  visitors[config[:adapter]] = visitor
121
122
  end
123
+ @visitor = visitors[config[:adapter]].new(self)
122
124
  end
123
125
  end
124
126
 
@@ -196,7 +198,8 @@ module ActiveRecord
196
198
  if binds.empty?
197
199
  sql
198
200
  else
199
- sql.gsub('?') { quote(*binds.shift.reverse) }
201
+ copy = binds.dup
202
+ sql.gsub('?') { quote(*copy.shift.reverse) }
200
203
  end
201
204
  end
202
205
 
@@ -0,0 +1,15 @@
1
+ module ActiveRecord
2
+ class Base # reopen
3
+ class <<self
4
+ # Allow adapters to provide their own reset_column_information methods
5
+ #
6
+ # NOTE: This only affects the current thread's connection.
7
+ def reset_column_information_with_arjdbc_base_ext
8
+ # Invoke the adapter-specific reset_column_information method
9
+ connection.reset_column_information if connection.respond_to?(:reset_column_information)
10
+ reset_column_information_without_arjdbc_base_ext
11
+ end
12
+ alias_method_chain :reset_column_information, :arjdbc_base_ext unless instance_methods.include?("reset_column_information_without_arjdbc_base_ext")
13
+ end
14
+ end
15
+ end
@@ -107,7 +107,11 @@ module ActiveRecord
107
107
  types = {}
108
108
  @native_types.each_pair do |k, v|
109
109
  types[k] = v.inject({}) do |memo, kv|
110
- memo[kv.first] = begin kv.last.dup rescue kv.last end
110
+ memo[kv.first] = if kv.last.is_a? Numeric
111
+ kv.last
112
+ else
113
+ begin kv.last.dup rescue kv.last end
114
+ end
111
115
  memo
112
116
  end
113
117
  end
@@ -33,6 +33,7 @@ module ArJdbc
33
33
 
34
34
  @definition.column(column_name, column.type,
35
35
  :limit => column.limit, :default => column.default,
36
+ :precision => column.precision, :scale => column.scale,
36
37
  :null => column.null)
37
38
  end
38
39
  @definition.primary_key(primary_key(from)) if primary_key(from)
@@ -1,5 +1,7 @@
1
1
  require 'arjdbc/mssql/tsql_helper'
2
2
  require 'arjdbc/mssql/limit_helpers'
3
+ require 'arjdbc/mssql/lock_helpers'
4
+ require 'strscan'
3
5
 
4
6
  module ::ArJdbc
5
7
  module MsSQL
@@ -12,7 +14,13 @@ module ::ArJdbc
12
14
  def after_save_with_mssql_lob
13
15
  self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
14
16
  value = self[c.name]
15
- value = value.to_yaml if unserializable_attribute?(c.name, c)
17
+ if coder = self.class.serialized_attributes[c.name]
18
+ if coder.respond_to?(:dump)
19
+ value = coder.dump(value)
20
+ else
21
+ value = value.to_yaml
22
+ end
23
+ end
16
24
  next if value.nil? || (value == '')
17
25
 
18
26
  connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
@@ -84,22 +92,26 @@ module ::ArJdbc
84
92
  end
85
93
 
86
94
  module Column
95
+ include LockHelpers::SqlServerAddLock
96
+
87
97
  attr_accessor :identity, :is_special
88
98
 
89
99
  def simplified_type(field_type)
90
100
  case field_type
91
- when /int|bigint|smallint|tinyint/i then :integer
92
- when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
93
- when /float|double|decimal|money|real|smallmoney/i then :decimal
94
- when /datetime|smalldatetime/i then :datetime
95
- when /timestamp/i then :timestamp
96
- when /time/i then :time
97
- when /date/i then :date
98
- when /text|ntext|xml/i then :text
99
- when /binary|image|varbinary/i then :binary
100
- when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
101
- when /bit/i then :boolean
102
- when /uniqueidentifier/i then :string
101
+ when /int|bigint|smallint|tinyint/i then :integer
102
+ when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
103
+ when /float|double|money|real|smallmoney/i then :decimal
104
+ when /datetime|smalldatetime/i then :datetime
105
+ when /timestamp/i then :timestamp
106
+ when /time/i then :time
107
+ when /date/i then :date
108
+ when /text|ntext|xml/i then :text
109
+ when /binary|image|varbinary/i then :binary
110
+ when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
111
+ when /bit/i then :boolean
112
+ when /uniqueidentifier/i then :string
113
+ else
114
+ super
103
115
  end
104
116
  end
105
117
 
@@ -109,7 +121,7 @@ module ::ArJdbc
109
121
  end
110
122
 
111
123
  def type_cast(value)
112
- return nil if value.nil? || value == "(null)" || value == "(NULL)"
124
+ return nil if value.nil?
113
125
  case type
114
126
  when :integer then value.delete('()').to_i rescue unquote(value).to_i rescue value ? 1 : 0
115
127
  when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
@@ -143,15 +155,7 @@ module ::ArJdbc
143
155
 
144
156
  def cast_to_time(value)
145
157
  return value if value.is_a?(Time)
146
- time_array = ParseDate.parsedate(value)
147
- return nil if !time_array.any?
148
- time_array[0] ||= 2000
149
- time_array[1] ||= 1
150
- time_array[2] ||= 1
151
- return Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
152
-
153
- # Try DateTime instead - the date may be outside the time period support by Time.
154
- DateTime.new(*time_array[0..5]) rescue nil
158
+ DateTime.parse(value).to_time rescue nil
155
159
  end
156
160
 
157
161
  def cast_to_date(value)
@@ -389,11 +393,6 @@ module ::ArJdbc
389
393
  end
390
394
  end
391
395
 
392
- #SELECT .. FOR UPDATE is not supported on Microsoft SQL Server
393
- def add_lock!(sql, options)
394
- sql
395
- end
396
-
397
396
  # Turns IDENTITY_INSERT ON for table during execution of the block
398
397
  # N.B. This sets the state of IDENTITY_INSERT to OFF after the
399
398
  # block has been executed without regard to its previous state
@@ -469,6 +468,10 @@ module ::ArJdbc
469
468
  def clear_cached_table(name)
470
469
  (@table_columns ||= {}).delete(name.to_s)
471
470
  end
471
+
472
+ def reset_column_information
473
+ @table_columns = nil
474
+ end
472
475
  end
473
476
  end
474
477
 
@@ -0,0 +1,72 @@
1
+ module ::ArJdbc
2
+ module MsSQL
3
+ module LockHelpers
4
+ module SqlServerAddLock
5
+ # Microsoft SQL Server uses its own syntax for SELECT .. FOR UPDATE:
6
+ # SELECT .. FROM table1 WITH(ROWLOCK,UPDLOCK), table2 WITH(ROWLOCK,UPDLOCK) WHERE ..
7
+ #
8
+ # This does in-place modification of the passed-in string.
9
+ def add_lock!(sql, options)
10
+ if options[:lock] and sql =~ /\A\s*SELECT/mi
11
+ # Check for and extract the :limit/:offset sub-query
12
+ if sql =~ /\A(\s*SELECT t\.\* FROM \()(.*)(\) AS t WHERE t._row_num BETWEEN \d+ AND \d+\s*)\Z/m
13
+ prefix, subselect, suffix = [$1, $2, $3]
14
+ add_lock!(subselect, options)
15
+ return sql.replace(prefix + subselect + suffix)
16
+ end
17
+ unless sql =~ /\A(\s*SELECT\s.*?)(\sFROM\s)(.*?)(\sWHERE\s.*|)\Z/mi
18
+ # If you get this error, this driver probably needs to be fixed.
19
+ raise NotImplementedError, "Don't know how to add_lock! to SQL statement: #{sql.inspect}"
20
+ end
21
+ select_clause, from_word, from_tables, where_clause = [$1, $2, $3, $4]
22
+ with_clause = options[:lock].is_a?(String) ? " #{options[:lock]} " : " WITH(ROWLOCK,UPDLOCK) "
23
+
24
+ # Split the FROM clause into its constituent tables, and add the with clause after each one.
25
+ new_from_tables = []
26
+ s = StringScanner.new(from_tables)
27
+ until s.eos?
28
+ prev_pos = s.pos
29
+ if s.scan_until(/,|(INNER\s+JOIN|CROSS\s+JOIN|(LEFT|RIGHT|FULL)(\s+OUTER)?\s+JOIN)\s+/mi)
30
+ join_operand = s.pre_match[prev_pos..-1]
31
+ join_operator = s.matched
32
+ else
33
+ join_operand = s.rest
34
+ join_operator = ""
35
+ s.terminate
36
+ end
37
+
38
+ # At this point, we have something like:
39
+ # join_operand == "appointments "
40
+ # join_operator == "INNER JOIN "
41
+ # or:
42
+ # join_operand == "appointment_details AS d1 ON appointments.[id] = d1.[appointment_id]"
43
+ # join_operator == ""
44
+ if join_operand =~ /\A(.*)(\s+ON\s+.*)\Z/mi
45
+ table_spec, on_clause = [$1, $2]
46
+ else
47
+ table_spec = join_operand
48
+ on_clause = ""
49
+ end
50
+
51
+ # Add the "WITH(ROWLOCK,UPDLOCK)" option to the table specification
52
+ table_spec << with_clause unless table_spec =~ /\A\(\s*SELECT\s+/mi # HACK - this parser isn't so great
53
+ join_operand = table_spec + on_clause
54
+
55
+ # So now we have something like:
56
+ # join_operand == "appointments WITH(ROWLOCK,UPDLOCK) "
57
+ # join_operator == "INNER JOIN "
58
+ # or:
59
+ # join_operand == "appointment_details AS d1 WITH(ROWLOCK,UPDLOCK) ON appointments.[id] = d1.[appointment_id]"
60
+ # join_operator == ""
61
+
62
+ new_from_tables << join_operand
63
+ new_from_tables << join_operator
64
+ end
65
+ sql.replace([select_clause, from_word, new_from_tables, where_clause].flatten.join)
66
+ end
67
+ sql
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end