activerecord-sqlserver-adapter 4.2.6 → 4.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/Gemfile +9 -0
  4. data/README.md +40 -26
  5. data/VERSION +1 -0
  6. data/activerecord-sqlserver-adapter.gemspec +0 -10
  7. data/appveyor.yml +15 -3
  8. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +3 -3
  9. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +35 -11
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -16
  11. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +12 -2
  12. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +8 -0
  13. data/lib/active_record/connection_adapters/sqlserver/type.rb +3 -1
  14. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +9 -0
  15. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +18 -12
  16. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
  17. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +31 -0
  18. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +6 -6
  19. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +13 -29
  20. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +72 -0
  21. data/lib/active_record/connection_adapters/sqlserver/utils.rb +12 -12
  22. data/lib/active_record/connection_adapters/sqlserver/version.rb +1 -1
  23. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +41 -5
  24. data/lib/active_record/sqlserver_base.rb +2 -4
  25. data/lib/arel/visitors/sqlserver.rb +19 -0
  26. data/test/cases/adapter_test_sqlserver.rb +24 -0
  27. data/test/cases/coerced_tests.rb +14 -0
  28. data/test/cases/column_test_sqlserver.rb +120 -47
  29. data/test/cases/connection_test_sqlserver.rb +3 -3
  30. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
  31. data/test/cases/helper_sqlserver.rb +24 -16
  32. data/test/cases/migration_test_sqlserver.rb +0 -5
  33. data/test/cases/rake_test_sqlserver.rb +29 -10
  34. data/test/cases/schema_dumper_test_sqlserver.rb +28 -11
  35. data/test/cases/transaction_test_sqlserver.rb +2 -2
  36. data/test/cases/utils_test_sqlserver.rb +44 -14
  37. data/test/config.yml +2 -0
  38. data/test/debug.rb +14 -0
  39. data/test/schema/datatypes/2012.sql +8 -18
  40. data/test/schema/sqlserver_specific_schema.rb +14 -12
  41. data/test/support/connection_reflection.rb +37 -0
  42. metadata +13 -143
  43. data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +0 -32
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module Type
5
+ class DateTime2 < DateTime
6
+
7
+ include TimeValueFractional2
8
+
9
+ def type
10
+ :datetime2
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module Type
5
+ class DateTimeOffset < DateTime2
6
+
7
+ def type
8
+ :datetimeoffset
9
+ end
10
+
11
+ def type_cast_for_database(value)
12
+ return super unless value.acts_like?(:time)
13
+ value.to_s :_sqlserver_datetimeoffset
14
+ end
15
+
16
+ def type_cast_for_schema(value)
17
+ type_cast_for_database(value).inspect
18
+ end
19
+
20
+
21
+ private
22
+
23
+ def zone_conversion(value)
24
+ value
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,15 +4,15 @@ module ActiveRecord
4
4
  module Type
5
5
  class SmallDateTime < DateTime
6
6
 
7
+ def type
8
+ :smalldatetime
9
+ end
7
10
 
8
- private
9
11
 
10
- def cast_usec(value)
11
- 0
12
- end
12
+ private
13
13
 
14
- def cast_usec_for_database(value)
15
- '.000'
14
+ def cast_fractional(value)
15
+ value.change usec: 0
16
16
  end
17
17
 
18
18
  end
@@ -1,52 +1,36 @@
1
- Time::DATE_FORMATS[:_sqlserver_time] = '%H:%M:%S'
2
-
3
1
  module ActiveRecord
4
2
  module ConnectionAdapters
5
3
  module SQLServer
6
4
  module Type
7
5
  class Time < ActiveRecord::Type::Time
8
6
 
9
- def initialize(options = {})
10
- super
11
- @precision = nil if @precision == 7
12
- end
7
+ include TimeValueFractional2
13
8
 
14
9
  def type_cast_for_database(value)
15
- return if value.nil?
16
- Quoter.new super, self
10
+ return super unless value.acts_like?(:time)
11
+ time = value.to_s(:_sqlserver_time)
12
+ "#{time}".tap do |v|
13
+ fraction = quote_fractional(value)
14
+ v << ".#{fraction}" unless fraction.to_i.zero?
15
+ end
17
16
  end
18
17
 
19
18
  def type_cast_for_schema(value)
