activerecord-jdbc-adapter 1.2.1 → 1.2.2

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 (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