20
- value.acts_like?(:string) ? "'#{value}'" : super
21
- end
22
-
23
- def quote_ss(value)
24
- return unless value
25
- value = cast_value(value) if value.acts_like?(:string)
26
- date = value.to_s(:_sqlserver_time)
27
- frac = quote_usec(value)
28
- "'#{date}.#{frac}'"
19
+ type_cast_for_database(value).inspect
29
20
  end
30
21
 
31
22
 
32
23
  private
33
24
 
34
25
  def cast_value(value)
35
- value = value.respond_to?(:usec) ? value.change(year: 2000, month: 01, day: 01) : super
26
+ value = value.acts_like?(:time) ? value : super
36
27
  return if value.blank?
37
- value.change usec: cast_usec(value)
38
- end
39
-
40
- def cast_usec(value)
41
- (usec_to_seconds_frction(value) * 1_000_000).to_i
42
- end
43
-
44
- def usec_to_seconds_frction(value)
45
- (value.usec.to_f / 1_000_000.0).round(precision || 7)
28
+ value = value.change year: 2000, month: 01, day: 01
29
+ cast_fractional(value)
46
30
  end
47
31
 
48
- def quote_usec(value)
49
- usec_to_seconds_frction(value).to_s.split('.').last
32
+ def fractional_scale
33
+ precision
50
34
  end
51
35
 
52
36
  end
@@ -0,0 +1,72 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module Type
5
+
6
+ module TimeValueFractional
7
+
8
+ private
9
+
10
+ def cast_fractional(value)
11
+ return value if !value.respond_to?(fractional_property) || value.send(fractional_property).zero?
12
+ seconds = value.send(fractional_property).to_f / fractional_operator.to_f
13
+ seconds = ((seconds * (1 / fractional_precision)).round / (1 / fractional_precision)).round(fractional_scale)
14
+ frac_seconds = (seconds * fractional_operator).to_i
15
+ value.change fractional_property => frac_seconds
16
+ end
17
+
18
+ def quote_fractional(value)
19
+ seconds = (value.send(fractional_property).to_f / fractional_operator.to_f).round(fractional_scale)
20
+ seconds.to_s.split('.').last.to(fractional_scale-1)
21
+ end
22
+
23
+ def fractional_property
24
+ :usec
25
+ end
26
+
27
+ def fractional_digits
28
+ 6
29
+ end
30
+
31
+ def fractional_operator
32
+ 10 ** fractional_digits
33
+ end
34
+
35
+ def fractional_precision
36
+ 0.00333
37
+ end
38
+
39
+ def fractional_scale
40
+ 3
41
+ end
42
+
43
+ end
44
+
45
+ module TimeValueFractional2
46
+
47
+ include TimeValueFractional
48
+
49
+ private
50
+
51
+ def fractional_property
52
+ :nsec
53
+ end
54
+
55
+ def fractional_digits
56
+ 9
57
+ end
58
+
59
+ def fractional_precision
60
+ 0.0000001
61
+ end
62
+
63
+ def fractional_scale
64
+ precision
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+ end
@@ -43,6 +43,10 @@ module ActiveRecord
43
43
  [server_quoted, database_quoted].compact.join(SEPARATOR)
44
44
  end
45
45
 
46
+ def fully_qualified?
47
+ parts.compact.size == 4
48
+ end
49
+
46
50
  def to_s
47
51
  quoted
48
52
  end
@@ -51,6 +55,10 @@ module ActiveRecord
51
55
  parts.map{ |p| quote(p) if p }.join SEPARATOR
52
56
  end
53
57
 
58
+ def quoted_raw
59
+ quote @raw_name
60
+ end
61
+
54
62
  def ==(o)
55
63
  o.class == self.class && o.parts == parts
56
64
  end
@@ -110,6 +118,10 @@ module ActiveRecord
110
118
  s.to_s.gsub /\'/, "''"
111
119
  end
112
120
 
121
+ def quoted_raw(name)
122
+ SQLServer::Utils::Name.new(name).quoted_raw
123
+ end
124
+
113
125
  def unquote_string(s)
114
126
  s.to_s.gsub(/\'\'/, "'")
115
127
  end
@@ -118,18 +130,6 @@ module ActiveRecord
118
130
  SQLServer::Utils::Name.new(name)
119
131
  end
120
132
 
121
- def with_sqlserver_db_date_formats
122
- old_db_format_date = Date::DATE_FORMATS[:db]
123
- old_db_format_time = Time::DATE_FORMATS[:db]
124
- date_format = Date::DATE_FORMATS[:_sqlserver_dateformat]
125
- Date::DATE_FORMATS[:db] = "#{date_format}"
126
- Time::DATE_FORMATS[:db] = "#{date_format} %H:%M:%S"
127
- yield
128
- ensure
129
- Date::DATE_FORMATS[:db] = old_db_format_date
130
- Time::DATE_FORMATS[:db] = old_db_format_time
131
- end
132
-
133
133
  end
134
134
  end
135
135
  end
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  module SQLServer
4
4
  module Version
5
5
 
6
- VERSION = '4.2.6'
6
+ VERSION = File.read(File.expand_path("../../../../../VERSION", __FILE__)).chomp
7
7
 
8
8
  end
9
9
  end
@@ -54,7 +54,6 @@ module ActiveRecord
54
54
  @visitor = Arel::Visitors::SQLServer.new self
55
55
  @prepared_statements = true
56
56
  # Our Responsibility
57
- @config = config
58
57
  @connection_options = config
59
58
  connect
60
59
  @sqlserver_azure = !!(select_value('SELECT @@version', 'SCHEMA') =~ /Azure/i)
@@ -121,10 +120,11 @@ module ActiveRecord
121
120
  end
122
121
 
123
122
  def disable_referential_integrity
124
- do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
123
+ tables = tables_with_referential_integrity
124
+ tables.each { |t| do_execute "ALTER TABLE #{t} NOCHECK CONSTRAINT ALL" }
125
125
  yield
126
126
  ensure
127
- do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
127
+ tables.each { |t| do_execute "ALTER TABLE #{t} CHECK CONSTRAINT ALL" }
128
128
  end
129
129
 
130
130
  # === Abstract Adapter (Connection Management) ================== #
@@ -162,6 +162,19 @@ module ActiveRecord
162
162
 
163
163
  # === Abstract Adapter (Misc Support) =========================== #
164
164
 
165
+ def tables_with_referential_integrity
166
+ schemas_and_tables = select_rows <<-SQL.strip_heredoc
167
+ SELECT s.name, o.name
168
+ FROM sys.foreign_keys i
169
+ INNER JOIN sys.objects o ON i.parent_object_id = o.OBJECT_ID
170
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
171
+ SQL
172
+ schemas_and_tables.map do |schema_table|
173
+ schema, table = schema_table
174
+ "#{SQLServer::Utils.quoted_raw(schema)}.#{SQLServer::Utils.quoted_raw(table)}"
175
+ end
176
+ end
177
+
165
178
  def pk_and_sequence_for(table_name)
166
179
  pk = primary_key(table_name)
167
180
  pk ? [pk, nil] : nil
@@ -181,6 +194,16 @@ module ActiveRecord
181
194
  @sqlserver_azure
182
195
  end
183
196
 
197
+ def database_prefix_remote_server?
198
+ return false if database_prefix.blank?
199
+ name = SQLServer::Utils.extract_identifiers(database_prefix)
200
+ name.fully_qualified? && name.object.blank?
201
+ end
202
+
203
+ def database_prefix
204
+ @connection_options[:database_prefix]
205
+ end
206
+
184
207
  def version
185
208
  self.class::VERSION
186
209
  end
@@ -221,6 +244,14 @@ module ActiveRecord
221
244
  # Date and Time
222
245
  m.register_type 'date', SQLServer::Type::Date.new
223
246
  m.register_type 'datetime', SQLServer::Type::DateTime.new
247
+ m.register_type %r{\Adatetime2}i do |sql_type|
248
+ precision = extract_precision(sql_type)
249
+ SQLServer::Type::DateTime2.new precision: precision
250
+ end
251
+ m.register_type %r{\Adatetimeoffset}i do |sql_type|
252
+ precision = extract_precision(sql_type)
253
+ SQLServer::Type::DateTimeOffset.new precision: precision
254
+ end
224
255
  m.register_type 'smalldatetime', SQLServer::Type::SmallDateTime.new
225
256
  m.register_type %r{\Atime}i do |sql_type|
226
257
  scale = extract_scale(sql_type)
@@ -353,8 +384,13 @@ module ActiveRecord
353
384
  a, b, c = @database_dateformat.each_char.to_a
354
385
  [a, b, c].each { |f| f.upcase! if f == 'y' }
355
386
  dateformat = "%#{a}-%#{b}-%#{c}"
356
- ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
357
- ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
387
+ ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
388
+ ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
389
+ ::Time::DATE_FORMATS[:_sqlserver_time] = '%H:%M:%S'
390
+ ::Time::DATE_FORMATS[:_sqlserver_datetime] = "#{dateformat} %H:%M:%S"
391
+ ::Time::DATE_FORMATS[:_sqlserver_datetimeoffset] = lambda { |time|
392
+ time.strftime "#{dateformat} %H:%M:%S.%9N #{time.formatted_offset}"
393
+ }
358
394
  end
359
395
 
360
396
  end
@@ -1,7 +1,6 @@
1
1
  module ActiveRecord
2
- class Base
3
-
4
- def self.sqlserver_connection(config) #:nodoc:
2
+ module ConnectionHandling
3
+ def sqlserver_connection(config) #:nodoc:
5
4
  config = config.symbolize_keys
6
5
  config.reverse_merge! mode: :dblib
7
6
  mode = config[:mode].to_s.downcase.underscore.to_sym
@@ -17,6 +16,5 @@ module ActiveRecord
17
16
  end
18
17
  ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(mode: mode))
19
18
  end
20
-
21
19
  end
22
20
  end
@@ -73,6 +73,19 @@ module Arel
73
73
  @select_statement = nil
74
74
  end
75
75
 
76
+ def visit_Arel_Table o, collector
77
+ table_name = if o.engine.connection.database_prefix_remote_server?
78
+ remote_server_table_name(o)
79
+ else
80
+ quote_table_name(o.name)
81
+ end
82
+ if o.table_alias
83
+ collector << "#{table_name} #{quote_table_name o.table_alias}"
84
+ else
85
+ collector << table_name
86
+ end
87
+ end
88
+
76
89
  def visit_Arel_Nodes_JoinSource o, collector
77
90
  if o.left
78
91
  collector = visit o.left, collector
@@ -185,6 +198,12 @@ module Arel
185
198
  column_name ? t[column_name] : nil
186
199
  end
187
200
 
201
+ def remote_server_table_name o
202
+ ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
203
+ "#{o.engine.connection.database_prefix}#{o.name}"
204
+ ).quoted
205
+ end
206
+
188
207
  end
189
208
  end
190
209
  end
@@ -240,6 +240,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
240
240
  describe 'database statements' do
241
241
 
242
242
  it "run the database consistency checker useroptions command" do
243
+ skip 'on azure' if connection_sqlserver_azure?
243
244
  keys = [:textsize, :language, :isolation_level, :dateformat]
244
245
  user_options = connection.user_options
245
246
  keys.each do |key|
@@ -249,6 +250,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
249
250
  end
250
251
 
251
252
  it "return a underscored key hash with indifferent access of the results" do
253
+ skip 'on azure' if connection_sqlserver_azure?
252
254
  user_options = connection.user_options
253
255
  assert_equal 'read committed', user_options['isolation_level']
254
256
  assert_equal 'read committed', user_options[:isolation_level]
@@ -416,5 +418,27 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
416
418
 
417
419
  end
418
420
 
421
+ describe 'database_prefix_remote_server?' do
422
+
423
+ after do
424
+ connection_options.delete(:database_prefix)
425
+ end
426
+
427
+ it 'returns false if database_prefix is not configured' do
428
+ assert_equal false, connection.database_prefix_remote_server?
429
+ end
430
+
431
+ it 'returns true if database_prefix has been set' do
432
+ connection_options[:database_prefix] = "server.database.schema."
433
+ assert_equal true, connection.database_prefix_remote_server?
434
+ end
435
+
436
+ it 'returns false if database_prefix has been set incorrectly' do
437
+ connection_options[:database_prefix] = "server.database.schema"
438
+ assert_equal false, connection.database_prefix_remote_server?
439
+ end
440
+
441
+ end
442
+
419
443
  end
420
444